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