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