• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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