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 }