1 /*
2  * Copyright (C) 2015 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 
17 package android.car;
18 
19 import static android.car.feature.Flags.FLAG_CLUSTER_HEALTH_MONITORING;
20 
21 import static com.android.car.internal.common.CommonConstants.EMPTY_INT_ARRAY;
22 
23 import android.annotation.FlaggedApi;
24 import android.annotation.IntDef;
25 import android.annotation.NonNull;
26 import android.annotation.RequiresPermission;
27 import android.annotation.SystemApi;
28 import android.annotation.TestApi;
29 import android.os.IBinder;
30 import android.os.RemoteException;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 
34 import java.lang.annotation.Retention;
35 import java.lang.annotation.RetentionPolicy;
36 import java.lang.ref.WeakReference;
37 import java.util.Collections;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 
44 /**
45  * CarAppFocusManager allows applications to set and listen for the current application focus
46  * like active navigation or active voice command. Usually only one instance of such application
47  * should run in the system, and other app setting the flag for the matching app should
48  * lead into other app to stop.
49  */
50 public final class CarAppFocusManager extends CarManagerBase {
51     /**
52      * Listener to get notification for app getting information on application type status changes.
53      */
54     public interface OnAppFocusChangedListener {
55         /**
56          * Application focus has changed. Note that {@link CarAppFocusManager} instance
57          * causing the change will not get this notification.
58          *
59          * <p>Note that this call can happen for app focus grant, release, and ownership change.
60          *
61          * @param appType appType where the focus change has happened.
62          * @param active {@code true} if there is an active owner for the focus.
63          */
onAppFocusChanged(@ppFocusType int appType, boolean active)64         void onAppFocusChanged(@AppFocusType int appType, boolean active);
65     }
66 
67     /**
68      * Listener to get notification for app getting information on app type ownership loss.
69      */
70     public interface OnAppFocusOwnershipCallback {
71         /**
72          * Lost ownership for the focus, which happens when other app has set the focus.
73          * The app losing focus should stop the action associated with the focus.
74          * For example, navigation app currently running active navigation should stop navigation
75          * upon getting this for {@link CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION}.
76          * @param appType
77          */
onAppFocusOwnershipLost(@ppFocusType int appType)78         void onAppFocusOwnershipLost(@AppFocusType int appType);
79 
80         /**
81          * Granted ownership for the focus, which happens when app has requested the focus.
82          * The app getting focus can start the action associated with the focus.
83          * For example, navigation app can start navigation
84          * upon getting this for {@link CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION}.
85          * @param appType
86          */
onAppFocusOwnershipGranted(@ppFocusType int appType)87         void onAppFocusOwnershipGranted(@AppFocusType int appType);
88     }
89 
90     /**
91      * Represents navigation focus.
92      */
93     public static final int APP_FOCUS_TYPE_NAVIGATION = 1;
94     /**
95      * Represents voice command focus.
96      *
97      * @deprecated use {@link android.service.voice.VoiceInteractionService} instead.
98      */
99     @Deprecated
100     public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2;
101     /**
102      * Update this after adding a new app type.
103      * @hide
104      */
105     public static final int APP_FOCUS_MAX = 2;
106 
107     /** @hide */
108     @IntDef({
109         APP_FOCUS_TYPE_NAVIGATION,
110     })
111     @Retention(RetentionPolicy.SOURCE)
112     public @interface AppFocusType {}
113 
114     /**
115      * A failed focus change request.
116      */
117     public static final int APP_FOCUS_REQUEST_FAILED = 0;
118     /**
119      * A successful focus change request.
120      */
121     public static final int APP_FOCUS_REQUEST_SUCCEEDED = 1;
122 
123     /** @hide */
124     @IntDef({
125         APP_FOCUS_REQUEST_FAILED,
126         APP_FOCUS_REQUEST_SUCCEEDED
127     })
128     @Retention(RetentionPolicy.SOURCE)
129     public @interface AppFocusRequestResult {}
130 
131     private final IAppFocus mService;
132     private final Map<OnAppFocusChangedListener, IAppFocusListenerImpl> mChangeBinders =
133             new HashMap<>();
134     private final Map<OnAppFocusOwnershipCallback, IAppFocusOwnershipCallbackImpl>
135             mOwnershipBinders = new HashMap<>();
136 
137     /**
138      * @hide
139      */
140     @VisibleForTesting
CarAppFocusManager(Car car, IBinder service)141     public CarAppFocusManager(Car car, IBinder service) {
142         super(car);
143         mService = IAppFocus.Stub.asInterface(service);
144     }
145 
146     /**
147      * Register listener to monitor app focus change.
148      * @param listener
149      * @param appType Application type to get notification for.
150      */
addFocusListener(OnAppFocusChangedListener listener, @AppFocusType int appType)151     public void addFocusListener(OnAppFocusChangedListener listener, @AppFocusType int appType) {
152         if (listener == null) {
153             throw new IllegalArgumentException("null listener");
154         }
155         IAppFocusListenerImpl binder;
156         synchronized (this) {
157             binder = mChangeBinders.get(listener);
158             if (binder == null) {
159                 binder = new IAppFocusListenerImpl(this, listener);
160                 mChangeBinders.put(listener, binder);
161             }
162             binder.addAppType(appType);
163         }
164         try {
165             mService.registerFocusListener(binder, appType);
166         } catch (RemoteException e) {
167             handleRemoteExceptionFromCarService(e);
168         }
169     }
170 
171     /**
172      * Unregister listener for application type and stop listening focus change events.
173      * @param listener
174      * @param appType
175      */
removeFocusListener(OnAppFocusChangedListener listener, @AppFocusType int appType)176     public void removeFocusListener(OnAppFocusChangedListener listener, @AppFocusType int appType) {
177         IAppFocusListenerImpl binder;
178         synchronized (this) {
179             binder = mChangeBinders.get(listener);
180             if (binder == null) {
181                 return;
182             }
183         }
184         try {
185             mService.unregisterFocusListener(binder, appType);
186         } catch (RemoteException e) {
187             handleRemoteExceptionFromCarService(e);
188             // continue for local clean-up
189         }
190         synchronized (this) {
191             binder.removeAppType(appType);
192             if (!binder.hasAppTypes()) {
193                 mChangeBinders.remove(listener);
194             }
195 
196         }
197     }
198 
199     /**
200      * Unregister listener and stop listening focus change events.
201      * @param listener
202      */
removeFocusListener(OnAppFocusChangedListener listener)203     public void removeFocusListener(OnAppFocusChangedListener listener) {
204         IAppFocusListenerImpl binder;
205         synchronized (this) {
206             binder = mChangeBinders.remove(listener);
207             if (binder == null) {
208                 return;
209             }
210         }
211         try {
212             for (Integer appType : binder.getAppTypes()) {
213                 mService.unregisterFocusListener(binder, appType);
214             }
215         } catch (RemoteException e) {
216             handleRemoteExceptionFromCarService(e);
217         }
218     }
219 
220     /**
221      * Returns application types currently active in the system.
222      * @hide
223      */
224     @TestApi
getActiveAppTypes()225     public int[] getActiveAppTypes() {
226         try {
227             return mService.getActiveAppTypes();
228         } catch (RemoteException e) {
229             return handleRemoteExceptionFromCarService(e, EMPTY_INT_ARRAY);
230         }
231     }
232 
233     /**
234      * Returns the package names of the current owner of a given application type, or an empty list
235      * if there is no owner. This method might return more than one package name if the current
236      * owner uses the "android:sharedUserId" feature.
237      * @param appType the app type. For example, {@link #APP_FOCUS_TYPE_NAVIGATION}.
238      * @return the package names of the current focus owner of the given {@code appType}.
239      *
240      * @hide
241      */
242     @FlaggedApi(FLAG_CLUSTER_HEALTH_MONITORING)
243     @SystemApi
244     @RequiresPermission(allOf = {android.Manifest.permission.QUERY_ALL_PACKAGES,
245             android.Manifest.permission.INTERACT_ACROSS_USERS},
246             conditional = true)
247     @NonNull
getAppTypeOwner(@ppFocusType int appType)248     public List<String> getAppTypeOwner(@AppFocusType int appType) {
249         try {
250             return mService.getAppTypeOwner(appType);
251         } catch (RemoteException e) {
252             return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST);
253         }
254     }
255 
256     /**
257      * Checks if listener is associated with active a focus
258      * @param callback
259      * @param appType
260      */
isOwningFocus(OnAppFocusOwnershipCallback callback, @AppFocusType int appType)261     public boolean isOwningFocus(OnAppFocusOwnershipCallback callback, @AppFocusType int appType) {
262         IAppFocusOwnershipCallbackImpl binder;
263         synchronized (this) {
264             binder = mOwnershipBinders.get(callback);
265             if (binder == null) {
266                 return false;
267             }
268         }
269         try {
270             return mService.isOwningFocus(binder, appType);
271         } catch (RemoteException e) {
272             return handleRemoteExceptionFromCarService(e, false);
273         }
274     }
275 
276     /**
277      * Requests application focus.
278      * By requesting this, the application is becoming owner of the focus, and will get
279      * {@link OnAppFocusOwnershipCallback#onAppFocusOwnershipLost(int)}
280      * if ownership is given to other app by calling this. Fore-ground app will have higher priority
281      * and other app cannot set the same focus while owner is in fore-ground.
282      * @param appType
283      * @param ownershipCallback
284      * @return {@link #APP_FOCUS_REQUEST_FAILED} or {@link #APP_FOCUS_REQUEST_SUCCEEDED}
285      * @throws SecurityException If owner cannot be changed.
286      */
requestAppFocus( int appType, OnAppFocusOwnershipCallback ownershipCallback)287     public @AppFocusRequestResult int requestAppFocus(
288             int appType, OnAppFocusOwnershipCallback ownershipCallback) {
289         if (ownershipCallback == null) {
290             throw new IllegalArgumentException("null listener");
291         }
292         IAppFocusOwnershipCallbackImpl binder;
293         synchronized (this) {
294             binder = mOwnershipBinders.get(ownershipCallback);
295             if (binder == null) {
296                 binder = new IAppFocusOwnershipCallbackImpl(this, ownershipCallback);
297                 mOwnershipBinders.put(ownershipCallback, binder);
298             }
299             binder.addAppType(appType);
300         }
301         try {
302             return mService.requestAppFocus(binder, appType);
303         } catch (RemoteException e) {
304             return handleRemoteExceptionFromCarService(e, APP_FOCUS_REQUEST_FAILED);
305         }
306     }
307 
308     /**
309      * Abandons the given focus, marking it as inactive. This also involves releasing ownership
310      * for the focus.
311      * @param ownershipCallback
312      * @param appType
313      */
abandonAppFocus(OnAppFocusOwnershipCallback ownershipCallback, @AppFocusType int appType)314     public void abandonAppFocus(OnAppFocusOwnershipCallback ownershipCallback,
315             @AppFocusType int appType) {
316         if (ownershipCallback == null) {
317             throw new IllegalArgumentException("null callback");
318         }
319         IAppFocusOwnershipCallbackImpl binder;
320         synchronized (this) {
321             binder = mOwnershipBinders.get(ownershipCallback);
322             if (binder == null) {
323                 return;
324             }
325         }
326         try {
327             mService.abandonAppFocus(binder, appType);
328         } catch (RemoteException e) {
329             handleRemoteExceptionFromCarService(e);
330             // continue for local clean-up
331         }
332         synchronized (this) {
333             binder.removeAppType(appType);
334             if (!binder.hasAppTypes()) {
335                 mOwnershipBinders.remove(ownershipCallback);
336             }
337         }
338     }
339 
340     /**
341      * Abandons all focuses, marking them as inactive. This also involves releasing ownership
342      * for the focus.
343      * @param ownershipCallback
344      */
abandonAppFocus(OnAppFocusOwnershipCallback ownershipCallback)345     public void abandonAppFocus(OnAppFocusOwnershipCallback ownershipCallback) {
346         IAppFocusOwnershipCallbackImpl binder;
347         synchronized (this) {
348             binder = mOwnershipBinders.remove(ownershipCallback);
349             if (binder == null) {
350                 return;
351             }
352         }
353         try {
354             for (Integer appType : binder.getAppTypes()) {
355                 mService.abandonAppFocus(binder, appType);
356             }
357         } catch (RemoteException e) {
358             handleRemoteExceptionFromCarService(e);
359         }
360     }
361 
362     /** @hide */
363     @Override
onCarDisconnected()364     public void onCarDisconnected() {
365         // nothing to do
366     }
367 
368     private static class IAppFocusListenerImpl extends IAppFocusListener.Stub {
369 
370         private final WeakReference<CarAppFocusManager> mManager;
371         private final WeakReference<OnAppFocusChangedListener> mListener;
372         private final Set<Integer> mAppTypes = new HashSet<>();
373 
IAppFocusListenerImpl(CarAppFocusManager manager, OnAppFocusChangedListener listener)374         private IAppFocusListenerImpl(CarAppFocusManager manager,
375                 OnAppFocusChangedListener listener) {
376             mManager = new WeakReference<>(manager);
377             mListener = new WeakReference<>(listener);
378         }
379 
addAppType(@ppFocusType int appType)380         public void addAppType(@AppFocusType int appType) {
381             mAppTypes.add(appType);
382         }
383 
removeAppType(@ppFocusType int appType)384         public void removeAppType(@AppFocusType int appType) {
385             mAppTypes.remove(appType);
386         }
387 
getAppTypes()388         public Set<Integer> getAppTypes() {
389             return mAppTypes;
390         }
391 
hasAppTypes()392         public boolean hasAppTypes() {
393             return !mAppTypes.isEmpty();
394         }
395 
396         @Override
onAppFocusChanged(final @AppFocusType int appType, final boolean active)397         public void onAppFocusChanged(final @AppFocusType int appType, final boolean active) {
398             final CarAppFocusManager manager = mManager.get();
399             final OnAppFocusChangedListener listener = mListener.get();
400             if (manager == null || listener == null) {
401                 return;
402             }
403             manager.getEventHandler().post(() -> {
404                 listener.onAppFocusChanged(appType, active);
405             });
406         }
407     }
408 
409     private static class IAppFocusOwnershipCallbackImpl extends IAppFocusOwnershipCallback.Stub {
410 
411         private final WeakReference<CarAppFocusManager> mManager;
412         private final WeakReference<OnAppFocusOwnershipCallback> mCallback;
413         private final Set<Integer> mAppTypes = new HashSet<>();
414 
IAppFocusOwnershipCallbackImpl(CarAppFocusManager manager, OnAppFocusOwnershipCallback callback)415         private IAppFocusOwnershipCallbackImpl(CarAppFocusManager manager,
416                 OnAppFocusOwnershipCallback callback) {
417             mManager = new WeakReference<>(manager);
418             mCallback = new WeakReference<>(callback);
419         }
420 
addAppType(@ppFocusType int appType)421         public void addAppType(@AppFocusType int appType) {
422             mAppTypes.add(appType);
423         }
424 
removeAppType(@ppFocusType int appType)425         public void removeAppType(@AppFocusType int appType) {
426             mAppTypes.remove(appType);
427         }
428 
getAppTypes()429         public Set<Integer> getAppTypes() {
430             return mAppTypes;
431         }
432 
hasAppTypes()433         public boolean hasAppTypes() {
434             return !mAppTypes.isEmpty();
435         }
436 
437         @Override
onAppFocusOwnershipLost(final @AppFocusType int appType)438         public void onAppFocusOwnershipLost(final @AppFocusType int appType) {
439             final CarAppFocusManager manager = mManager.get();
440             final OnAppFocusOwnershipCallback callback = mCallback.get();
441             if (manager == null || callback == null) {
442                 return;
443             }
444             manager.getEventHandler().post(() -> {
445                 callback.onAppFocusOwnershipLost(appType);
446             });
447         }
448 
449         @Override
onAppFocusOwnershipGranted(final @AppFocusType int appType)450         public void onAppFocusOwnershipGranted(final @AppFocusType int appType) {
451             final CarAppFocusManager manager = mManager.get();
452             final OnAppFocusOwnershipCallback callback = mCallback.get();
453             if (manager == null || callback == null) {
454                 return;
455             }
456             manager.getEventHandler().post(() -> {
457                 callback.onAppFocusOwnershipGranted(appType);
458             });
459         }
460     }
461 }
462