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