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