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