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