Add inject-key command for CarInputService

- Running: $ adb shell cmd car_service inject-key [-d 1] [-t 2000] 219
- -d : display, 0 for main, 1 for cluster
- -t : delay (ms) between key down and key up events. Making this long like
  2000 can trigger long press operation.
- last digit: key code to inject.
- Triggering assistant action looks like:
  $ adb shell cmd car_service inject-key 219

Bug: 138391974
Test: Run commands and check correct case vs wrong params
  Failing cases:
  adb shell cmd car_service inject-key
  adb shell cmd car_service inject-key -d 2
  adb shell cmd car_service inject-key -d abc
  adb shell cmd car_service inject-key -d 1 -t -1
  adb shell cmd car_service inject-key -d 1 -t 10
  adb shell cmd car_service inject-key 100 200

  Working cases:
  adb shell cmd car_service inject-key 219
  adb shell cmd car_service inject-key -d 1 219
  adb shell cmd car_service inject-key -t 2000 219

Change-Id: Ib00cf4be69bcd1843d1b03aab4e8addda4386e8e
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 6ec1290..893fe3f 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -41,16 +41,19 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserManager;
 import android.util.Log;
 import android.util.Slog;
 import android.util.TimingsTraceLog;
+import android.view.KeyEvent;
 
 import com.android.car.am.FixedActivityService;
 import com.android.car.audio.CarAudioService;
 import com.android.car.cluster.InstrumentClusterService;
 import com.android.car.garagemode.GarageModeService;
+import com.android.car.hal.InputHalService;
 import com.android.car.hal.VehicleHal;
 import com.android.car.pm.CarPackageManagerService;
 import com.android.car.stats.CarStatsService;
@@ -708,6 +711,7 @@
         private static final String COMMAND_STOP_FIXED_ACTIVITY_MODE = "stop-fixed-activity-mode";
         private static final String COMMAND_ENABLE_FEATURE = "enable-feature";
         private static final String COMMAND_DISABLE_FEATURE = "disable-feature";
+        private static final String COMMAND_INJECT_KEY = "inject-key";
 
         private static final String PARAM_DAY_MODE = "day";
         private static final String PARAM_NIGHT_MODE = "night";
@@ -793,6 +797,12 @@
             pw.println("\tdisable-feature featureName");
             pw.println("\t  Disable the requested feature. Change will happen after reboot");
             pw.println("\t  This requires root/su.");
+            pw.println("\tinject-key [-d display] [-t down_delay_ms] key_code");
+            pw.println("\t  inject key down / up event to car service");
+            pw.println("\t  display: 0 for main, 1 for cluster. If not specified, it will be 0.");
+            pw.println("\t  down_delay_ms: delay from down to up key event. If not specified,");
+            pw.println("\t                 it will be 0");
+            pw.println("\t  key_code: int key code defined in android KeyEvent");
         }
 
         private int dumpInvalidArguments(PrintWriter pw) {
@@ -948,6 +958,12 @@
                     }
                     handleEnableDisableFeature(args, writer, /* enable= */ false);
                     break;
+                case COMMAND_INJECT_KEY:
+                    if (args.length < 2) {
+                        return dumpInvalidArguments(writer);
+                    }
+                    handleInjectKey(args, writer);
+                    break;
                 default:
                     writer.println("Unknown command: \"" + arg + "\"");
                     dumpHelp(writer);
@@ -1042,6 +1058,60 @@
             Binder.restoreCallingIdentity(id);
         }
 
+        private void handleInjectKey(String[] args, PrintWriter writer) {
+            int i = 1; // 0 is command itself
+            int display = InputHalService.DISPLAY_MAIN;
+            int delayMs = 0;
+            int keyCode = KeyEvent.KEYCODE_UNKNOWN;
+            try {
+                while (i < args.length) {
+                    switch (args[i]) {
+                        case "-d":
+                            i++;
+                            display = Integer.parseInt(args[i]);
+                            break;
+                        case "-t":
+                            i++;
+                            delayMs = Integer.parseInt(args[i]);
+                            break;
+                        default:
+                            if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+                                throw new IllegalArgumentException("key_code already set:"
+                                        + keyCode);
+                            }
+                            keyCode = Integer.parseInt(args[i]);
+                    }
+                    i++;
+                }
+            } catch (Exception e) {
+                writer.println("Invalid args:" + e);
+                dumpHelp(writer);
+                return;
+            }
+            if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+                writer.println("Missing key code or invalid keycode");
+                dumpHelp(writer);
+                return;
+            }
+            if (display != InputHalService.DISPLAY_MAIN
+                    && display != InputHalService.DISPLAY_INSTRUMENT_CLUSTER) {
+                writer.println("Invalid display:" + display);
+                dumpHelp(writer);
+                return;
+            }
+            if (delayMs < 0) {
+                writer.println("Invalid delay:" + delayMs);
+                dumpHelp(writer);
+                return;
+            }
+            KeyEvent keyDown = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
+            mCarInputService.onKeyEvent(keyDown, display);
+            SystemClock.sleep(delayMs);
+            KeyEvent keyUp = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
+            mCarInputService.onKeyEvent(keyUp, display);
+            writer.println("Succeeded");
+        }
+
         private void forceDayNightMode(String arg, PrintWriter writer) {
             int mode;
             switch (arg) {