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