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