Home | History | Annotate | Download | only in cluster
      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 }