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.os.Handler;
20 import android.os.IBinder;
21 import android.os.Looper;
22 import android.os.RemoteException;
23 
24 import java.lang.ref.WeakReference;
25 import java.util.HashMap;
26 import java.util.Map;
27 
28 /**
29  * CarAppContextManager allows applications to set and listen for the current application context
30  * like active navigation or active voice command. Usually only one instance of such application
31  * should run in the system, and other app setting the flag for the matching app should
32  * lead into other app to stop.
33  * @hide
34  */
35 public class CarAppContextManager implements CarManagerBase {
36     /**
37      * Listener to get notification for app getting information on app context change.
38      */
39     public interface AppContextChangeListener {
40         /**
41          * Application context has changed. Note that {@link CarAppContextManager} instance
42          * causing the change will not get this notification.
43          * @param activeContexts
44          */
onAppContextChange(int activeContexts)45         void onAppContextChange(int activeContexts);
46     }
47 
48     /**
49      * Listener to get notification for app getting information on app context ownership loss.
50      */
51     public interface AppContextOwnershipChangeListener {
52         /**
53          * Lost ownership for the context, which happens when other app has set the context.
54          * The app losing context should stop the action associated with the context.
55          * For example, navigaiton app currently running active navigation should stop navigation
56          * upon getting this for {@link CarAppContextManager#APP_CONTEXT_NAVIGATION}.
57          * @param context
58          */
onAppContextOwnershipLoss(int context)59         void onAppContextOwnershipLoss(int context);
60     }
61 
62     /** @hide */
63     public static final int APP_CONTEXT_START_FLAG = 0x1;
64     /**
65      * Flag for active navigation.
66      */
67     public static final int APP_CONTEXT_NAVIGATION = 0x1;
68     /**
69      * Flag for active voice command.
70      */
71     public static final int APP_CONTEXT_VOICE_COMMAND = 0x2;
72     /**
73      * Update this after adding a new flag.
74      * @hide
75      */
76     public static final int APP_CONTEXT_END_FLAG = 0x2;
77 
78     private final IAppContext mService;
79     private final Handler mHandler;
80     private final IAppContextListenerImpl mBinderListener;
81     private final Map<Integer, AppContextOwnershipChangeListener> mOwnershipListeners;
82 
83     private AppContextChangeListener mListener;
84     private int mContextFilter;
85 
86     /**
87      * @hide
88      */
CarAppContextManager(IBinder service, Looper looper)89     CarAppContextManager(IBinder service, Looper looper) {
90         mService = IAppContext.Stub.asInterface(service);
91         mHandler = new Handler(looper);
92         mBinderListener = new IAppContextListenerImpl(this);
93         mOwnershipListeners = new HashMap<Integer, AppContextOwnershipChangeListener>();
94     }
95 
96     /**
97      * Register listener to monitor app context change. Only one listener can be registered and
98      * registering multiple times will lead into only the last listener to be active.
99      * @param listener
100      * @param contextFilter Flags of cotexts to get notification.
101      * @throws CarNotConnectedException
102      */
registerContextListener(AppContextChangeListener listener, int contextFilter)103     public void registerContextListener(AppContextChangeListener listener, int contextFilter)
104             throws CarNotConnectedException {
105         if (listener == null) {
106             throw new IllegalArgumentException("null listener");
107         }
108         synchronized(this) {
109             if (mListener == null || mContextFilter != contextFilter) {
110                 try {
111                     mService.registerContextListener(mBinderListener, contextFilter);
112                 } catch (RemoteException e) {
113                     throw new CarNotConnectedException(e);
114                 }
115             }
116             mListener = listener;
117             mContextFilter = contextFilter;
118         }
119     }
120 
121     /**
122      * Unregister listener and stop listening context change events. If app has owned a context
123      * by {@link #setActiveContext(int)}, it will be reset to inactive state.
124      * @throws CarNotConnectedException
125      */
unregisterContextListener()126     public void unregisterContextListener() throws CarNotConnectedException {
127         synchronized(this) {
128             try {
129                 mService.unregisterContextListener(mBinderListener);
130             } catch (RemoteException e) {
131                 throw new CarNotConnectedException(e);
132             }
133             mListener = null;
134             mContextFilter = 0;
135         }
136     }
137 
getActiveAppContexts()138     public int getActiveAppContexts() throws CarNotConnectedException {
139         try {
140             return mService.getActiveAppContexts();
141         } catch (RemoteException e) {
142             throw new CarNotConnectedException(e);
143         }
144     }
145 
isOwningContext(int context)146     public boolean isOwningContext(int context) throws CarNotConnectedException {
147         try {
148             return mService.isOwningContext(mBinderListener, context);
149         } catch (RemoteException e) {
150             throw new CarNotConnectedException(e);
151         }
152     }
153 
154     /**
155      * Set the given contexts as active. By setting this, the application is becoming owner
156      * of the context, and will get
157      * {@link AppContextOwnershipChangeListener#onAppContextOwnershipLoss(int)}
158      * if ownership is given to other app by calling this. Fore-ground app will have higher priority
159      * and other app cannot set the same context while owner is in fore-ground.
160      * Only one listener per context can be registered and
161      * registering multiple times will lead into only the last listener to be active.
162      * @param ownershipListener
163      * @param contexts
164      * @throws CarNotConnectedException
165      * @throws SecurityException If owner cannot be changed.
166      */
setActiveContexts(AppContextOwnershipChangeListener ownershipListener, int contexts)167     public void setActiveContexts(AppContextOwnershipChangeListener ownershipListener, int contexts)
168             throws SecurityException, CarNotConnectedException {
169         if (ownershipListener == null) {
170             throw new IllegalArgumentException("null listener");
171         }
172         synchronized (this) {
173             try {
174                 mService.setActiveContexts(mBinderListener, contexts);
175             } catch (RemoteException e) {
176                 throw new CarNotConnectedException(e);
177             }
178             for (int flag = APP_CONTEXT_START_FLAG; flag <= APP_CONTEXT_END_FLAG; flag <<= 1) {
179                 if ((flag & contexts) != 0) {
180                     mOwnershipListeners.put(flag, ownershipListener);
181                 }
182             }
183         }
184     }
185 
186     /**
187      * Reset the given contexts, i.e. mark them as inactive. This also involves releasing ownership
188      * for the context.
189      * @param contexts
190      * @throws CarNotConnectedException
191      */
resetActiveContexts(int contexts)192     public void resetActiveContexts(int contexts) throws CarNotConnectedException {
193         try {
194             mService.resetActiveContexts(mBinderListener, contexts);
195         } catch (RemoteException e) {
196             throw new CarNotConnectedException(e);
197         }
198         synchronized (this) {
199             for (int flag = APP_CONTEXT_START_FLAG; flag <= APP_CONTEXT_END_FLAG; flag <<= 1) {
200                 if ((flag & contexts) != 0) {
201                     mOwnershipListeners.remove(flag);
202                 }
203             }
204         }
205     }
206 
207     @Override
onCarDisconnected()208     public void onCarDisconnected() {
209         // nothing to do
210     }
211 
handleAppContextChange(int activeContexts)212     private void handleAppContextChange(int activeContexts) {
213         AppContextChangeListener listener;
214         int newContext;
215         synchronized (this) {
216             if (mListener == null) {
217                 return;
218             }
219             listener = mListener;
220             newContext = activeContexts & mContextFilter;
221         }
222         listener.onAppContextChange(newContext);
223     }
224 
handleAppContextOwnershipLoss(int context)225     private void handleAppContextOwnershipLoss(int context) {
226         AppContextOwnershipChangeListener listener;
227         synchronized (this) {
228             listener = mOwnershipListeners.get(context);
229             if (listener == null) {
230                 return;
231             }
232         }
233         listener.onAppContextOwnershipLoss(context);
234     }
235 
236     private static class IAppContextListenerImpl extends IAppContextListener.Stub {
237 
238         private final WeakReference<CarAppContextManager> mManager;
239 
IAppContextListenerImpl(CarAppContextManager manager)240         private IAppContextListenerImpl(CarAppContextManager manager) {
241             mManager = new WeakReference<>(manager);
242         }
243 
244         @Override
onAppContextChange(final int activeContexts)245         public void onAppContextChange(final int activeContexts) {
246             final CarAppContextManager manager = mManager.get();
247             if (manager == null) {
248                 return;
249             }
250             manager.mHandler.post(new Runnable() {
251                 @Override
252                 public void run() {
253                     manager.handleAppContextChange(activeContexts);
254                 }
255             });
256         }
257 
258         @Override
onAppContextOwnershipLoss(final int context)259         public void onAppContextOwnershipLoss(final int context) {
260             final CarAppContextManager manager = mManager.get();
261             if (manager == null) {
262                 return;
263             }
264             manager.mHandler.post(new Runnable() {
265                 @Override
266                 public void run() {
267                     manager.handleAppContextOwnershipLoss(context);
268                 }
269             });
270         }
271     }
272 }
273