/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.car.cluster; import android.annotation.SystemApi; import android.car.CarManagerBase; import android.car.CarNotConnectedException; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; import android.util.Pair; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * API to work with instrument cluster. * * @hide */ @SystemApi public class CarInstrumentClusterManager implements CarManagerBase { private static final String TAG = CarInstrumentClusterManager.class.getSimpleName(); /** @hide */ @SystemApi public static final String CATEGORY_NAVIGATION = "android.car.cluster.NAVIGATION"; /** * When activity in the cluster is launched it will receive {@link ClusterActivityState} in the * intent's extra thus activity will know information about unobscured area, etc. upon activity * creation. * * @hide */ @SystemApi public static final String KEY_EXTRA_ACTIVITY_STATE = "android.car.cluster.ClusterActivityState"; private final EventHandler mHandler; private final Map> mCallbacksByCategory = new HashMap<>(0); private final Object mLock = new Object(); private final Map mActivityStatesByCategory = new HashMap<>(0); private final IInstrumentClusterManagerService mService; private ClusterManagerCallback mServiceToManagerCallback; /** * Starts activity in the instrument cluster. * * @hide */ @SystemApi public void startActivity(Intent intent) throws CarNotConnectedException { try { mService.startClusterActivity(intent); } catch (RemoteException e) { throw new CarNotConnectedException(e); } } /** * Caller of this method will receive immediate callback with the most recent state if state * exists for given category. * * @param category category of the activity in the cluster, * see {@link #CATEGORY_NAVIGATION} * @param callback instance of {@link Callback} class to receive events. * * @hide */ @SystemApi public void registerCallback(String category, Callback callback) throws CarNotConnectedException { Log.i(TAG, "registerCallback, category: " + category + ", callback: " + callback); ClusterManagerCallback callbackToCarService = null; synchronized (mLock) { Set callbacks = mCallbacksByCategory.get(category); if (callbacks == null) { callbacks = new HashSet<>(1); mCallbacksByCategory.put(category, callbacks); } if (!callbacks.add(callback)) { Log.w(TAG, "registerCallback: already registered"); return; // already registered } if (mActivityStatesByCategory.containsKey(category)) { Log.i(TAG, "registerCallback: sending activity state..."); callback.onClusterActivityStateChanged( category, mActivityStatesByCategory.get(category)); } if (mServiceToManagerCallback == null) { Log.i(TAG, "registerCallback: registering callback with car service..."); mServiceToManagerCallback = new ClusterManagerCallback(); callbackToCarService = mServiceToManagerCallback; } } try { mService.registerCallback(callbackToCarService); Log.i(TAG, "registerCallback: done"); } catch (RemoteException e) { throw new CarNotConnectedException(e); } } /** * Unregisters given callback for all activity categories. * * @param callback previously registered callback * * @hide */ @SystemApi public void unregisterCallback(Callback callback) throws CarNotConnectedException { List keysToRemove = new ArrayList<>(1); synchronized (mLock) { for (Map.Entry> entry : mCallbacksByCategory.entrySet()) { Set callbacks = entry.getValue(); if (callbacks.remove(callback) && callbacks.isEmpty()) { keysToRemove.add(entry.getKey()); } } for (String key: keysToRemove) { mCallbacksByCategory.remove(key); } if (mCallbacksByCategory.isEmpty()) { try { mService.unregisterCallback(mServiceToManagerCallback); } catch (RemoteException e) { throw new CarNotConnectedException(e); } mServiceToManagerCallback = null; } } } /** @hide */ public CarInstrumentClusterManager(IBinder service, Handler handler) { mService = IInstrumentClusterManagerService.Stub.asInterface(service); mHandler = new EventHandler(handler.getLooper()); } /** @hide */ @SystemApi public interface Callback { /** * Notify client that activity state was changed. * * @param category cluster activity category, see {@link #CATEGORY_NAVIGATION} * @param clusterActivityState see {@link ClusterActivityState} how to read this bundle. */ void onClusterActivityStateChanged(String category, Bundle clusterActivityState); } /** @hide */ @Override public void onCarDisconnected() { } private class EventHandler extends Handler { final static int MSG_ACTIVITY_STATE = 1; EventHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { Log.i(TAG, "handleMessage, message: " + msg); switch (msg.what) { case MSG_ACTIVITY_STATE: Pair info = (Pair) msg.obj; String category = info.first; Bundle state = info.second; List callbacks = null; synchronized (mLock) { if (mCallbacksByCategory.containsKey(category)) { callbacks = new ArrayList<>(mCallbacksByCategory.get(category)); } } Log.i(TAG, "handleMessage, callbacks: " + callbacks); if (callbacks != null) { for (CarInstrumentClusterManager.Callback cb : callbacks) { cb.onClusterActivityStateChanged(category, state); } } break; default: Log.e(TAG, "Unexpected message: " + msg.what); } } } private class ClusterManagerCallback extends IInstrumentClusterManagerCallback.Stub { @Override public void setClusterActivityState(String category, Bundle clusterActivityState) throws RemoteException { Log.i(TAG, "setClusterActivityState, category: " + category); synchronized (mLock) { mActivityStatesByCategory.put(category, clusterActivityState); } mHandler.sendMessage(mHandler.obtainMessage(EventHandler.MSG_ACTIVITY_STATE, new Pair<>(category, clusterActivityState))); } } }