• 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.content.pm.PackageManager.PERMISSION_GRANTED;
19 
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.car.Car;
23 import android.car.CarAppFocusManager;
24 import android.car.cluster.CarInstrumentClusterManager;
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.IInstrumentClusterCallback;
29 import android.car.cluster.renderer.IInstrumentClusterNavigation;
30 import android.car.cluster.renderer.InstrumentClusterRenderingService;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.ServiceConnection;
35 import android.content.pm.PackageManager;
36 import android.content.pm.ResolveInfo;
37 import android.os.Binder;
38 import android.os.Bundle;
39 import android.os.Handler;
40 import android.os.IBinder;
41 import android.os.IBinder.DeathRecipient;
42 import android.os.Message;
43 import android.os.Process;
44 import android.os.RemoteException;
45 import android.text.TextUtils;
46 import android.util.Log;
47 import android.util.Pair;
48 import android.view.KeyEvent;
49 
50 import com.android.car.AppFocusService;
51 import com.android.car.AppFocusService.FocusOwnershipCallback;
52 import com.android.car.CarInputService;
53 import com.android.car.CarInputService.KeyEventListener;
54 import com.android.car.CarLog;
55 import com.android.car.CarServiceBase;
56 import com.android.car.R;
57 import com.android.internal.annotations.GuardedBy;
58 
59 import java.io.PrintWriter;
60 import java.util.ArrayList;
61 import java.util.HashMap;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Set;
65 
66 /**
67  * Service responsible for interaction with car's instrument cluster.
68  *
69  * @hide
70  */
71 @SystemApi
72 public class InstrumentClusterService implements CarServiceBase,
73         FocusOwnershipCallback, KeyEventListener {
74 
75     private static final String TAG = CarLog.TAG_CLUSTER;
76     private static final Boolean DBG = false;
77 
78     private final Context mContext;
79 
80     private final AppFocusService mAppFocusService;
81     private final CarInputService mCarInputService;
82     private final PackageManager mPackageManager;
83     private final Object mSync = new Object();
84 
85     private final ClusterServiceCallback mClusterCallback = new ClusterServiceCallback();
86     private final ClusterManagerService mClusterManagerService = new ClusterManagerService();
87 
88     @GuardedBy("mSync")
89     private ContextOwner mNavContextOwner;
90     @GuardedBy("mSync")
91     private IInstrumentCluster mRendererService;
92     @GuardedBy("mSync")
93     private final HashMap<String, ClusterActivityInfo> mActivityInfoByCategory = new HashMap<>();
94     @GuardedBy("mSync")
95     private final HashMap<IBinder, ManagerCallbackInfo> mManagerCallbacks = new HashMap<>();
96 
97     // If renderer service crashed / stopped and this class fails to rebind with it immediately,
98     // we should wait some time before next attempt. This may happen during APK update for example.
99     private DeferredRebinder mDeferredRebinder;
100 
101     private boolean mRendererBound = false;
102 
103     private final ServiceConnection mRendererServiceConnection = new ServiceConnection() {
104         @Override
105         public void onServiceConnected(ComponentName name, IBinder binder) {
106             if (DBG) {
107                 Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder);
108             }
109             IInstrumentCluster service = IInstrumentCluster.Stub.asInterface(binder);
110             ContextOwner navContextOwner;
111             synchronized (mSync) {
112                 mRendererService = service;
113                 navContextOwner = mNavContextOwner;
114             }
115             if (navContextOwner !=  null && service != null) {
116                 notifyNavContextOwnerChanged(service, navContextOwner.uid, navContextOwner.pid);
117             }
118         }
119 
120         @Override
121         public void onServiceDisconnected(ComponentName name) {
122             Log.d(TAG, "onServiceDisconnected, name: " + name);
123             mRendererBound = false;
124 
125             synchronized (mSync) {
126                 mRendererService = null;
127             }
128 
129             if (mDeferredRebinder == null) {
130                 mDeferredRebinder = new DeferredRebinder();
131             }
132             mDeferredRebinder.rebind();
133         }
134     };
135 
InstrumentClusterService(Context context, AppFocusService appFocusService, CarInputService carInputService)136     public InstrumentClusterService(Context context, AppFocusService appFocusService,
137             CarInputService carInputService) {
138         mContext = context;
139         mAppFocusService = appFocusService;
140         mCarInputService = carInputService;
141         mPackageManager = mContext.getPackageManager();
142     }
143 
144     @Override
init()145     public void init() {
146         if (DBG) {
147             Log.d(TAG, "init");
148         }
149 
150         mAppFocusService.registerContextOwnerChangedCallback(this /* FocusOwnershipCallback */);
151         mCarInputService.setInstrumentClusterKeyListener(this /* KeyEventListener */);
152         mRendererBound = bindInstrumentClusterRendererService();
153     }
154 
155     @Override
release()156     public void release() {
157         if (DBG) {
158             Log.d(TAG, "release");
159         }
160 
161         mAppFocusService.unregisterContextOwnerChangedCallback(this);
162         if (mRendererBound) {
163             mContext.unbindService(mRendererServiceConnection);
164             mRendererBound = false;
165         }
166     }
167 
168     @Override
dump(PrintWriter writer)169     public void dump(PrintWriter writer) {
170         writer.println("**" + getClass().getSimpleName() + "**");
171         writer.println("bound with renderer: " + mRendererBound);
172         writer.println("renderer service: " + mRendererService);
173     }
174 
175     @Override
onFocusAcquired(int appType, int uid, int pid)176     public void onFocusAcquired(int appType, int uid, int pid) {
177         if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
178             return;
179         }
180 
181         IInstrumentCluster service;
182         synchronized (mSync) {
183             mNavContextOwner = new ContextOwner(uid, pid);
184             service = mRendererService;
185         }
186 
187         if (service != null) {
188             notifyNavContextOwnerChanged(service, uid, pid);
189         }
190     }
191 
192     @Override
onFocusAbandoned(int appType, int uid, int pid)193     public void onFocusAbandoned(int appType, int uid, int pid) {
194         if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
195             return;
196         }
197 
198         IInstrumentCluster service;
199         synchronized (mSync) {
200             if (mNavContextOwner == null
201                     || mNavContextOwner.uid != uid
202                     || mNavContextOwner.pid != pid) {
203                 return;  // Nothing to do here, no active focus or not owned by this client.
204             }
205 
206             mNavContextOwner = null;
207             service = mRendererService;
208         }
209 
210         if (service != null) {
211             notifyNavContextOwnerChanged(service, 0, 0);
212         }
213     }
214 
notifyNavContextOwnerChanged(IInstrumentCluster service, int uid, int pid)215     private static void notifyNavContextOwnerChanged(IInstrumentCluster service, int uid, int pid) {
216         try {
217             service.setNavigationContextOwner(uid, pid);
218         } catch (RemoteException e) {
219             Log.e(TAG, "Failed to call setNavigationContextOwner", e);
220         }
221     }
222 
bindInstrumentClusterRendererService()223     private boolean bindInstrumentClusterRendererService() {
224         String rendererService = mContext.getString(R.string.instrumentClusterRendererService);
225         if (TextUtils.isEmpty(rendererService)) {
226             Log.i(TAG, "Instrument cluster renderer was not configured");
227             return false;
228         }
229 
230         Log.d(TAG, "bindInstrumentClusterRendererService, component: " + rendererService);
231 
232         Intent intent = new Intent();
233         intent.setComponent(ComponentName.unflattenFromString(rendererService));
234         Bundle extras = new Bundle();
235         extras.putBinder(
236                 InstrumentClusterRenderingService.EXTRA_KEY_CALLBACK_SERVICE,
237                 mClusterCallback);
238         intent.putExtras(extras);
239         return mContext.bindService(intent, mRendererServiceConnection,
240                 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
241     }
242 
243     @Nullable
getNavigationService()244     public IInstrumentClusterNavigation getNavigationService() {
245         IInstrumentCluster service;
246         synchronized (mSync) {
247             service = mRendererService;
248         }
249 
250         try {
251             return service == null ? null : service.getNavigationService();
252         } catch (RemoteException e) {
253             Log.e(TAG, "getNavigationServiceBinder" , e);
254             return null;
255         }
256     }
257 
getManagerService()258     public IInstrumentClusterManagerService.Stub getManagerService() {
259         return mClusterManagerService;
260     }
261 
262     @Override
onKeyEvent(KeyEvent event)263     public boolean onKeyEvent(KeyEvent event) {
264         if (DBG) {
265             Log.d(TAG, "InstrumentClusterService#onKeyEvent: " + event);
266         }
267 
268         IInstrumentCluster service;
269         synchronized (mSync) {
270             service = mRendererService;
271         }
272 
273         if (service != null) {
274             try {
275                 service.onKeyEvent(event);
276             } catch (RemoteException e) {
277                 Log.e(TAG, "onKeyEvent", e);
278             }
279         }
280         return true;
281     }
282 
283     private static class ContextOwner {
284         final int uid;
285         final int pid;
286 
ContextOwner(int uid, int pid)287         ContextOwner(int uid, int pid) {
288             this.uid = uid;
289             this.pid = pid;
290         }
291     }
292 
293     private static class ClusterActivityInfo {
294         Bundle launchOptions;  // ActivityOptions
295         Bundle state;          // ClusterActivityState
296     }
297 
enforcePermission(String permission)298     private void enforcePermission(String permission) {
299         int callingUid = Binder.getCallingUid();
300         int callingPid = Binder.getCallingPid();
301         if (Binder.getCallingUid() == Process.myUid()) {
302             if (mContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
303                 throw new SecurityException("Permission " + permission + " is not granted to "
304                         + "client {uid: " + callingUid + ", pid: " + callingPid + "}");
305             }
306         }
307     }
308 
enforceClusterControlPermission()309     private void enforceClusterControlPermission() {
310         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
311     }
312 
doStartClusterActivity(Intent intent)313     private void doStartClusterActivity(Intent intent) {
314         enforceClusterControlPermission();
315 
316         // Category from given intent should match category from cluster vendor implementation.
317         List<ResolveInfo> resolveList = mPackageManager.queryIntentActivities(intent,
318                 PackageManager.GET_RESOLVED_FILTER);
319         if (resolveList == null || resolveList.isEmpty()) {
320             Log.w(TAG, "Failed to resolve an intent: " + intent);
321             return;
322         }
323 
324         resolveList = checkPermission(resolveList, Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER);
325         if (resolveList.isEmpty()) {
326             return;
327         }
328 
329         // TODO(b/63861009): we may have multiple navigation apps that eligible to be launched in
330         // the cluster. We need to resolve intent that may have multiple activity candidates, right
331         // now we pickup the first one that matches registered category (resolveList is sorted
332         // priority).
333         Pair<ResolveInfo, ClusterActivityInfo> attributedResolveInfo =
334                 findClusterActivityOptions(resolveList);
335         if (attributedResolveInfo == null) {
336             Log.w(TAG, "Unable to start an activity with intent: " + intent + " in the cluster: "
337                     + "category intent didn't match with any categories from vendor "
338                     + "implementation");
339             return;
340         }
341         ClusterActivityInfo opts = attributedResolveInfo.second;
342 
343         // Intent was already checked for permission and resolved, make it explicit.
344         intent.setComponent(attributedResolveInfo.first.getComponentInfo().getComponentName());
345 
346         intent.putExtra(CarInstrumentClusterManager.KEY_EXTRA_ACTIVITY_STATE, opts.state);
347         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
348         // Virtual display could be private and not available to calling process.
349         final long token = Binder.clearCallingIdentity();
350         try {
351             mContext.startActivity(intent, opts.launchOptions);
352         } finally {
353             Binder.restoreCallingIdentity(token);
354         }
355     }
356 
checkPermission(List<ResolveInfo> resolveList, String permission)357     private List<ResolveInfo> checkPermission(List<ResolveInfo> resolveList,
358             String permission) {
359         List<ResolveInfo> permittedResolveList = new ArrayList<>(resolveList.size());
360         for (ResolveInfo info : resolveList) {
361             String pkgName = info.getComponentInfo().packageName;
362             if (mPackageManager.checkPermission(permission, pkgName) == PERMISSION_GRANTED) {
363                 permittedResolveList.add(info);
364             } else {
365                 Log.w(TAG, "Permission " + permission + " not granted for "
366                         + info.getComponentInfo());
367             }
368 
369         }
370         return permittedResolveList;
371     }
372 
doRegisterManagerCallback(IInstrumentClusterManagerCallback callback)373     private void doRegisterManagerCallback(IInstrumentClusterManagerCallback callback)
374             throws RemoteException {
375         enforceClusterControlPermission();
376         IBinder binder = callback.asBinder();
377 
378         List<Pair<String, Bundle>> knownActivityStates = null;
379         ManagerCallbackDeathRecipient deathRecipient = new ManagerCallbackDeathRecipient(binder);
380         synchronized (mSync) {
381             if (mManagerCallbacks.containsKey(binder)) {
382                 Log.w(TAG, "Manager callback already registered for binder: " + binder);
383                 return;
384             }
385             mManagerCallbacks.put(binder, new ManagerCallbackInfo(callback, deathRecipient));
386             if (!mActivityInfoByCategory.isEmpty()) {
387                 knownActivityStates = new ArrayList<>(mActivityInfoByCategory.size());
388                 for (Map.Entry<String, ClusterActivityInfo> it : mActivityInfoByCategory.entrySet()) {
389                     knownActivityStates.add(new Pair<>(it.getKey(), it.getValue().state));
390                 }
391             }
392         }
393         binder.linkToDeath(deathRecipient, 0);
394 
395         // Notify manager immediately with known states.
396         if (knownActivityStates != null) {
397             for (Pair<String, Bundle> it : knownActivityStates) {
398                 callback.setClusterActivityState(it.first, it.second);
399             }
400         }
401     }
402 
doUnregisterManagerCallback(IBinder binder)403     private void doUnregisterManagerCallback(IBinder binder) throws RemoteException {
404         enforceClusterControlPermission();
405         ManagerCallbackInfo info;
406         synchronized (mSync) {
407             info = mManagerCallbacks.get(binder);
408             if (info == null) {
409                 Log.w(TAG, "Unable to unregister manager callback binder: " + binder + " because "
410                         + "it wasn't previously registered.");
411                 return;
412             }
413             mManagerCallbacks.remove(binder);
414         }
415         binder.unlinkToDeath(info.deathRecipient, 0);
416     }
417 
418     @Nullable
findClusterActivityOptions( List<ResolveInfo> resolveList)419     private Pair<ResolveInfo, ClusterActivityInfo> findClusterActivityOptions(
420             List<ResolveInfo> resolveList) {
421         synchronized (mSync) {
422             Set<String> registeredCategories = mActivityInfoByCategory.keySet();
423 
424             for (ResolveInfo resolveInfo : resolveList) {
425                 for (String category : registeredCategories) {
426                     if (resolveInfo.filter != null && resolveInfo.filter.hasCategory(category)) {
427                         ClusterActivityInfo categoryInfo = mActivityInfoByCategory.get(category);
428                         return new Pair<>(resolveInfo, categoryInfo);
429                     }
430                 }
431             }
432         }
433         return null;
434     }
435 
436     private class ManagerCallbackDeathRecipient implements DeathRecipient {
437         private final IBinder mBinder;
438 
ManagerCallbackDeathRecipient(IBinder binder)439         ManagerCallbackDeathRecipient(IBinder binder) {
440             mBinder = binder;
441         }
442 
443         @Override
binderDied()444         public void binderDied() {
445             try {
446                 doUnregisterManagerCallback(mBinder);
447             } catch (RemoteException e) {
448                 // Ignore, shutdown route.
449             }
450         }
451     }
452 
453     private class ClusterManagerService extends IInstrumentClusterManagerService.Stub {
454 
455         @Override
startClusterActivity(Intent intent)456         public void startClusterActivity(Intent intent) throws RemoteException {
457             doStartClusterActivity(intent);
458         }
459 
460         @Override
registerCallback(IInstrumentClusterManagerCallback callback)461         public void registerCallback(IInstrumentClusterManagerCallback callback)
462                 throws RemoteException {
463             doRegisterManagerCallback(callback);
464         }
465 
466         @Override
unregisterCallback(IInstrumentClusterManagerCallback callback)467         public void unregisterCallback(IInstrumentClusterManagerCallback callback)
468                 throws RemoteException {
469             doUnregisterManagerCallback(callback.asBinder());
470         }
471     }
472 
473     @GuardedBy("mSync")
getOrCreateActivityInfoLocked(String category)474     private ClusterActivityInfo getOrCreateActivityInfoLocked(String category) {
475         return mActivityInfoByCategory.computeIfAbsent(category, k -> new ClusterActivityInfo());
476     }
477 
478     /** This is communication channel from vendor cluster implementation to Car Service. */
479     private class ClusterServiceCallback extends IInstrumentClusterCallback.Stub {
480 
481         @Override
setClusterActivityLaunchOptions(String category, Bundle activityOptions)482         public void setClusterActivityLaunchOptions(String category, Bundle activityOptions)
483                 throws RemoteException {
484             doSetActivityLaunchOptions(category, activityOptions);
485         }
486 
487         @Override
setClusterActivityState(String category, Bundle clusterActivityState)488         public void setClusterActivityState(String category, Bundle clusterActivityState)
489                 throws RemoteException {
490             doSetClusterActivityState(category, clusterActivityState);
491         }
492     }
493 
494     /** Called from cluster vendor implementation */
doSetActivityLaunchOptions(String category, Bundle activityOptions)495     private void doSetActivityLaunchOptions(String category, Bundle activityOptions) {
496         if (DBG) {
497             Log.d(TAG, "doSetActivityLaunchOptions, category: " + category
498                     + ", options: " + activityOptions);
499         }
500         synchronized (mSync) {
501             ClusterActivityInfo info = getOrCreateActivityInfoLocked(category);
502             info.launchOptions = activityOptions;
503         }
504     }
505 
506     /** Called from cluster vendor implementation */
doSetClusterActivityState(String category, Bundle clusterActivityState)507     private void doSetClusterActivityState(String category, Bundle clusterActivityState)
508             throws RemoteException {
509         if (DBG) {
510             Log.d(TAG, "doSetClusterActivityState, category: " + category
511                     + ", state: " + clusterActivityState);
512         }
513 
514         List<ManagerCallbackInfo> managerCallbacks;
515         synchronized (mSync) {
516             ClusterActivityInfo info = getOrCreateActivityInfoLocked(category);
517             info.state = clusterActivityState;
518             managerCallbacks = new ArrayList<>(mManagerCallbacks.values());
519         }
520 
521         for (ManagerCallbackInfo cbInfo : managerCallbacks) {
522             cbInfo.callback.setClusterActivityState(category, clusterActivityState);
523         }
524     }
525 
526     private static class ManagerCallbackInfo {
527         final IInstrumentClusterManagerCallback callback;
528         final ManagerCallbackDeathRecipient deathRecipient;
529 
ManagerCallbackInfo(IInstrumentClusterManagerCallback callback, ManagerCallbackDeathRecipient deathRecipient)530         ManagerCallbackInfo(IInstrumentClusterManagerCallback callback,
531                 ManagerCallbackDeathRecipient deathRecipient) {
532             this.callback = callback;
533             this.deathRecipient = deathRecipient;
534         }
535     }
536 
537     private class DeferredRebinder extends Handler {
538         private static final long NEXT_REBIND_ATTEMPT_DELAY_MS = 1000L;
539         private static final int NUMBER_OF_ATTEMPTS = 10;
540 
rebind()541         public void rebind() {
542             mRendererBound = bindInstrumentClusterRendererService();
543 
544             if (!mRendererBound) {
545                 removeMessages(0);
546                 sendMessageDelayed(obtainMessage(0, NUMBER_OF_ATTEMPTS, 0),
547                         NEXT_REBIND_ATTEMPT_DELAY_MS);
548             }
549         }
550 
551         @Override
handleMessage(Message msg)552         public void handleMessage(Message msg) {
553             mRendererBound = bindInstrumentClusterRendererService();
554 
555             if (mRendererBound) {
556                 Log.w(TAG, "Failed to bound to render service, next attempt in "
557                         + NEXT_REBIND_ATTEMPT_DELAY_MS + "ms.");
558 
559                 int attempts = msg.arg1;
560                 if (--attempts >= 0) {
561                     sendMessageDelayed(obtainMessage(0, attempts, 0), NEXT_REBIND_ATTEMPT_DELAY_MS);
562                 } else {
563                     Log.wtf(TAG, "Failed to rebind with cluster rendering service");
564                 }
565             }
566         }
567     }
568 }
569