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 * <service android:name=".MyInstrumentClusterService" 52 * android:permission="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"> 53 * </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