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