• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onBind(Intent intent)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
getNavigationRenderer()99     protected abstract NavigationRenderer getNavigationRenderer();
100 
101     /** Called when key event that was addressed to instrument cluster display has been received. */
102     @MainThread
onKeyEvent(KeyEvent keyEvent)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      */
setClusterActivityLaunchOptions(String category, ActivityOptions activityOptions)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      */
setClusterActivityState(String category, Bundle state)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
dump(FileDescriptor fd, PrintWriter writer, String[] args)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 
RendererBinder(NavigationRenderer navigationRenderer)189         RendererBinder(NavigationRenderer navigationRenderer) {
190             mNavigationRenderer = navigationRenderer;
191             mUiHandler = new UiHandler(InstrumentClusterRenderingService.this);
192         }
193 
194         @Override
getNavigationService()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
setNavigationContextOwner(int uid, int pid)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
onKeyEvent(KeyEvent keyEvent)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 
NavigationBinder(NavigationRenderer navigationRenderer)230         NavigationBinder(NavigationRenderer navigationRenderer) {
231             mNavigationRenderer = ThreadSafeNavigationRenderer.createFor(
232                     Looper.getMainLooper(),
233                     navigationRenderer);
234         }
235 
setNavigationContextOwner(int uid, int pid)236         void setNavigationContextOwner(int uid, int pid) {
237             mNavContextOwner = new Pair<>(uid, pid);
238         }
239 
240         @Override
onEvent(int eventType, Bundle bundle)241         public void onEvent(int eventType, Bundle bundle) throws RemoteException {
242             assertContextOwnership();
243             mNavigationRenderer.onEvent(eventType, bundle);
244         }
245 
246         @Override
getInstrumentClusterInfo()247         public CarNavigationInstrumentCluster getInstrumentClusterInfo() throws RemoteException {
248             return mNavigationRenderer.getNavigationProperties();
249         }
250 
assertContextOwnership()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 
UiHandler(InstrumentClusterRenderingService service)267         UiHandler(InstrumentClusterRenderingService service) {
268             mRefService = new WeakReference<>(service);
269         }
270 
271         @Override
handleMessage(Message msg)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 
doKeyEvent(KeyEvent event)285         void doKeyEvent(KeyEvent event) {
286             sendMessage(obtainMessage(KEY_EVENT, event));
287         }
288     }
289 }
290