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