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 178 private final ICarOccupantZoneCallback mOccupantZoneCallback = 179 new ICarOccupantZoneCallback.Stub() { 180 @Override 181 public void onOccupantZoneConfigChanged(int flags) throws RemoteException { 182 if ((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) != 0 183 || (flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER) != 0) { 184 initClusterDisplay(); 185 } 186 } 187 }; 188 189 @Override release()190 public void release() { 191 Slogf.d(TAG, "releaseClusterHomeService"); 192 mOccupantZoneService.unregisterCallback(mOccupantZoneCallback); 193 mClusterHalService.setCallback(null); 194 mClusterNavigationService.setClusterServiceCallback(null); 195 mClientListeners.kill(); 196 mClientNavigationListeners.kill(); 197 } 198 199 @Override 200 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)201 public void dump(IndentingPrintWriter writer) { 202 checkHasDumpPermissionGranted(mContext, "dump()"); 203 writer.println("*ClusterHomeService*"); 204 205 writer.increaseIndent(); 206 writer.printf("mServiceEnabled: %b\n", mServiceEnabled); 207 writer.printf("isLightMode: %b\n", mClusterHalService.isLightMode()); 208 writer.printf("mClusterDisplayId: %d\n", mClusterDisplayId); 209 writer.printf("mClusterHomeActivity: %s\n", mClusterHomeActivity); 210 writer.printf("mOnOff: %d\n", mOnOff); 211 writer.printf("mBounds: %s\n", mBounds); 212 writer.printf("mInsets: %s\n", mInsets); 213 writer.printf("mUiType: %d\n", mUiType); 214 writer.printf("mLastIntent: %s\n", mLastIntent); 215 writer.printf("mLastIntentUserId: %d\n", mLastIntentUserId); 216 mClusterHealthMonitor.dump(writer); 217 writer.decreaseIndent(); 218 } 219 220 @Override 221 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)222 public void dumpProto(ProtoOutputStream proto) {} 223 224 // ClusterHalEventListener starts 225 @Override onSwitchUi(int uiType)226 public void onSwitchUi(int uiType) { 227 Slogf.d(TAG, "onSwitchUi: uiType=%d", uiType); 228 int changes = 0; 229 if (mUiType != uiType) { 230 mUiType = uiType; 231 changes |= ClusterHomeManager.CONFIG_UI_TYPE; 232 } 233 sendDisplayState(changes); 234 } 235 236 @Override onDisplayState(int onOff, Rect bounds, Insets insets)237 public void onDisplayState(int onOff, Rect bounds, Insets insets) { 238 Slogf.d(TAG, "onDisplayState: onOff=%d, bounds=%s, insets=%s", onOff, bounds, insets); 239 int changes = 0; 240 if (onOff != DONT_CARE && mOnOff != onOff) { 241 mOnOff = onOff; 242 changes |= ClusterHomeManager.CONFIG_DISPLAY_ON_OFF; 243 } 244 if (bounds != null && !mBounds.equals(bounds)) { 245 mBounds = bounds; 246 changes |= ClusterHomeManager.CONFIG_DISPLAY_BOUNDS; 247 } 248 if (insets != null && !mInsets.equals(insets)) { 249 mInsets = insets; 250 changes |= ClusterHomeManager.CONFIG_DISPLAY_INSETS; 251 } 252 sendDisplayState(changes); 253 } 254 // ClusterHalEventListener ends 255 sendDisplayState(int changes)256 private void sendDisplayState(int changes) { 257 ClusterState state = createClusterState(); 258 int n = mClientListeners.beginBroadcast(); 259 for (int i = 0; i < n; i++) { 260 IClusterStateListener callback = mClientListeners.getBroadcastItem(i); 261 try { 262 callback.onClusterStateChanged(state, changes); 263 } catch (RemoteException ignores) { 264 // ignore 265 } 266 } 267 mClientListeners.finishBroadcast(); 268 } 269 270 // ClusterNavigationServiceCallback starts 271 @Override onNavigationStateChanged(Bundle bundle)272 public void onNavigationStateChanged(Bundle bundle) { 273 byte[] protoBytes = bundle.getByteArray(NAV_STATE_PROTO_BUNDLE_KEY); 274 275 sendNavigationState(protoBytes); 276 } 277 sendNavigationState(byte[] protoBytes)278 private void sendNavigationState(byte[] protoBytes) { 279 final int n = mClientNavigationListeners.beginBroadcast(); 280 for (int i = 0; i < n; i++) { 281 IClusterNavigationStateListener callback = 282 mClientNavigationListeners.getBroadcastItem(i); 283 try { 284 callback.onNavigationStateChanged(protoBytes); 285 } catch (RemoteException ignores) { 286 // ignore 287 } 288 } 289 mClientNavigationListeners.finishBroadcast(); 290 291 if (!mClusterHalService.isNavigationStateSupported()) { 292 Slogf.d(TAG, "No Cluster NavigationState HAL property"); 293 return; 294 } 295 mClusterHalService.sendNavigationState(protoBytes); 296 } 297 298 @Override getInstrumentClusterInfo()299 public CarNavigationInstrumentCluster getInstrumentClusterInfo() { 300 return CarNavigationInstrumentCluster.createCluster(DEFAULT_MIN_UPDATE_INTERVAL_MILLIS); 301 } 302 303 @Override notifyNavContextOwnerChanged(ClusterNavigationService.ContextOwner owner)304 public void notifyNavContextOwnerChanged(ClusterNavigationService.ContextOwner owner) { 305 Slogf.d(TAG, "notifyNavContextOwnerChanged: owner=%s", owner); 306 // Sends the empty NavigationStateProto to clear out the last direction 307 // when the app context owner is changed or the navigation is finished. 308 NavigationStateProto emptyProto = NavigationStateProto.newBuilder() 309 .setServiceStatus(NavigationStateProto.ServiceStatus.NORMAL).build(); 310 sendNavigationState(emptyProto.toByteArray()); 311 } 312 // ClusterNavigationServiceCallback ends 313 314 // IClusterHomeService starts 315 @Override reportState(int uiTypeMain, int uiTypeSub, byte[] uiAvailability)316 public void reportState(int uiTypeMain, int uiTypeSub, byte[] uiAvailability) { 317 Slogf.d(TAG, "reportState: main=%d, sub=%d", uiTypeMain, uiTypeSub); 318 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 319 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 320 321 mUiType = uiTypeMain; 322 mClusterHalService.reportState(mOnOff, mBounds, mInsets, 323 uiTypeMain, uiTypeSub, uiAvailability); 324 } 325 326 @Override requestDisplay(int uiType)327 public void requestDisplay(int uiType) { 328 Slogf.d(TAG, "requestDisplay: uiType=%d", uiType); 329 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 330 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 331 332 mClusterHalService.requestDisplay(uiType); 333 } 334 335 @Override startFixedActivityModeAsUser(Intent intent, Bundle activityOptionsBundle, int userId)336 public boolean startFixedActivityModeAsUser(Intent intent, 337 Bundle activityOptionsBundle, int userId) { 338 Slogf.d(TAG, "startFixedActivityModeAsUser: intent=%s, userId=%d", intent, userId); 339 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 340 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 341 if (mClusterDisplayId == Display.INVALID_DISPLAY) { 342 Slogf.e(TAG, "Cluster display is not ready."); 343 return false; 344 } 345 346 ActivityOptions activityOptions = activityOptionsBundle != null 347 ? createActivityOptions(activityOptionsBundle) 348 : ActivityOptions.makeBasic(); 349 activityOptions.setLaunchDisplayId(mClusterDisplayId); 350 mLastIntent = intent; 351 mLastIntentUserId = userId; 352 return mFixedActivityService.startFixedActivityModeForDisplayAndUser( 353 intent, activityOptions, mClusterDisplayId, userId); 354 } 355 356 @Override stopFixedActivityMode()357 public void stopFixedActivityMode() { 358 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 359 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 360 if (mClusterDisplayId == Display.INVALID_DISPLAY) { 361 Slogf.e(TAG, "Cluster display is not ready."); 362 return; 363 } 364 365 mFixedActivityService.stopFixedActivityMode(mClusterDisplayId); 366 } 367 368 @Override registerClusterStateListener(IClusterStateListener listener)369 public void registerClusterStateListener(IClusterStateListener listener) { 370 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 371 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 372 373 mClientListeners.register(listener); 374 } 375 376 @Override unregisterClusterStateListener(IClusterStateListener listener)377 public void unregisterClusterStateListener(IClusterStateListener listener) { 378 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 379 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 380 381 mClientListeners.unregister(listener); 382 } 383 384 @Override registerClusterNavigationStateListener(IClusterNavigationStateListener listener)385 public void registerClusterNavigationStateListener(IClusterNavigationStateListener listener) { 386 enforcePermission(Car.PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE); 387 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 388 389 mClientNavigationListeners.register(listener); 390 } 391 392 @Override unregisterClusterNavigationStateListener(IClusterNavigationStateListener listener)393 public void unregisterClusterNavigationStateListener(IClusterNavigationStateListener listener) { 394 enforcePermission(Car.PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE); 395 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 396 397 mClientNavigationListeners.unregister(listener); 398 } 399 400 @Override getClusterState()401 public ClusterState getClusterState() { 402 Slogf.d(TAG, "getClusterState"); 403 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 404 if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled"); 405 return createClusterState(); 406 } 407 408 @Override sendHeartbeat(long epochTimeNs, byte[] appMetadata)409 public void sendHeartbeat(long epochTimeNs, byte[] appMetadata) { 410 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 411 mClusterHealthMonitor.sendHeartbeat(epochTimeNs, appMetadata); 412 } 413 414 @Override startVisibilityMonitoring(SurfaceControl surface)415 public void startVisibilityMonitoring(SurfaceControl surface) { 416 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 417 mClusterHealthMonitor.startVisibilityMonitoring(surface); 418 } 419 420 @Override stopVisibilityMonitoring()421 public void stopVisibilityMonitoring() { 422 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 423 mClusterHealthMonitor.stopVisibilityMonitoring(); 424 } 425 // IClusterHomeService ends 426 enforcePermission(String permissionName)427 private void enforcePermission(String permissionName) { 428 if (mContext.checkCallingOrSelfPermission(permissionName) 429 != PackageManager.PERMISSION_GRANTED) { 430 throw new SecurityException("requires permission " + permissionName); 431 } 432 } 433 createClusterState()434 private ClusterState createClusterState() { 435 ClusterState state = new ClusterState(); 436 state.on = mOnOff == DISPLAY_ON; 437 state.bounds = mBounds; 438 state.insets = mInsets; 439 state.uiType = mUiType; 440 state.displayId = mClusterDisplayId; 441 return state; 442 } 443 } 444