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