Home | History | Annotate | Download | only in renderer
      1 /*
      2  * Copyright (C) 2016 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 package android.car.cluster.renderer;
     17 
     18 import android.annotation.CallSuper;
     19 import android.annotation.MainThread;
     20 import android.annotation.SystemApi;
     21 import android.app.ActivityOptions;
     22 import android.app.Service;
     23 import android.car.CarLibLog;
     24 import android.car.CarNotConnectedException;
     25 import android.car.navigation.CarNavigationInstrumentCluster;
     26 import android.content.Intent;
     27 import android.graphics.Bitmap;
     28 import android.os.Bundle;
     29 import android.os.Handler;
     30 import android.os.IBinder;
     31 import android.os.Looper;
     32 import android.os.Message;
     33 import android.os.RemoteException;
     34 import android.util.Log;
     35 import android.util.Pair;
     36 import android.view.KeyEvent;
     37 
     38 import com.android.internal.annotations.GuardedBy;
     39 
     40 import java.io.FileDescriptor;
     41 import java.io.PrintWriter;
     42 import java.lang.ref.WeakReference;
     43 
     44 /**
     45  * A service that used for interaction between Car Service and Instrument Cluster. Car Service may
     46  * provide internal navigation binder interface to Navigation App and all notifications will be
     47  * eventually land in the {@link NavigationRenderer} returned by {@link #getNavigationRenderer()}.
     48  *
     49  * <p>To extend this class, you must declare the service in your manifest file with
     50  * the {@code android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE} permission
     51  * <pre>
     52  * &lt;service android:name=".MyInstrumentClusterService"
     53  *          android:permission="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE">
     54  * &lt;/service></pre>
     55  * <p>Also, you will need to register this service in the following configuration file:
     56  * {@code packages/services/Car/service/res/values/config.xml}
     57  *
     58  * @hide
     59  */
     60 @SystemApi
     61 public abstract class InstrumentClusterRenderingService extends Service {
     62 
     63     private static final String TAG = CarLibLog.TAG_CLUSTER;
     64 
     65     private RendererBinder mRendererBinder;
     66 
     67     /** @hide */
     68     public static final String EXTRA_KEY_CALLBACK_SERVICE =
     69             "android.car.cluster.IInstrumentClusterCallback";
     70 
     71     private final Object mLock = new Object();
     72     @GuardedBy("mLock")
     73     private IInstrumentClusterCallback mCallback;
     74 
     75     @Override
     76     @CallSuper
     77     public IBinder onBind(Intent intent) {
     78         if (Log.isLoggable(TAG, Log.DEBUG)) {
     79             Log.d(TAG, "onBind, intent: " + intent);
     80         }
     81 
     82         if (intent.getExtras().containsKey(EXTRA_KEY_CALLBACK_SERVICE)) {
     83             IBinder callbackBinder = intent.getExtras().getBinder(EXTRA_KEY_CALLBACK_SERVICE);
     84             synchronized (mLock) {
     85                 mCallback = IInstrumentClusterCallback.Stub.asInterface(callbackBinder);
     86             }
     87         } else {
     88             Log.w(TAG, "onBind, no callback in extra!");
     89         }
     90 
     91         if (mRendererBinder == null) {
     92             mRendererBinder = new RendererBinder(getNavigationRenderer());
     93         }
     94 
     95         return mRendererBinder;
     96     }
     97 
     98     /** Returns {@link NavigationRenderer} or null if it's not supported. */
     99     @MainThread
    100     protected abstract NavigationRenderer getNavigationRenderer();
    101 
    102     /** Called when key event that was addressed to instrument cluster display has been received. */
    103     @MainThread
    104     protected void onKeyEvent(KeyEvent keyEvent) {
    105     }
    106 
    107     /**
    108      *
    109      * Sets configuration for activities that should be launched directly in the instrument
    110      * cluster.
    111      *
    112      * @param category category of cluster activity
    113      * @param activityOptions contains information of how to start cluster activity (on what display
    114      *                        or activity stack.
    115      *
    116      * @hide
    117      */
    118     public void setClusterActivityLaunchOptions(String category,
    119             ActivityOptions activityOptions) throws CarNotConnectedException {
    120         IInstrumentClusterCallback cb;
    121         synchronized (mLock) {
    122             cb = mCallback;
    123         }
    124         if (cb == null) throw new CarNotConnectedException();
    125         try {
    126             cb.setClusterActivityLaunchOptions(category, activityOptions.toBundle());
    127         } catch (RemoteException e) {
    128             throw new CarNotConnectedException(e);
    129         }
    130     }
    131 
    132     /**
    133      *
    134      * @param category cluster activity category,
    135      *        see {@link android.car.cluster.CarInstrumentClusterManager}
    136      * @param state pass information about activity state,
    137      *        see {@link android.car.cluster.ClusterActivityState}
    138      * @return true if information was sent to Car Service
    139      * @throws CarNotConnectedException
    140      *
    141      * @hide
    142      */
    143     public void setClusterActivityState(String category, Bundle state)
    144             throws CarNotConnectedException {
    145         IInstrumentClusterCallback cb;
    146         synchronized (mLock) {
    147             cb = mCallback;
    148         }
    149         if (cb == null) throw new CarNotConnectedException();
    150         try {
    151             cb.setClusterActivityState(category, state);
    152         } catch (RemoteException e) {
    153             throw new CarNotConnectedException(e);
    154         }
    155     }
    156 
    157 
    158     @Override
    159     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
    160         writer.println("**" + getClass().getSimpleName() + "**");
    161         writer.println("renderer binder: " + mRendererBinder);
    162         if (mRendererBinder != null) {
    163             writer.println("navigation renderer: " + mRendererBinder.mNavigationRenderer);
    164             String owner = "none";
    165             synchronized (mLock) {
    166                 if (mRendererBinder.mNavContextOwner != null) {
    167                     owner = "[uid: " + mRendererBinder.mNavContextOwner.first
    168                             + ", pid: " + mRendererBinder.mNavContextOwner.second + "]";
    169                 }
    170             }
    171             writer.println("navigation focus owner: " + owner);
    172         }
    173         IInstrumentClusterCallback cb;
    174         synchronized (mLock) {
    175             cb = mCallback;
    176         }
    177         writer.println("callback: " + cb);
    178     }
    179 
    180     private class RendererBinder extends IInstrumentCluster.Stub {
    181 
    182         private final NavigationRenderer mNavigationRenderer;
    183         private final UiHandler mUiHandler;
    184 
    185         @GuardedBy("mLock")
    186         private NavigationBinder mNavigationBinder;
    187         @GuardedBy("mLock")
    188         private Pair<Integer, Integer> mNavContextOwner;
    189 
    190         RendererBinder(NavigationRenderer navigationRenderer) {
    191             mNavigationRenderer = navigationRenderer;
    192             mUiHandler = new UiHandler(InstrumentClusterRenderingService.this);
    193         }
    194 
    195         @Override
    196         public IInstrumentClusterNavigation getNavigationService() throws RemoteException {
    197             synchronized (mLock) {
    198                 if (mNavigationBinder == null) {
    199                     mNavigationBinder = new NavigationBinder(mNavigationRenderer);
    200                     if (mNavContextOwner != null) {
    201                         mNavigationBinder.setNavigationContextOwner(
    202                                 mNavContextOwner.first, mNavContextOwner.second);
    203                     }
    204                 }
    205                 return mNavigationBinder;
    206             }
    207         }
    208 
    209         @Override
    210         public void setNavigationContextOwner(int uid, int pid) throws RemoteException {
    211             synchronized (mLock) {
    212                 mNavContextOwner = new Pair<>(uid, pid);
    213                 if (mNavigationBinder != null) {
    214                     mNavigationBinder.setNavigationContextOwner(uid, pid);
    215                 }
    216             }
    217         }
    218 
    219         @Override
    220         public void onKeyEvent(KeyEvent keyEvent) throws RemoteException {
    221             mUiHandler.doKeyEvent(keyEvent);
    222         }
    223     }
    224 
    225     private class NavigationBinder extends IInstrumentClusterNavigation.Stub {
    226 
    227         private final NavigationRenderer mNavigationRenderer;  // Thread-safe navigation renderer.
    228 
    229         private volatile Pair<Integer, Integer> mNavContextOwner;
    230 
    231         NavigationBinder(NavigationRenderer navigationRenderer) {
    232             mNavigationRenderer = ThreadSafeNavigationRenderer.createFor(
    233                     Looper.getMainLooper(),
    234                     navigationRenderer);
    235         }
    236 
    237         void setNavigationContextOwner(int uid, int pid) {
    238             mNavContextOwner = new Pair<>(uid, pid);
    239         }
    240 
    241         @Override
    242         public void onStartNavigation() throws RemoteException {
    243             assertContextOwnership();
    244             mNavigationRenderer.onStartNavigation();
    245         }
    246 
    247         @Override
    248         public void onStopNavigation() throws RemoteException {
    249             assertContextOwnership();
    250             mNavigationRenderer.onStopNavigation();
    251         }
    252 
    253         @Override
    254         public void onNextManeuverChanged(int event, CharSequence eventName, int turnAngle,
    255                 int turnNumber, Bitmap image, int turnSide) throws RemoteException {
    256             assertContextOwnership();
    257             mNavigationRenderer.onNextTurnChanged(event, eventName, turnAngle, turnNumber,
    258                     image, turnSide);
    259         }
    260 
    261         @Override
    262         public void onNextManeuverDistanceChanged(int distanceMeters, int timeSeconds,
    263                 int displayDistanceMillis, int displayDistanceUnit) throws RemoteException {
    264             assertContextOwnership();
    265             mNavigationRenderer.onNextTurnDistanceChanged(distanceMeters, timeSeconds,
    266                     displayDistanceMillis, displayDistanceUnit);
    267         }
    268 
    269         @Override
    270         public void onEvent(int eventType, Bundle bundle) throws RemoteException {
    271             assertContextOwnership();
    272             mNavigationRenderer.onEvent(eventType, bundle);
    273         }
    274 
    275         @Override
    276         public CarNavigationInstrumentCluster getInstrumentClusterInfo() throws RemoteException {
    277             return mNavigationRenderer.getNavigationProperties();
    278         }
    279 
    280         private void assertContextOwnership() {
    281             int uid = getCallingUid();
    282             int pid = getCallingPid();
    283 
    284             Pair<Integer, Integer> owner = mNavContextOwner;
    285             if (owner == null || owner.first != uid || owner.second != pid) {
    286                 throw new IllegalStateException("Client (uid:" + uid + ", pid: " + pid + ") is"
    287                         + "not an owner of APP_CONTEXT_NAVIGATION");
    288             }
    289         }
    290     }
    291 
    292     private static class UiHandler extends Handler {
    293         private static int KEY_EVENT = 0;
    294         private final WeakReference<InstrumentClusterRenderingService> mRefService;
    295 
    296         UiHandler(InstrumentClusterRenderingService service) {
    297             mRefService = new WeakReference<>(service);
    298         }
    299 
    300         @Override
    301         public void handleMessage(Message msg) {
    302             InstrumentClusterRenderingService service = mRefService.get();
    303             if (service == null) {
    304                 return;
    305             }
    306 
    307             if (msg.what == KEY_EVENT) {
    308                 service.onKeyEvent((KeyEvent) msg.obj);
    309             } else {
    310                 throw new IllegalArgumentException("Unexpected message: " + msg);
    311             }
    312         }
    313 
    314         void doKeyEvent(KeyEvent event) {
    315             sendMessage(obtainMessage(KEY_EVENT, event));
    316         }
    317     }
    318 }
    319