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 */ 16package android.car.cluster.renderer; 17 18import android.annotation.CallSuper; 19import android.annotation.MainThread; 20import android.annotation.SystemApi; 21import android.app.Service; 22import android.car.CarLibLog; 23import android.car.navigation.CarNavigationInstrumentCluster; 24import android.content.Intent; 25import android.graphics.Bitmap; 26import android.os.Handler; 27import android.os.IBinder; 28import android.os.Looper; 29import android.os.Message; 30import android.os.RemoteException; 31import android.util.Log; 32import android.util.Pair; 33import android.view.KeyEvent; 34 35import java.io.FileDescriptor; 36import java.io.PrintWriter; 37import java.lang.ref.WeakReference; 38 39/** 40 * A service that used for interaction between Car Service and Instrument Cluster. Car Service may 41 * provide internal navigation binder interface to Navigation App and all notifications will be 42 * eventually land in the {@link NavigationRenderer} returned by {@link #getNavigationRenderer()}. 43 * 44 * <p>To extend this class, you must declare the service in your manifest file with 45 * the {@code android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE} permission 46 * <pre> 47 * <service android:name=".MyInstrumentClusterService" 48 * android:permission="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"> 49 * </service></pre> 50 * <p>Also, you will need to register this service in the following configuration file: 51 * {@code packages/services/Car/service/res/values/config.xml} 52 * 53 * @hide 54 */ 55@SystemApi 56public abstract class InstrumentClusterRenderingService extends Service { 57 58 private static final String TAG = CarLibLog.TAG_CLUSTER; 59 60 private RendererBinder mRendererBinder; 61 62 @Override 63 @CallSuper 64 public IBinder onBind(Intent intent) { 65 if (Log.isLoggable(TAG, Log.DEBUG)) { 66 Log.d(TAG, "onBind, intent: " + intent); 67 } 68 69 if (mRendererBinder == null) { 70 mRendererBinder = new RendererBinder(getNavigationRenderer()); 71 } 72 73 return mRendererBinder; 74 } 75 76 /** Returns {@link NavigationRenderer} or null if it's not supported. */ 77 @MainThread 78 protected abstract NavigationRenderer getNavigationRenderer(); 79 80 /** Called when key event that was addressed to instrument cluster display has been received. */ 81 @MainThread 82 protected void onKeyEvent(KeyEvent keyEvent) { 83 } 84 85 @Override 86 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 87 writer.println("**" + getClass().getSimpleName() + "**"); 88 writer.println("renderer binder: " + mRendererBinder); 89 if (mRendererBinder != null) { 90 writer.println("navigation renderer: " + mRendererBinder.mNavigationRenderer); 91 String owner = "none"; 92 if (mRendererBinder.mNavContextOwner != null) { 93 owner = "[uid: " + mRendererBinder.mNavContextOwner.first 94 + ", pid: " + mRendererBinder.mNavContextOwner.second + "]"; 95 } 96 writer.println("navigation focus owner: " + owner); 97 } 98 } 99 100 private class RendererBinder extends IInstrumentCluster.Stub { 101 102 private final NavigationRenderer mNavigationRenderer; 103 private final UiHandler mUiHandler; 104 105 private volatile NavigationBinder mNavigationBinder; 106 private volatile Pair<Integer, Integer> mNavContextOwner; 107 108 RendererBinder(NavigationRenderer navigationRenderer) { 109 mNavigationRenderer = navigationRenderer; 110 mUiHandler = new UiHandler(InstrumentClusterRenderingService.this); 111 } 112 113 @Override 114 public IInstrumentClusterNavigation getNavigationService() throws RemoteException { 115 if (mNavigationBinder == null) { 116 mNavigationBinder = new NavigationBinder(mNavigationRenderer); 117 if (mNavContextOwner != null) { 118 mNavigationBinder.setNavigationContextOwner( 119 mNavContextOwner.first, mNavContextOwner.second); 120 } 121 } 122 return mNavigationBinder; 123 } 124 125 @Override 126 public void setNavigationContextOwner(int uid, int pid) throws RemoteException { 127 mNavContextOwner = new Pair<>(uid, pid); 128 if (mNavigationBinder != null) { 129 mNavigationBinder.setNavigationContextOwner(uid, pid); 130 } 131 } 132 133 @Override 134 public void onKeyEvent(KeyEvent keyEvent) throws RemoteException { 135 mUiHandler.doKeyEvent(keyEvent); 136 } 137 } 138 139 private class NavigationBinder extends IInstrumentClusterNavigation.Stub { 140 141 private final NavigationRenderer mNavigationRenderer; // Thread-safe navigation renderer. 142 143 private volatile Pair<Integer, Integer> mNavContextOwner; 144 145 NavigationBinder(NavigationRenderer navigationRenderer) { 146 mNavigationRenderer = ThreadSafeNavigationRenderer.createFor( 147 Looper.getMainLooper(), 148 navigationRenderer); 149 } 150 151 void setNavigationContextOwner(int uid, int pid) { 152 mNavContextOwner = new Pair<>(uid, pid); 153 } 154 155 @Override 156 public void onStartNavigation() throws RemoteException { 157 assertContextOwnership(); 158 mNavigationRenderer.onStartNavigation(); 159 } 160 161 @Override 162 public void onStopNavigation() throws RemoteException { 163 assertContextOwnership(); 164 mNavigationRenderer.onStopNavigation(); 165 } 166 167 @Override 168 public void onNextManeuverChanged(int event, CharSequence eventName, int turnAngle, 169 int turnNumber, Bitmap image, int turnSide) throws RemoteException { 170 assertContextOwnership(); 171 mNavigationRenderer.onNextTurnChanged(event, eventName, turnAngle, turnNumber, 172 image, turnSide); 173 } 174 175 @Override 176 public void onNextManeuverDistanceChanged(int distanceMeters, int timeSeconds, 177 int displayDistanceMillis, int displayDistanceUnit) throws RemoteException { 178 assertContextOwnership(); 179 mNavigationRenderer.onNextTurnDistanceChanged(distanceMeters, timeSeconds, 180 displayDistanceMillis, displayDistanceUnit); 181 } 182 183 @Override 184 public CarNavigationInstrumentCluster getInstrumentClusterInfo() throws RemoteException { 185 return mNavigationRenderer.getNavigationProperties(); 186 } 187 188 private void assertContextOwnership() { 189 int uid = getCallingUid(); 190 int pid = getCallingPid(); 191 192 Pair<Integer, Integer> owner = mNavContextOwner; 193 if (owner == null || owner.first != uid || owner.second != pid) { 194 throw new IllegalStateException("Client (uid:" + uid + ", pid: " + pid + ") is" 195 + "not an owner of APP_CONTEXT_NAVIGATION"); 196 } 197 } 198 } 199 200 private static class UiHandler extends Handler { 201 private static int KEY_EVENT = 0; 202 private final WeakReference<InstrumentClusterRenderingService> mRefService; 203 204 UiHandler(InstrumentClusterRenderingService service) { 205 mRefService = new WeakReference<>(service); 206 } 207 208 @Override 209 public void handleMessage(Message msg) { 210 InstrumentClusterRenderingService service = mRefService.get(); 211 if (service == null) { 212 return; 213 } 214 215 if (msg.what == KEY_EVENT) { 216 service.onKeyEvent((KeyEvent) msg.obj); 217 } else { 218 throw new IllegalArgumentException("Unexpected message: " + msg); 219 } 220 } 221 222 void doKeyEvent(KeyEvent event) { 223 sendMessage(obtainMessage(KEY_EVENT, event)); 224 } 225 } 226} 227