• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.cluster;
18 
19 import android.annotation.SystemApi;
20 import android.car.CarManagerBase;
21 import android.car.CarNotConnectedException;
22 import android.content.Intent;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.IBinder;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.RemoteException;
29 import android.util.Log;
30 import android.util.Pair;
31 
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 
39 /**
40  * API to work with instrument cluster.
41  *
42  * @hide
43  */
44 @SystemApi
45 public class CarInstrumentClusterManager implements CarManagerBase {
46     private static final String TAG = CarInstrumentClusterManager.class.getSimpleName();
47 
48     /** @hide */
49     @SystemApi
50     public static final String CATEGORY_NAVIGATION = "android.car.cluster.NAVIGATION";
51 
52     /**
53      * When activity in the cluster is launched it will receive {@link ClusterActivityState} in the
54      * intent's extra thus activity will know information about unobscured area, etc. upon activity
55      * creation.
56      *
57      * @hide
58      */
59     @SystemApi
60     public static final String KEY_EXTRA_ACTIVITY_STATE =
61             "android.car.cluster.ClusterActivityState";
62 
63     private final EventHandler mHandler;
64     private final Map<String, Set<Callback>> mCallbacksByCategory = new HashMap<>(0);
65     private final Object mLock = new Object();
66     private final Map<String, Bundle> mActivityStatesByCategory = new HashMap<>(0);
67 
68     private final IInstrumentClusterManagerService mService;
69 
70     private ClusterManagerCallback mServiceToManagerCallback;
71 
72     /**
73      * Starts activity in the instrument cluster.
74      *
75      * @hide
76      */
77     @SystemApi
startActivity(Intent intent)78     public void startActivity(Intent intent) throws CarNotConnectedException {
79         try {
80             mService.startClusterActivity(intent);
81         } catch (RemoteException e) {
82             throw new CarNotConnectedException(e);
83         }
84     }
85 
86     /**
87      * Caller of this method will receive immediate callback with the most recent state if state
88      * exists for given category.
89      *
90      * @param category category of the activity in the cluster,
91      *                         see {@link #CATEGORY_NAVIGATION}
92      * @param callback instance of {@link Callback} class to receive events.
93      *
94      * @hide
95      */
96     @SystemApi
registerCallback(String category, Callback callback)97     public void registerCallback(String category, Callback callback)
98             throws CarNotConnectedException {
99         Log.i(TAG, "registerCallback, category: " + category + ", callback: " + callback);
100         ClusterManagerCallback callbackToCarService = null;
101         synchronized (mLock) {
102             Set<Callback> callbacks = mCallbacksByCategory.get(category);
103             if (callbacks == null) {
104                 callbacks = new HashSet<>(1);
105                 mCallbacksByCategory.put(category, callbacks);
106             }
107             if (!callbacks.add(callback)) {
108                 Log.w(TAG, "registerCallback: already registered");
109                 return;  // already registered
110             }
111 
112             if (mActivityStatesByCategory.containsKey(category)) {
113                 Log.i(TAG, "registerCallback: sending activity state...");
114                 callback.onClusterActivityStateChanged(
115                         category, mActivityStatesByCategory.get(category));
116             }
117 
118             if (mServiceToManagerCallback == null) {
119                 Log.i(TAG, "registerCallback: registering callback with car service...");
120                 mServiceToManagerCallback = new ClusterManagerCallback();
121                 callbackToCarService = mServiceToManagerCallback;
122             }
123         }
124         try {
125             mService.registerCallback(callbackToCarService);
126             Log.i(TAG, "registerCallback: done");
127         } catch (RemoteException e) {
128             throw new CarNotConnectedException(e);
129         }
130     }
131 
132     /**
133      * Unregisters given callback for all activity categories.
134      *
135      * @param callback previously registered callback
136      *
137      * @hide
138      */
139     @SystemApi
unregisterCallback(Callback callback)140     public void unregisterCallback(Callback callback) throws CarNotConnectedException {
141         List<String> keysToRemove = new ArrayList<>(1);
142         synchronized (mLock) {
143             for (Map.Entry<String, Set<Callback>> entry : mCallbacksByCategory.entrySet()) {
144                 Set<Callback> callbacks = entry.getValue();
145                 if (callbacks.remove(callback) && callbacks.isEmpty()) {
146                     keysToRemove.add(entry.getKey());
147                 }
148 
149             }
150 
151             for (String key: keysToRemove) {
152                 mCallbacksByCategory.remove(key);
153             }
154 
155             if (mCallbacksByCategory.isEmpty()) {
156                 try {
157                     mService.unregisterCallback(mServiceToManagerCallback);
158                 } catch (RemoteException e) {
159                     throw new CarNotConnectedException(e);
160                 }
161                 mServiceToManagerCallback = null;
162             }
163         }
164     }
165 
166     /** @hide */
CarInstrumentClusterManager(IBinder service, Handler handler)167     public CarInstrumentClusterManager(IBinder service, Handler handler) {
168         mService = IInstrumentClusterManagerService.Stub.asInterface(service);
169 
170         mHandler = new EventHandler(handler.getLooper());
171     }
172 
173     /** @hide */
174     @SystemApi
175     public interface Callback {
176 
177         /**
178          * Notify client that activity state was changed.
179          *
180          * @param category cluster activity category, see {@link #CATEGORY_NAVIGATION}
181          * @param clusterActivityState see {@link ClusterActivityState} how to read this bundle.
182          */
onClusterActivityStateChanged(String category, Bundle clusterActivityState)183         void onClusterActivityStateChanged(String category, Bundle clusterActivityState);
184     }
185 
186     /** @hide */
187     @Override
onCarDisconnected()188     public void onCarDisconnected() {
189     }
190 
191     private class EventHandler extends Handler {
192 
193         final static int MSG_ACTIVITY_STATE = 1;
194 
EventHandler(Looper looper)195         EventHandler(Looper looper) {
196             super(looper);
197         }
198 
199         @Override
handleMessage(Message msg)200         public void handleMessage(Message msg) {
201             Log.i(TAG, "handleMessage, message: " + msg);
202             switch (msg.what) {
203                 case MSG_ACTIVITY_STATE:
204                     Pair<String, Bundle> info = (Pair<String, Bundle>) msg.obj;
205                     String category = info.first;
206                     Bundle state = info.second;
207                     List<CarInstrumentClusterManager.Callback> callbacks = null;
208                     synchronized (mLock) {
209                         if (mCallbacksByCategory.containsKey(category)) {
210                             callbacks = new ArrayList<>(mCallbacksByCategory.get(category));
211                         }
212                     }
213                     Log.i(TAG, "handleMessage, callbacks: " + callbacks);
214                     if (callbacks != null) {
215                         for (CarInstrumentClusterManager.Callback cb : callbacks) {
216                             cb.onClusterActivityStateChanged(category, state);
217                         }
218                     }
219                     break;
220                 default:
221                     Log.e(TAG, "Unexpected message: " + msg.what);
222             }
223         }
224     }
225 
226     private class ClusterManagerCallback extends IInstrumentClusterManagerCallback.Stub {
227 
228         @Override
setClusterActivityState(String category, Bundle clusterActivityState)229         public void setClusterActivityState(String category, Bundle clusterActivityState)
230                 throws RemoteException {
231             Log.i(TAG, "setClusterActivityState, category: " + category);
232             synchronized (mLock) {
233                 mActivityStatesByCategory.put(category, clusterActivityState);
234             }
235 
236             mHandler.sendMessage(mHandler.obtainMessage(EventHandler.MSG_ACTIVITY_STATE,
237                     new Pair<>(category, clusterActivityState)));
238         }
239     }
240 }