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