1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.cluster; 18 19 import static android.car.builtin.app.ActivityManagerHelper.createActivityOptions; 20 import static android.content.Intent.ACTION_MAIN; 21 22 import static com.android.car.PermissionHelper.checkHasDumpPermissionGranted; 23 import static com.android.car.hal.ClusterHalService.DISPLAY_OFF; 24 import static com.android.car.hal.ClusterHalService.DISPLAY_ON; 25 import static com.android.car.hal.ClusterHalService.DONT_CARE; 26 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 27 28 import android.app.ActivityOptions; 29 import android.car.Car; 30 import android.car.CarOccupantZoneManager; 31 import android.car.ICarOccupantZoneCallback; 32 import android.car.builtin.os.UserManagerHelper; 33 import android.car.builtin.util.Slogf; 34 import android.car.cluster.ClusterHomeManager; 35 import android.car.cluster.ClusterState; 36 import android.car.cluster.IClusterHomeService; 37 import android.car.cluster.IClusterNavigationStateListener; 38 import android.car.cluster.IClusterStateListener; 39 import android.car.cluster.navigation.NavigationState.NavigationStateProto; 40 import android.car.navigation.CarNavigationInstrumentCluster; 41 import android.content.ComponentName; 42 import android.content.Context; 43 import android.content.Intent; 44 import android.content.pm.PackageManager; 45 import android.graphics.Insets; 46 import android.graphics.Point; 47 import android.graphics.Rect; 48 import android.hardware.display.DisplayManager; 49 import android.os.Bundle; 50 import android.os.RemoteCallbackList; 51 import android.os.RemoteException; 52 import android.text.TextUtils; 53 import android.util.proto.ProtoOutputStream; 54 import android.view.Display; 55 import android.view.SurfaceControl; 56 57 import com.android.car.CarLog; 58 import com.android.car.CarOccupantZoneService; 59 import com.android.car.CarServiceBase; 60 import com.android.car.R; 61 import com.android.car.am.FixedActivityService; 62 import com.android.car.hal.ClusterHalService; 63 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 64 import com.android.car.internal.util.IndentingPrintWriter; 65 66 /** 67 * Service responsible for interactions between ClusterOS and ClusterHome. 68 */ 69 public final class ClusterHomeService extends IClusterHomeService.Stub 70 implements CarServiceBase, ClusterNavigationService.ClusterNavigationServiceCallback, 71 ClusterHalService.ClusterHalEventCallback { 72 private static final String TAG = CarLog.TAG_CLUSTER; 73 private static final int DEFAULT_MIN_UPDATE_INTERVAL_MILLIS = 1000; 74 private static final String NAV_STATE_PROTO_BUNDLE_KEY = "navstate2"; 75 76 private final Context mContext; 77 private final ClusterHalService mClusterHalService; 78 private final ClusterNavigationService mClusterNavigationService; 79 private final CarOccupantZoneService mOccupantZoneService; 80 private final FixedActivityService mFixedActivityService; 81 private final ComponentName mClusterHomeActivity; 82 private final ClusterHealthMonitor mClusterHealthMonitor; 83 84 private boolean mServiceEnabled; 85 86 private int mClusterDisplayId = Display.INVALID_DISPLAY; 87 88 private int mOnOff = DISPLAY_OFF; 89 private Rect mBounds = new Rect(); 90 private Insets mInsets = Insets.NONE; 91 private int mUiType = ClusterHomeManager.UI_TYPE_CLUSTER_HOME; 92 private Intent mLastIntent; 93 private int mLastIntentUserId = UserManagerHelper.USER_SYSTEM; 94 95 private final RemoteCallbackList<IClusterStateListener> mClientListeners = 96 new RemoteCallbackList<>(); 97 98 private final RemoteCallbackList<IClusterNavigationStateListener> mClientNavigationListeners = 99 new RemoteCallbackList<>(); 100 ClusterHomeService(Context context, ClusterHalService clusterHalService, ClusterNavigationService navigationService, CarOccupantZoneService occupantZoneService, FixedActivityService fixedActivityService)101 public ClusterHomeService(Context context, ClusterHalService clusterHalService, 102 ClusterNavigationService navigationService, 103 CarOccupantZoneService occupantZoneService, 104 FixedActivityService fixedActivityService) { 105 mContext = context; 106 mClusterHalService = clusterHalService; 107 mClusterNavigationService = navigationService; 108 mOccupantZoneService = occupantZoneService; 109 mFixedActivityService = fixedActivityService; 110 mClusterHomeActivity = ComponentName.unflattenFromString( 111 mContext.getString(R.string.config_clusterHomeActivity)); 112 mClusterHealthMonitor = new ClusterHealthMonitor(mContext, mClusterHalService); 113 mLastIntent = new Intent(ACTION_MAIN).setComponent(mClusterHomeActivity); 114 } 115 116 @Override init()117 public void init() { 118 Slogf.d(TAG, "initClusterHomeService"); 119 if (TextUtils.isEmpty(mClusterHomeActivity.getPackageName()) 120 || TextUtils.isEmpty(mClusterHomeActivity.getClassName())) { 121 Slogf.i(TAG, "Improper ClusterHomeActivity: %s", mClusterHomeActivity); 122 return; 123 } 124 if (!mClusterHalService.isServiceEnabled()) { 125 Slogf.e(TAG, "ClusterHomeService is disabled. To enable, it must be either in LIGHT " 126 + "mode, or all core properties must be defined in FULL mode."); 127 return; 128 } 129 // In FULL mode mOnOff is set to 'OFF', and can be changed by the CLUSTER_DISPLAY_STATE 130 // property. In LIGHT mode, we set it to 'ON' because the CLUSTER_DISPLAY_STATE property may 131 // not be available, and we do not subscribe to it. 132 if (mClusterHalService.isLightMode()) { 133 mOnOff = DISPLAY_ON; 134 } 135 136 mServiceEnabled = true; 137 mClusterHalService.setCallback(this); 138 mClusterNavigationService.setClusterServiceCallback(this); 139 140 mOccupantZoneService.registerCallback(mOccupantZoneCallback); 141 142 initClusterDisplay(); 143 } 144 initClusterDisplay()145 private void initClusterDisplay() { 146 int clusterDisplayId = mOccupantZoneService.getDisplayIdForDriver( 147 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER); 148 Slogf.d(TAG, "initClusterDisplay: displayId=%d", clusterDisplayId); 149 if (clusterDisplayId == Display.INVALID_DISPLAY) { 150 Slogf.i(TAG, "No cluster display is defined"); 151 } 152 if (clusterDisplayId == mClusterDisplayId) { 153 return; // Skip if the cluster display isn't changed. 154 } 155 mClusterDisplayId = clusterDisplayId; 156 sendDisplayState(ClusterHomeManager.CONFIG_DISPLAY_ID); 157 if (clusterDisplayId == Display.INVALID_DISPLAY) { 158 return; 159 } 160 161 // Initialize mBounds only once. 162 if (mBounds.right == 0 && mBounds.bottom == 0 && mBounds.left == 0 && mBounds.top == 0) { 163 DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); 164 Display clusterDisplay = displayManager.getDisplay(clusterDisplayId); 165 Point size = new Point(); 166 clusterDisplay.getRealSize(size); 167 mBounds.right = size.x; 168 mBounds.bottom = size.y; 169 Slogf.d(TAG, "Found cluster displayId=%d, bounds=%s", clusterDisplayId, mBounds); 170 } 171 172 ActivityOptions activityOptions = ActivityOptions.makeBasic() 173 .setLaunchDisplayId(clusterDisplayId); 174 mFixedActivityService.startFixedActivityModeForDisplayAndUser( 175 mLastIntent, activityOptions, clusterDisplayId, mLastIntentUserId); 176 177 Slogf.i(TAG, "Initialized cluster display %d", clusterDisplayId); 178 } 179 180 private final ICarOccupantZoneCallback mOccupantZoneCallback = 181 new ICarOccupantZoneCallback.Stub() { 182 @Override 183 public void onOccupantZoneConfigChanged(int flags) throws RemoteException { 184 if ((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) != 0 185 || (flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER) != 0) { 186 initClusterDisplay(); 187 } 188 } 189 }; 190 191 @Override release()192 public void release() { 193 Slogf.d(TAG, "releaseClusterHomeService"); 194 mOccupantZoneService.unregisterCallback(mOccupantZoneCallback); 195 mClusterHalService.setCallback(null); 196 mClusterNavigationService.setClusterServiceCallback(null); 197 mClientListeners.kill(); 198 mClientNavigationListeners.kill(); 199 } 200 201 @Override 202 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)203 public void dump(IndentingPrintWriter writer) { 204 checkHasDumpPermissionGranted(mContext, "dump()"); 205 writer.println("*ClusterHomeService*"); 206 207 writer.increaseIndent(); 208 writer.printf("mServiceEnabled: %b\n", mServiceEnabled); 209 writer.printf("isLightMode: %b\n", mClusterHalService.isLightMode()); 210 writer.printf("mClusterDisplayId: %d\n", mClusterDisplayId); 211 writer.printf("mClusterHomeActivity: %s\n", mClusterHomeActivity); 212 writer.printf("mOnOff: %d\n", mOnOff); 213 writer.printf("mBounds: %s\n", mBounds); 214 writer.printf("mInsets: %s\n", mInsets); 215 writer.printf("mUiType: %d\n", mUiType); 216 writer.printf("mLastIntent: %s\n", mLastIntent); 217 writer.printf("mLastIntentUserId: %d\n", mLastIntentUserId); 218 mClusterHealthMonitor.dump(writer); 219 writer.decreaseIndent(); 220 } 221 222 @Override 223 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)224 public void dumpProto(ProtoOutputStream proto) {} 225 226 // ClusterHalEventListener starts 227 @Override onSwitchUi(int uiType)228 public void onSwitchUi(int uiType) { 229 Slogf.d(TAG, "onSwitchUi: uiType=%d", uiType); 230 int changes = 0; 231 if (mUiType != uiType) { 232 mUiType = uiType; 233 changes |= ClusterHomeManager.CONFIG_UI_TYPE; 234 } 235 sendDisplayState(changes); 236 } 237 238 @Override onDisplayState(int onOff, Rect bounds, Insets insets)239 public void onDisplayState(int onOff, Rect bounds, Insets insets) { 240 Slogf.d(TAG, "onDisplayState: onOff=%d, bounds=%s, insets=%s", onOff, bounds, insets); 241 int changes = 0; 242 if (onOff != DONT_CARE && mOnOff != onOff) { 243 mOnOff = onOff; 244 changes |= ClusterHomeManager.CONFIG_DISPLAY_ON_OFF; 245 } 246 if (bounds != null && !mBounds.equals(bounds)) { 247 mBounds = bounds; 248 changes |= ClusterHomeManager.CONFIG_DISPLAY_BOUNDS; 249 } 250 if (insets != null && !mInsets.equals(insets)) { 251 mInsets = insets; 252 changes |= ClusterHomeManager.CONFIG_DISPLAY_INSETS; 253 } 254 sendDisplayState(changes); 255 } 256 // ClusterHalEventListener ends 257 sendDisplayState(int changes)258 private void sendDisplayState(int changes) { 259 ClusterState state = createClusterState(); 260 int n = mClientListeners.beginBroadcast(); 261 for (int i = 0; i < n; i++) { 262 IClusterStateListener callback = mClientListeners.getBroadcastItem(i); 263 try { 264 callback.onClusterStateChanged(state, changes); 265 } catch (RemoteException ignores) { 266 // ignore 267 } 268 } 269 mClientListeners.finishBroadcast(); 270 } 271 272 // ClusterNavigationServiceCallback starts 273 @Override onNavigationStateChanged(Bundle bundle)274 public void onNavigationStateChanged(Bundle bundle) { 275 byte[] protoBytes = bundle.getByteArray(NAV_STATE_PROTO_BUNDLE_KEY); 276 277 sendNavigationState(protoBytes); 278 } 279 sendNavigationState(byte[] protoBytes)280 private void sendNavigationState(byte[] protoBytes) { 281 final int n = mClientNavigationListeners.beginBroadcast(); 282 for (int i = 0; i < n; i++) { 283 IClusterNavigationStateListener callback = 284 mClientNavigationListeners.getBroadcastItem(i); 285 try { 286 callback.onNavigationStateChanged(protoBytes); 287 } catch (RemoteException ignores) { 288 // ignore 289 } 290 } 291 mClientNavigationListeners.finishBroadcast(); 292 293 if (!mClusterHalService.isNavigationStateSupported()) { 294 Slogf.d(TAG, "No Cluster NavigationState HAL property"); 295 return; 296 } 297 mClusterHalService.sendNavigationState(protoBytes); 298 } 299 300 @Override getInstrumentClusterInfo()301 public CarNavigationInstrumentCluster getInstrumentClusterInfo() { 302 return CarNavigationInstrumentCluster.createCluster(DEFAULT_MIN_UPDATE_INTERVAL_MILLIS); 303 } 304 305 @Override notifyNavContextOwnerChanged(ClusterNavigationService.ContextOwner owner)306 public void notifyNavContextOwnerChanged(ClusterNavigationService.ContextOwner owner) { 307 Slogf.d(TAG, "notifyNavContextOwnerChanged: owner=%s", owner); 308 // Sends the empty NavigationStateProto to clear out the last direction 309 // when the app context owner is changed or the navigation is finished. 310 NavigationStateProto emptyProto = NavigationStateProto.newBuilder() 311 .setServiceStatus(NavigationStateProto.ServiceStatus.NORMAL).build(); 312 sendNavigationState(emptyProto.toByteArray()); 313 } 314 // ClusterNavigationServiceCallback ends 315 316 // IClusterHomeService starts 317 @Override reportState(int uiTypeMain, int uiTypeSub, byte[] uiAvailability)318 public void reportState(int uiTypeMain, int uiTypeSub, byte[] uiAvailability) { 319 Slogf.d(TAG, "reportState: main=%d, sub=%d", uiTypeMain, uiTypeSub); 320 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 321 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 322 323 mUiType = uiTypeMain; 324 mClusterHalService.reportState(mOnOff, mBounds, mInsets, 325 uiTypeMain, uiTypeSub, uiAvailability); 326 } 327 328 @Override requestDisplay(int uiType)329 public void requestDisplay(int uiType) { 330 Slogf.d(TAG, "requestDisplay: uiType=%d", uiType); 331 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 332 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 333 334 mClusterHalService.requestDisplay(uiType); 335 } 336 337 @Override startFixedActivityModeAsUser(Intent intent, Bundle activityOptionsBundle, int userId)338 public boolean startFixedActivityModeAsUser(Intent intent, 339 Bundle activityOptionsBundle, int userId) { 340 Slogf.d(TAG, "startFixedActivityModeAsUser: intent=%s, userId=%d", intent, userId); 341 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 342 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 343 if (mClusterDisplayId == Display.INVALID_DISPLAY) { 344 Slogf.e(TAG, "Cluster display is not ready."); 345 return false; 346 } 347 348 ActivityOptions activityOptions = activityOptionsBundle != null 349 ? createActivityOptions(activityOptionsBundle) 350 : ActivityOptions.makeBasic(); 351 activityOptions.setLaunchDisplayId(mClusterDisplayId); 352 mLastIntent = intent; 353 mLastIntentUserId = userId; 354 return mFixedActivityService.startFixedActivityModeForDisplayAndUser( 355 intent, activityOptions, mClusterDisplayId, userId); 356 } 357 358 @Override stopFixedActivityMode()359 public void stopFixedActivityMode() { 360 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 361 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 362 if (mClusterDisplayId == Display.INVALID_DISPLAY) { 363 Slogf.e(TAG, "Cluster display is not ready."); 364 return; 365 } 366 367 mFixedActivityService.stopFixedActivityMode(mClusterDisplayId); 368 } 369 370 @Override registerClusterStateListener(IClusterStateListener listener)371 public void registerClusterStateListener(IClusterStateListener listener) { 372 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 373 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 374 375 mClientListeners.register(listener); 376 } 377 378 @Override unregisterClusterStateListener(IClusterStateListener listener)379 public void unregisterClusterStateListener(IClusterStateListener listener) { 380 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 381 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 382 383 mClientListeners.unregister(listener); 384 } 385 386 @Override registerClusterNavigationStateListener(IClusterNavigationStateListener listener)387 public void registerClusterNavigationStateListener(IClusterNavigationStateListener listener) { 388 enforcePermission(Car.PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE); 389 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 390 391 mClientNavigationListeners.register(listener); 392 } 393 394 @Override unregisterClusterNavigationStateListener(IClusterNavigationStateListener listener)395 public void unregisterClusterNavigationStateListener(IClusterNavigationStateListener listener) { 396 enforcePermission(Car.PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE); 397 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 398 399 mClientNavigationListeners.unregister(listener); 400 } 401 402 @Override getClusterState()403 public ClusterState getClusterState() { 404 Slogf.d(TAG, "getClusterState"); 405 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 406 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 407 return createClusterState(); 408 } 409 410 @Override sendHeartbeat(long epochTimeNs, byte[] appMetadata)411 public void sendHeartbeat(long epochTimeNs, byte[] appMetadata) { 412 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 413 mClusterHealthMonitor.sendHeartbeat(epochTimeNs, appMetadata); 414 } 415 416 @Override startVisibilityMonitoring(SurfaceControl surface)417 public void startVisibilityMonitoring(SurfaceControl surface) { 418 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 419 mClusterHealthMonitor.startVisibilityMonitoring(surface); 420 } 421 422 @Override stopVisibilityMonitoring()423 public void stopVisibilityMonitoring() { 424 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 425 mClusterHealthMonitor.stopVisibilityMonitoring(); 426 } 427 // IClusterHomeService ends 428 enforcePermission(String permissionName)429 private void enforcePermission(String permissionName) { 430 if (mContext.checkCallingOrSelfPermission(permissionName) 431 != PackageManager.PERMISSION_GRANTED) { 432 throw new SecurityException("requires permission " + permissionName); 433 } 434 } 435 createClusterState()436 private ClusterState createClusterState() { 437 ClusterState state = new ClusterState(); 438 state.on = mOnOff == DISPLAY_ON; 439 state.bounds = mBounds; 440 state.insets = mInsets; 441 state.uiType = mUiType; 442 state.displayId = mClusterDisplayId; 443 return state; 444 } 445 } 446