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