1 /* 2 * Copyright (C) 2017 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.sample; 17 18 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 19 20 import static java.lang.Integer.parseInt; 21 22 import android.app.ActivityOptions; 23 import android.car.CarNotConnectedException; 24 import android.car.cluster.ClusterActivityState; 25 import android.car.cluster.renderer.InstrumentClusterRenderingService; 26 import android.car.cluster.renderer.NavigationRenderer; 27 import android.car.navigation.CarNavigationInstrumentCluster; 28 import android.content.Intent; 29 import android.graphics.Rect; 30 import android.hardware.display.DisplayManager.DisplayListener; 31 import android.os.Binder; 32 import android.os.Bundle; 33 import android.os.IBinder; 34 import android.os.SystemClock; 35 import android.provider.Settings; 36 import android.provider.Settings.Global; 37 import android.util.Log; 38 import android.view.InputDevice; 39 import android.view.KeyEvent; 40 41 import java.io.FileDescriptor; 42 import java.io.PrintWriter; 43 import java.util.Arrays; 44 45 /** 46 * Dummy implementation of {@link SampleClusterServiceImpl} to log all interaction. 47 */ 48 public class SampleClusterServiceImpl extends InstrumentClusterRenderingService { 49 50 private static final String TAG = SampleClusterServiceImpl.class.getSimpleName(); 51 52 private Listener mListener; 53 private final Binder mLocalBinder = new LocalBinder(); 54 static final String LOCAL_BINDING_ACTION = "local"; 55 56 private ClusterDisplayProvider mDisplayProvider; 57 58 @Override onBind(Intent intent)59 public IBinder onBind(Intent intent) { 60 Log.i(TAG, "onBind, intent: " + intent); 61 return (LOCAL_BINDING_ACTION.equals(intent.getAction())) 62 ? mLocalBinder : super.onBind(intent); 63 } 64 65 @Override onCreate()66 public void onCreate() { 67 super.onCreate(); 68 Log.i(TAG, "onCreate"); 69 70 mDisplayProvider = new ClusterDisplayProvider(this, 71 new DisplayListener() { 72 @Override 73 public void onDisplayAdded(int displayId) { 74 Log.i(TAG, "Cluster display found, displayId: " + displayId); 75 doClusterDisplayConnected(displayId); 76 } 77 78 @Override 79 public void onDisplayRemoved(int displayId) { 80 Log.w(TAG, "Cluster display has been removed"); 81 } 82 83 @Override 84 public void onDisplayChanged(int displayId) { 85 86 } 87 }); 88 } 89 doClusterDisplayConnected(int displayId)90 private void doClusterDisplayConnected(int displayId) { 91 ActivityOptions options = ActivityOptions.makeBasic(); 92 options.setLaunchDisplayId(displayId); 93 Intent intent = new Intent(this, MainClusterActivity.class); 94 intent.setFlags(FLAG_ACTIVITY_NEW_TASK); 95 startActivity(intent, options.toBundle()); 96 } 97 98 @Override onKeyEvent(KeyEvent keyEvent)99 protected void onKeyEvent(KeyEvent keyEvent) { 100 Log.i(TAG, "onKeyEvent, keyEvent: " + keyEvent + ", listener: " + mListener); 101 if (mListener != null) { 102 mListener.onKeyEvent(keyEvent); 103 } 104 } 105 106 @Override onDestroy()107 public void onDestroy() { 108 super.onDestroy(); 109 Log.w(TAG, "onDestroy"); 110 } 111 registerListener(Listener listener)112 void registerListener(Listener listener) { 113 mListener = listener; 114 } 115 unregisterListener()116 void unregisterListener() { 117 mListener = null; 118 } 119 120 @Override getNavigationRenderer()121 protected NavigationRenderer getNavigationRenderer() { 122 NavigationRenderer navigationRenderer = new NavigationRenderer() { 123 @Override 124 public CarNavigationInstrumentCluster getNavigationProperties() { 125 Log.i(TAG, "getNavigationProperties"); 126 CarNavigationInstrumentCluster config = 127 CarNavigationInstrumentCluster.createCluster(1000); 128 Log.i(TAG, "getNavigationProperties, returns: " + config); 129 return config; 130 } 131 132 @Override 133 public void onEvent(int eventType, Bundle bundle) { 134 StringBuilder bundleSummary = new StringBuilder(); 135 for (String key : bundle.keySet()) { 136 bundleSummary.append(key); 137 bundleSummary.append("="); 138 bundleSummary.append(bundle.get(key)); 139 bundleSummary.append(" "); 140 } 141 Log.i(TAG, "onEvent(" + eventType + ", " + bundleSummary + ")"); 142 } 143 }; 144 145 Log.i(TAG, "createNavigationRenderer, returns: " + navigationRenderer); 146 return navigationRenderer; 147 } 148 149 class LocalBinder extends Binder { getService()150 SampleClusterServiceImpl getService() { 151 // Return this instance of LocalService so clients can call public methods 152 return SampleClusterServiceImpl.this; 153 } 154 } 155 156 interface Listener { onKeyEvent(KeyEvent event)157 void onKeyEvent(KeyEvent event); 158 } 159 160 @Override dump(FileDescriptor fd, PrintWriter writer, String[] args)161 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 162 if (args != null && args.length > 0) { 163 execShellCommand(args); 164 } else { 165 166 if (args == null || args.length == 0) { 167 writer.println("* dump " + getClass().getCanonicalName() + " *"); 168 writer.println("DisplayProvider: " + mDisplayProvider); 169 } 170 } 171 } 172 emulateKeyEvent(int keyCode)173 private void emulateKeyEvent(int keyCode) { 174 Log.i(TAG, "emulateKeyEvent, keyCode: " + keyCode); 175 long downTime = SystemClock.uptimeMillis(); 176 long eventTime = SystemClock.uptimeMillis(); 177 KeyEvent event = obtainKeyEvent(keyCode, downTime, eventTime, KeyEvent.ACTION_DOWN); 178 onKeyEvent(event); 179 180 eventTime = SystemClock.uptimeMillis(); 181 event = obtainKeyEvent(keyCode, downTime, eventTime, KeyEvent.ACTION_UP); 182 onKeyEvent(event); 183 } 184 obtainKeyEvent(int keyCode, long downTime, long eventTime, int action)185 private KeyEvent obtainKeyEvent(int keyCode, long downTime, long eventTime, int action) { 186 int scanCode = 0; 187 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { 188 scanCode = 108; 189 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 190 scanCode = 106; 191 } 192 return KeyEvent.obtain( 193 downTime, 194 eventTime, 195 action, 196 keyCode, 197 0 /* repeat */, 198 0 /* meta state */, 199 0 /* deviceId*/, 200 scanCode /* scancode */, 201 KeyEvent.FLAG_FROM_SYSTEM /* flags */, 202 InputDevice.SOURCE_KEYBOARD, 203 null /* characters */); 204 } 205 execShellCommand(String[] args)206 private void execShellCommand(String[] args) { 207 Log.i(TAG, "execShellCommand, args: " + Arrays.toString(args)); 208 209 String command = args[0]; 210 211 switch (command) { 212 case "injectKey": { 213 if (args.length > 1) { 214 emulateKeyEvent(parseInt(args[1])); 215 } else { 216 Log.i(TAG, "Not enough arguments"); 217 } 218 break; 219 } 220 case "destroyOverlayDisplay": { 221 Settings.Global.putString(getContentResolver(), 222 Global.OVERLAY_DISPLAY_DEVICES, ""); 223 break; 224 } 225 226 case "createOverlayDisplay": { 227 if (args.length > 1) { 228 Settings.Global.putString(getContentResolver(), 229 Global.OVERLAY_DISPLAY_DEVICES, args[1]); 230 } else { 231 Log.i(TAG, "Not enough arguments, expected 2"); 232 } 233 break; 234 } 235 236 case "setUnobscuredArea": { 237 if (args.length > 5) { 238 Rect unobscuredArea = new Rect(parseInt(args[2]), parseInt(args[3]), 239 parseInt(args[4]), parseInt(args[5])); 240 try { 241 setClusterActivityState(args[1], 242 ClusterActivityState.create(true, unobscuredArea).toBundle()); 243 } catch (CarNotConnectedException e) { 244 Log.i(TAG, "Failed to set activity state.", e); 245 } 246 } else { 247 Log.i(TAG, "wrong format, expected: category left top right bottom"); 248 } 249 } 250 } 251 } 252 253 } 254