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 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.app.ActivityOptions; 23 import android.car.CarAppFocusManager; 24 import android.car.cluster.IInstrumentClusterManagerCallback; 25 import android.car.cluster.IInstrumentClusterManagerService; 26 import android.car.cluster.renderer.IInstrumentCluster; 27 import android.car.cluster.renderer.IInstrumentClusterHelper; 28 import android.car.cluster.renderer.IInstrumentClusterNavigation; 29 import android.content.ComponentName; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.ServiceConnection; 33 import android.os.Binder; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.IBinder; 37 import android.os.Message; 38 import android.os.RemoteException; 39 import android.os.UserHandle; 40 import android.text.TextUtils; 41 import android.util.Log; 42 import android.view.KeyEvent; 43 44 import com.android.car.AppFocusService; 45 import com.android.car.AppFocusService.FocusOwnershipCallback; 46 import com.android.car.CarInputService; 47 import com.android.car.CarInputService.KeyEventListener; 48 import com.android.car.CarLocalServices; 49 import com.android.car.CarLog; 50 import com.android.car.CarServiceBase; 51 import com.android.car.R; 52 import com.android.car.am.FixedActivityService; 53 import com.android.car.user.CarUserService; 54 import com.android.internal.annotations.GuardedBy; 55 56 import java.io.PrintWriter; 57 import java.util.Objects; 58 59 /** 60 * Service responsible for interaction with car's instrument cluster. 61 * 62 * @hide 63 */ 64 @SystemApi 65 public class InstrumentClusterService implements CarServiceBase, FocusOwnershipCallback, 66 KeyEventListener { 67 private static final String TAG = CarLog.TAG_CLUSTER; 68 private static final ContextOwner NO_OWNER = new ContextOwner(0, 0); 69 70 private final Context mContext; 71 private final AppFocusService mAppFocusService; 72 private final CarInputService mCarInputService; 73 /** 74 * TODO: (b/121277787) Remove this on master. 75 * @deprecated CarInstrumentClusterManager is being deprecated. 76 */ 77 @Deprecated 78 private final ClusterManagerService mClusterManagerService = new ClusterManagerService(); 79 private final Object mLock = new Object(); 80 @GuardedBy("mLock") 81 private ContextOwner mNavContextOwner = NO_OWNER; 82 @GuardedBy("mLock") 83 private IInstrumentCluster mRendererService; 84 // If renderer service crashed / stopped and this class fails to rebind with it immediately, 85 // we should wait some time before next attempt. This may happen during APK update for example. 86 @GuardedBy("mLock") 87 private DeferredRebinder mDeferredRebinder; 88 // Whether {@link android.car.cluster.renderer.InstrumentClusterRendererService} is bound 89 // (although not necessarily connected) 90 @GuardedBy("mLock") 91 private boolean mRendererBound = false; 92 93 /** 94 * Connection to {@link android.car.cluster.renderer.InstrumentClusterRendererService} 95 */ 96 private final ServiceConnection mRendererServiceConnection = new ServiceConnection() { 97 @Override 98 public void onServiceConnected(ComponentName name, IBinder binder) { 99 if (Log.isLoggable(TAG, Log.DEBUG)) { 100 Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder); 101 } 102 IInstrumentCluster service = IInstrumentCluster.Stub.asInterface(binder); 103 ContextOwner navContextOwner; 104 synchronized (mLock) { 105 mRendererService = service; 106 navContextOwner = mNavContextOwner; 107 } 108 if (navContextOwner != null && service != null) { 109 notifyNavContextOwnerChanged(service, navContextOwner); 110 } 111 } 112 113 @Override 114 public void onServiceDisconnected(ComponentName name) { 115 if (Log.isLoggable(TAG, Log.DEBUG)) { 116 Log.d(TAG, "onServiceDisconnected, name: " + name); 117 } 118 mContext.unbindService(this); 119 DeferredRebinder rebinder; 120 synchronized (mLock) { 121 mRendererBound = false; 122 mRendererService = null; 123 if (mDeferredRebinder == null) { 124 mDeferredRebinder = new DeferredRebinder(); 125 } 126 rebinder = mDeferredRebinder; 127 } 128 rebinder.rebind(); 129 } 130 }; 131 132 private final IInstrumentClusterHelper mInstrumentClusterHelper = 133 new IInstrumentClusterHelper.Stub() { 134 @Override 135 public boolean startFixedActivityModeForDisplayAndUser(Intent intent, 136 Bundle activityOptionsBundle, int userId) { 137 Binder.clearCallingIdentity(); 138 ActivityOptions options = new ActivityOptions(activityOptionsBundle); 139 FixedActivityService service = CarLocalServices.getService( 140 FixedActivityService.class); 141 return service.startFixedActivityModeForDisplayAndUser(intent, options, 142 options.getLaunchDisplayId(), userId); 143 } 144 145 @Override 146 public void stopFixedActivityMode(int displayId) { 147 Binder.clearCallingIdentity(); 148 FixedActivityService service = CarLocalServices.getService( 149 FixedActivityService.class); 150 service.stopFixedActivityMode(displayId); 151 } 152 }; 153 InstrumentClusterService(Context context, AppFocusService appFocusService, CarInputService carInputService)154 public InstrumentClusterService(Context context, AppFocusService appFocusService, 155 CarInputService carInputService) { 156 mContext = context; 157 mAppFocusService = appFocusService; 158 mCarInputService = carInputService; 159 } 160 161 @Override init()162 public void init() { 163 if (Log.isLoggable(TAG, Log.DEBUG)) { 164 Log.d(TAG, "init"); 165 } 166 167 mAppFocusService.registerContextOwnerChangedCallback(this /* FocusOwnershipCallback */); 168 mCarInputService.setInstrumentClusterKeyListener(this /* KeyEventListener */); 169 // TODO(b/124246323) Start earlier once data storage for cluster is clarified 170 // for early boot. 171 CarLocalServices.getService(CarUserService.class).runOnUser0Unlock(() -> { 172 mRendererBound = bindInstrumentClusterRendererService(); 173 }); 174 } 175 176 @Override release()177 public void release() { 178 if (Log.isLoggable(TAG, Log.DEBUG)) { 179 Log.d(TAG, "release"); 180 } 181 182 mAppFocusService.unregisterContextOwnerChangedCallback(this); 183 if (mRendererBound) { 184 mContext.unbindService(mRendererServiceConnection); 185 mRendererBound = false; 186 } 187 } 188 189 @Override dump(PrintWriter writer)190 public void dump(PrintWriter writer) { 191 writer.println("**" + getClass().getSimpleName() + "**"); 192 writer.println("bound with renderer: " + mRendererBound); 193 writer.println("renderer service: " + mRendererService); 194 writer.println("context owner: " + mNavContextOwner); 195 } 196 197 @Override onFocusAcquired(int appType, int uid, int pid)198 public void onFocusAcquired(int appType, int uid, int pid) { 199 changeNavContextOwner(appType, uid, pid, true); 200 } 201 202 @Override onFocusAbandoned(int appType, int uid, int pid)203 public void onFocusAbandoned(int appType, int uid, int pid) { 204 changeNavContextOwner(appType, uid, pid, false); 205 } 206 changeNavContextOwner(int appType, int uid, int pid, boolean acquire)207 private void changeNavContextOwner(int appType, int uid, int pid, boolean acquire) { 208 if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) { 209 return; 210 } 211 212 IInstrumentCluster service; 213 ContextOwner requester = new ContextOwner(uid, pid); 214 ContextOwner newOwner = acquire ? requester : NO_OWNER; 215 synchronized (mLock) { 216 if ((acquire && Objects.equals(mNavContextOwner, requester)) 217 || (!acquire && !Objects.equals(mNavContextOwner, requester))) { 218 // Nothing to do here. Either the same owner is acquiring twice, or someone is 219 // abandoning a focus they didn't have. 220 Log.w(TAG, "Invalid nav context owner change (acquiring: " + acquire 221 + "), current owner: [" + mNavContextOwner 222 + "], requester: [" + requester + "]"); 223 return; 224 } 225 226 mNavContextOwner = newOwner; 227 service = mRendererService; 228 } 229 230 if (service != null) { 231 notifyNavContextOwnerChanged(service, newOwner); 232 } 233 } 234 notifyNavContextOwnerChanged(IInstrumentCluster service, ContextOwner owner)235 private static void notifyNavContextOwnerChanged(IInstrumentCluster service, 236 ContextOwner owner) { 237 try { 238 service.setNavigationContextOwner(owner.uid, owner.pid); 239 } catch (RemoteException e) { 240 Log.e(TAG, "Failed to call setNavigationContextOwner", e); 241 } 242 } 243 bindInstrumentClusterRendererService()244 private boolean bindInstrumentClusterRendererService() { 245 String rendererService = mContext.getString(R.string.instrumentClusterRendererService); 246 if (TextUtils.isEmpty(rendererService)) { 247 Log.i(TAG, "Instrument cluster renderer was not configured"); 248 return false; 249 } 250 251 Log.d(TAG, "bindInstrumentClusterRendererService, component: " + rendererService); 252 253 Intent intent = new Intent(); 254 intent.setComponent(ComponentName.unflattenFromString(rendererService)); 255 // Litle bit inefficiency here as Intent.getIBinderExtra() is a hidden API. 256 Bundle bundle = new Bundle(); 257 bundle.putBinder(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER, 258 mInstrumentClusterHelper.asBinder()); 259 intent.putExtra(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER, bundle); 260 return mContext.bindServiceAsUser(intent, mRendererServiceConnection, 261 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM); 262 } 263 264 @Nullable getNavigationService()265 public IInstrumentClusterNavigation getNavigationService() { 266 try { 267 IInstrumentCluster service = getInstrumentClusterRendererService(); 268 return service == null ? null : service.getNavigationService(); 269 } catch (RemoteException e) { 270 Log.e(TAG, "getNavigationServiceBinder" , e); 271 return null; 272 } 273 } 274 275 /** 276 * @deprecated {@link android.car.cluster.CarInstrumentClusterManager} is now deprecated. 277 */ 278 @Deprecated getManagerService()279 public IInstrumentClusterManagerService.Stub getManagerService() { 280 return mClusterManagerService; 281 } 282 283 @Override onKeyEvent(KeyEvent event)284 public void onKeyEvent(KeyEvent event) { 285 if (Log.isLoggable(TAG, Log.DEBUG)) { 286 Log.d(TAG, "InstrumentClusterService#onKeyEvent: " + event); 287 } 288 289 IInstrumentCluster service = getInstrumentClusterRendererService(); 290 if (service != null) { 291 try { 292 service.onKeyEvent(event); 293 } catch (RemoteException e) { 294 Log.e(TAG, "onKeyEvent", e); 295 } 296 } 297 } 298 getInstrumentClusterRendererService()299 private IInstrumentCluster getInstrumentClusterRendererService() { 300 IInstrumentCluster service; 301 synchronized (mLock) { 302 service = mRendererService; 303 } 304 return service; 305 } 306 307 private static class ContextOwner { 308 final int uid; 309 final int pid; 310 ContextOwner(int uid, int pid)311 ContextOwner(int uid, int pid) { 312 this.uid = uid; 313 this.pid = pid; 314 } 315 316 @Override toString()317 public String toString() { 318 return "uid: " + uid + ", pid: " + pid; 319 } 320 321 @Override equals(Object o)322 public boolean equals(Object o) { 323 if (this == o) return true; 324 if (o == null || getClass() != o.getClass()) return false; 325 ContextOwner that = (ContextOwner) o; 326 return uid == that.uid && pid == that.pid; 327 } 328 329 @Override hashCode()330 public int hashCode() { 331 return Objects.hash(uid, pid); 332 } 333 } 334 335 /** 336 * TODO: (b/121277787) Remove on master 337 * @deprecated CarClusterManager is being deprecated. 338 */ 339 @Deprecated 340 private class ClusterManagerService extends IInstrumentClusterManagerService.Stub { 341 @Override startClusterActivity(Intent intent)342 public void startClusterActivity(Intent intent) throws RemoteException { 343 // No op. 344 } 345 346 @Override registerCallback(IInstrumentClusterManagerCallback callback)347 public void registerCallback(IInstrumentClusterManagerCallback callback) 348 throws RemoteException { 349 // No op. 350 } 351 352 @Override unregisterCallback(IInstrumentClusterManagerCallback callback)353 public void unregisterCallback(IInstrumentClusterManagerCallback callback) 354 throws RemoteException { 355 // No op. 356 } 357 } 358 359 private class DeferredRebinder extends Handler { 360 private static final long NEXT_REBIND_ATTEMPT_DELAY_MS = 1000L; 361 private static final int NUMBER_OF_ATTEMPTS = 10; 362 rebind()363 public void rebind() { 364 mRendererBound = bindInstrumentClusterRendererService(); 365 366 if (!mRendererBound) { 367 removeMessages(0); 368 sendMessageDelayed(obtainMessage(0, NUMBER_OF_ATTEMPTS, 0), 369 NEXT_REBIND_ATTEMPT_DELAY_MS); 370 } 371 } 372 373 @Override handleMessage(Message msg)374 public void handleMessage(Message msg) { 375 mRendererBound = bindInstrumentClusterRendererService(); 376 377 if (mRendererBound) { 378 Log.w(TAG, "Failed to bound to render service, next attempt in " 379 + NEXT_REBIND_ATTEMPT_DELAY_MS + "ms."); 380 381 int attempts = msg.arg1; 382 if (--attempts >= 0) { 383 sendMessageDelayed(obtainMessage(0, attempts, 0), NEXT_REBIND_ATTEMPT_DELAY_MS); 384 } else { 385 Log.wtf(TAG, "Failed to rebind with cluster rendering service"); 386 } 387 } 388 } 389 } 390 } 391