1 /* 2 * Copyright (C) 2016 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 package com.android.car.cluster; 17 18 import static android.car.builtin.app.ActivityManagerHelper.createActivityOptions; 19 import static android.car.cluster.renderer.InstrumentClusterRenderingService.EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER; 20 import static android.car.settings.CarSettings.Global.DISABLE_INSTRUMENTATION_SERVICE; 21 22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEPRECATED_CODE; 23 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 24 25 import android.annotation.SystemApi; 26 import android.app.ActivityOptions; 27 import android.car.builtin.util.Slogf; 28 import android.car.cluster.IInstrumentClusterManagerCallback; 29 import android.car.cluster.IInstrumentClusterManagerService; 30 import android.car.cluster.renderer.IInstrumentCluster; 31 import android.car.cluster.renderer.IInstrumentClusterHelper; 32 import android.car.cluster.renderer.IInstrumentClusterNavigation; 33 import android.car.navigation.CarNavigationInstrumentCluster; 34 import android.content.ComponentName; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.ServiceConnection; 38 import android.os.Binder; 39 import android.os.Bundle; 40 import android.os.Handler; 41 import android.os.IBinder; 42 import android.os.Message; 43 import android.os.RemoteException; 44 import android.os.SystemClock; 45 import android.os.UserHandle; 46 import android.provider.Settings; 47 import android.text.TextUtils; 48 import android.util.Log; 49 import android.util.proto.ProtoOutputStream; 50 import android.view.KeyEvent; 51 52 import com.android.car.CarInputService; 53 import com.android.car.CarInputService.KeyEventListener; 54 import com.android.car.CarLocalServices; 55 import com.android.car.CarLog; 56 import com.android.car.CarServiceBase; 57 import com.android.car.R; 58 import com.android.car.am.FixedActivityService; 59 import com.android.car.cluster.ClusterNavigationService.ContextOwner; 60 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 61 import com.android.car.internal.util.IndentingPrintWriter; 62 import com.android.car.user.CarUserService; 63 import com.android.internal.annotations.GuardedBy; 64 import com.android.internal.annotations.VisibleForTesting; 65 66 import java.lang.ref.WeakReference; 67 68 /** 69 * Service responsible for interaction with car's instrument cluster. 70 * 71 * @hide 72 */ 73 @SystemApi 74 public class InstrumentClusterService implements CarServiceBase, KeyEventListener, 75 ClusterNavigationService.ClusterNavigationServiceCallback { 76 77 @VisibleForTesting 78 static final String TAG = CarLog.TAG_CLUSTER; 79 80 private static final ContextOwner NO_OWNER = new ContextOwner(0, 0); 81 82 private static final long RENDERER_SERVICE_WAIT_TIMEOUT_MS = 5000; 83 private static final long RENDERER_WAIT_MAX_RETRY = 2; 84 85 private final Context mContext; 86 private final CarInputService mCarInputService; 87 private final ClusterNavigationService mClusterNavigationService; 88 private final long mRendererServiceWaitTimeoutMs; 89 /** 90 * TODO(b/121277787): Remove this on main. 91 * 92 * @deprecated CarInstrumentClusterManager is being deprecated. 93 */ 94 @Deprecated 95 private final ClusterManagerService mClusterManagerService = new ClusterManagerService(); 96 private final Object mLock = new Object(); 97 @GuardedBy("mLock") 98 private ContextOwner mNavContextOwner = NO_OWNER; 99 @GuardedBy("mLock") 100 private IInstrumentCluster mRendererService; 101 // If renderer service crashed / stopped and this class fails to rebind with it immediately, 102 // we should wait some time before next attempt. This may happen during APK update for example. 103 private final DeferredRebinder mDeferredRebinder; 104 // Whether {@link android.car.cluster.renderer.InstrumentClusterRendererService} is bound 105 // (although not necessarily connected) 106 @GuardedBy("mLock") 107 private boolean mRendererBound = false; 108 109 private final String mRenderingServiceConfig; 110 111 @GuardedBy("mLock") 112 private IInstrumentClusterNavigation mIInstrumentClusterNavigationFromRenderer; 113 114 @Override onNavigationStateChanged(Bundle bundle)115 public void onNavigationStateChanged(Bundle bundle) { 116 // No retry here as new events will be sent later. 117 IInstrumentClusterNavigation navigationBinder = getNavigationBinder(); 118 if (navigationBinder == null) { 119 Slogf.e(TAG, "onNavigationStateChanged failed, renderer not ready, Bundle:" + bundle); 120 return; 121 } 122 try { 123 navigationBinder.onNavigationStateChanged(bundle); 124 } catch (RemoteException e) { 125 Slogf.e(TAG, "onNavigationStateChanged failed, bundle:" + bundle, e); 126 } 127 } 128 129 @Override getInstrumentClusterInfo()130 public CarNavigationInstrumentCluster getInstrumentClusterInfo() { 131 // Failure in this call leads into an issue in the client, so throw exception 132 // when it cannot be recovered / retried. 133 for (int i = 0; i < RENDERER_WAIT_MAX_RETRY; i++) { 134 IInstrumentClusterNavigation navigationBinder = getNavigationBinder(); 135 if (navigationBinder == null) { 136 continue; 137 } 138 try { 139 return navigationBinder.getInstrumentClusterInfo(); 140 } catch (RemoteException e) { 141 Slogf.e(TAG, "getInstrumentClusterInfo failed", e); 142 } 143 } 144 throw new IllegalStateException("cannot access renderer service"); 145 } 146 147 @Override notifyNavContextOwnerChanged(ContextOwner owner)148 public void notifyNavContextOwnerChanged(ContextOwner owner) { 149 IInstrumentCluster service = getInstrumentClusterRendererService(); 150 if (service != null) { 151 notifyNavContextOwnerChanged(service, owner); 152 } 153 } 154 155 /** 156 * Connection to {@link android.car.cluster.renderer.InstrumentClusterRendererService} 157 */ 158 @VisibleForTesting 159 final ServiceConnection mRendererServiceConnection = new ServiceConnection() { 160 @Override 161 public void onServiceConnected(ComponentName name, IBinder binder) { 162 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 163 Slogf.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder); 164 } 165 IInstrumentCluster service = IInstrumentCluster.Stub.asInterface(binder); 166 ContextOwner navContextOwner; 167 synchronized (mLock) { 168 mRendererService = service; 169 navContextOwner = mNavContextOwner; 170 mLock.notifyAll(); 171 } 172 if (navContextOwner != null && service != null) { 173 notifyNavContextOwnerChanged(service, navContextOwner); 174 } 175 } 176 177 @Override 178 public void onServiceDisconnected(ComponentName name) { 179 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 180 Slogf.d(TAG, "onServiceDisconnected, name: " + name); 181 } 182 mContext.unbindService(this); 183 synchronized (mLock) { 184 mRendererBound = false; 185 mRendererService = null; 186 mIInstrumentClusterNavigationFromRenderer = null; 187 188 } 189 mDeferredRebinder.rebind(); 190 } 191 }; 192 193 private final IInstrumentClusterHelper mInstrumentClusterHelper = 194 new IInstrumentClusterHelper.Stub() { 195 @Override 196 public boolean startFixedActivityModeForDisplayAndUser(Intent intent, 197 Bundle activityOptionsBundle, int userId) { 198 Binder.clearCallingIdentity(); 199 ActivityOptions options = activityOptionsBundle != null 200 ? createActivityOptions(activityOptionsBundle) 201 : ActivityOptions.makeBasic(); 202 FixedActivityService service = CarLocalServices.getService( 203 FixedActivityService.class); 204 return service.startFixedActivityModeForDisplayAndUser(intent, options, 205 options.getLaunchDisplayId(), userId); 206 } 207 208 @Override 209 public void stopFixedActivityMode(int displayId) { 210 Binder.clearCallingIdentity(); 211 FixedActivityService service = CarLocalServices.getService( 212 FixedActivityService.class); 213 service.stopFixedActivityMode(displayId); 214 } 215 }; 216 InstrumentClusterService(Context context, ClusterNavigationService navigationService, CarInputService carInputService)217 public InstrumentClusterService(Context context, ClusterNavigationService navigationService, 218 CarInputService carInputService) { 219 this(context, navigationService, carInputService, RENDERER_SERVICE_WAIT_TIMEOUT_MS); 220 } 221 222 @VisibleForTesting InstrumentClusterService(Context context, ClusterNavigationService navigationService, CarInputService carInputService, long rendererServiceWaitTimeoutMs)223 InstrumentClusterService(Context context, ClusterNavigationService navigationService, 224 CarInputService carInputService, long rendererServiceWaitTimeoutMs) { 225 mContext = context; 226 mClusterNavigationService = navigationService; 227 mCarInputService = carInputService; 228 mRenderingServiceConfig = mContext.getString(R.string.instrumentClusterRendererService); 229 mDeferredRebinder = new DeferredRebinder(this); 230 mRendererServiceWaitTimeoutMs = rendererServiceWaitTimeoutMs; 231 } 232 233 @GuardedBy("mLock") waitForRendererLocked()234 private IInstrumentCluster waitForRendererLocked() { 235 long remainingTime = mRendererServiceWaitTimeoutMs; 236 long endTime = SystemClock.uptimeMillis() + remainingTime; 237 try { 238 while (mRendererService == null && remainingTime > 0) { 239 mLock.wait(remainingTime); 240 remainingTime = endTime - SystemClock.uptimeMillis(); 241 } 242 } catch (InterruptedException e) { 243 Slogf.d(TAG, "waitForRenderer, interrupted", e); 244 Thread.currentThread().interrupt(); 245 } 246 return mRendererService; 247 } 248 getNavigationBinder()249 private IInstrumentClusterNavigation getNavigationBinder() { 250 IInstrumentCluster renderer; 251 synchronized (mLock) { 252 if (mIInstrumentClusterNavigationFromRenderer != null) { 253 return mIInstrumentClusterNavigationFromRenderer; 254 } 255 renderer = waitForRendererLocked(); 256 } 257 IInstrumentClusterNavigation navigationBinder = null; 258 for (int i = 0; i < RENDERER_WAIT_MAX_RETRY; i++) { 259 if (renderer == null) { 260 synchronized (mLock) { 261 renderer = waitForRendererLocked(); 262 } 263 if (renderer == null) { 264 continue; 265 } 266 } 267 try { 268 navigationBinder = renderer.getNavigationService(); 269 break; 270 } catch (RemoteException e) { 271 Slogf.e(TAG, "RemoteException from renderer", e); 272 renderer = null; 273 } 274 } 275 if (navigationBinder == null) { 276 return navigationBinder; 277 } 278 synchronized (mLock) { 279 mIInstrumentClusterNavigationFromRenderer = navigationBinder; 280 } 281 return navigationBinder; 282 } 283 284 @Override init()285 public void init() { 286 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 287 Slogf.d(TAG, "init"); 288 } 289 290 // TODO(b/124246323) Start earlier once data storage for cluster is clarified 291 // for early boot. 292 if (!isRendererServiceEnabled()) { 293 synchronized (mLock) { 294 mRendererBound = false; 295 } 296 return; 297 } 298 mClusterNavigationService.setClusterServiceCallback(this); 299 mCarInputService.setInstrumentClusterKeyListener(this /* KeyEventListener */); 300 CarLocalServices.getService(CarUserService.class).runOnUser0Unlock(() -> { 301 boolean bound = bindInstrumentClusterRendererService(); 302 synchronized (mLock) { 303 mRendererBound = bound; 304 } 305 }); 306 } 307 308 @Override release()309 public void release() { 310 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 311 Slogf.d(TAG, "release"); 312 } 313 314 synchronized (mLock) { 315 if (mRendererBound) { 316 mContext.unbindService(mRendererServiceConnection); 317 mRendererBound = false; 318 } 319 } 320 } 321 322 @Override 323 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)324 public void dump(IndentingPrintWriter writer) { 325 writer.println("**" + getClass().getSimpleName() + "**"); 326 synchronized (mLock) { 327 writer.println("bound with renderer: " + mRendererBound); 328 writer.println("renderer service: " + mRendererService); 329 writer.println("context owner: " + mNavContextOwner); 330 writer.println("mRenderingServiceConfig:" + mRenderingServiceConfig); 331 writer.println("mIInstrumentClusterNavigationFromRenderer:" 332 + mIInstrumentClusterNavigationFromRenderer); 333 } 334 } 335 336 @Override 337 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)338 public void dumpProto(ProtoOutputStream proto) {} 339 notifyNavContextOwnerChanged(IInstrumentCluster service, ContextOwner owner)340 private static void notifyNavContextOwnerChanged(IInstrumentCluster service, 341 ContextOwner owner) { 342 try { 343 service.setNavigationContextOwner(owner.uid, owner.pid); 344 } catch (RemoteException e) { 345 Slogf.e(TAG, "Failed to call setNavigationContextOwner", e); 346 } 347 } 348 isRendererServiceEnabled()349 private boolean isRendererServiceEnabled() { 350 if (TextUtils.isEmpty(mRenderingServiceConfig)) { 351 Slogf.d(TAG, "Instrument cluster renderer was not configured"); 352 return false; 353 } 354 boolean explicitlyDisabled = "true".equals(Settings.Global 355 .getString(mContext.getContentResolver(), DISABLE_INSTRUMENTATION_SERVICE)); 356 if (explicitlyDisabled) { 357 Slogf.i(TAG, "Instrument cluster renderer explicitly disabled by settings"); 358 return false; 359 } 360 return true; 361 } 362 bindInstrumentClusterRendererService()363 private boolean bindInstrumentClusterRendererService() { 364 if (!isRendererServiceEnabled()) { 365 return false; 366 } 367 368 Slogf.d(TAG, "bindInstrumentClusterRendererService, component: " + mRenderingServiceConfig); 369 370 Intent intent = new Intent(); 371 intent.setComponent(ComponentName.unflattenFromString(mRenderingServiceConfig)); 372 // Litle bit inefficiency here as Intent.getIBinderExtra() is a hidden API. 373 Bundle bundle = new Bundle(); 374 bundle.putBinder(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER, 375 mInstrumentClusterHelper.asBinder()); 376 intent.putExtra(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER, bundle); 377 return mContext.bindServiceAsUser(intent, mRendererServiceConnection, 378 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM); 379 } 380 381 /** 382 * @deprecated {@link android.car.cluster.CarInstrumentClusterManager} is now deprecated. 383 */ 384 @ExcludeFromCodeCoverageGeneratedReport(reason = DEPRECATED_CODE) 385 @Deprecated getManagerService()386 public IInstrumentClusterManagerService.Stub getManagerService() { 387 return mClusterManagerService; 388 } 389 390 @Override onKeyEvent(KeyEvent event)391 public void onKeyEvent(KeyEvent event) { 392 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 393 Slogf.d(TAG, "InstrumentClusterService#onKeyEvent: " + event); 394 } 395 396 IInstrumentCluster service = getInstrumentClusterRendererService(); 397 if (service != null) { 398 try { 399 service.onKeyEvent(event); 400 } catch (RemoteException e) { 401 Slogf.e(TAG, "onKeyEvent", e); 402 } 403 } 404 } 405 getInstrumentClusterRendererService()406 private IInstrumentCluster getInstrumentClusterRendererService() { 407 synchronized (mLock) { 408 return mRendererService; 409 } 410 } 411 412 /** 413 * TODO: (b/121277787) Remove on main 414 * 415 * @deprecated CarClusterManager is being deprecated. 416 */ 417 @Deprecated 418 private static class ClusterManagerService extends IInstrumentClusterManagerService.Stub { 419 @Override startClusterActivity(Intent intent)420 public void startClusterActivity(Intent intent) throws RemoteException { 421 // No op. 422 } 423 424 @Override registerCallback(IInstrumentClusterManagerCallback callback)425 public void registerCallback(IInstrumentClusterManagerCallback callback) 426 throws RemoteException { 427 // No op. 428 } 429 430 @Override unregisterCallback(IInstrumentClusterManagerCallback callback)431 public void unregisterCallback(IInstrumentClusterManagerCallback callback) 432 throws RemoteException { 433 // No op. 434 } 435 } 436 437 private static final class DeferredRebinder extends Handler { 438 private static final String TAG = DeferredRebinder.class.getSimpleName(); 439 440 private static final long NEXT_REBIND_ATTEMPT_DELAY_MS = 1000L; 441 private static final int NUMBER_OF_ATTEMPTS = 10; 442 443 private final WeakReference<InstrumentClusterService> mService; 444 DeferredRebinder(InstrumentClusterService service)445 private DeferredRebinder(InstrumentClusterService service) { 446 mService = new WeakReference<InstrumentClusterService>(service); 447 } 448 rebind()449 public void rebind() { 450 InstrumentClusterService service = mService.get(); 451 if (service == null) { 452 Slogf.i(TAG, "rebind null service"); 453 return; 454 } 455 456 boolean bound = service.bindInstrumentClusterRendererService(); 457 synchronized (service.mLock) { 458 service.mRendererBound = bound; 459 } 460 461 if (!bound) { 462 removeMessages(0); 463 sendMessageDelayed(obtainMessage(0, NUMBER_OF_ATTEMPTS, 0), 464 NEXT_REBIND_ATTEMPT_DELAY_MS); 465 } 466 } 467 468 @Override handleMessage(Message msg)469 public void handleMessage(Message msg) { 470 InstrumentClusterService service = mService.get(); 471 if (service == null) { 472 Slogf.i(TAG, "handleMessage null service"); 473 return; 474 } 475 476 boolean bound = service.bindInstrumentClusterRendererService(); 477 synchronized (service.mLock) { 478 service.mRendererBound = bound; 479 } 480 481 if (!bound) { 482 Slogf.w(TAG, "Failed to bound to render service, next attempt in " 483 + NEXT_REBIND_ATTEMPT_DELAY_MS + "ms."); 484 485 int attempts = msg.arg1; 486 if (--attempts >= 0) { 487 sendMessageDelayed(obtainMessage(0, attempts, 0), 488 NEXT_REBIND_ATTEMPT_DELAY_MS); 489 } else { 490 Slogf.wtf(TAG, "Failed to rebind with cluster rendering service"); 491 } 492 } 493 } 494 } 495 } 496