Merge "Add notification for Test Harness Mode"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f953208..2b13c4e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3602,6 +3602,11 @@
     <string name="adb_active_notification_message">Tap to turn off USB debugging</string>
     <string name="adb_active_notification_message" product="tv">Select to disable USB debugging.</string>
 
+    <!-- Title of notification shown when Test Harness Mode is enabled. [CHAR LIMIT=NONE] -->
+    <string name="test_harness_mode_notification_title">Test Harness Mode enabled</string>
+    <!-- Message of notification shown when Test Harness Mode is enabled. [CHAR LIMIT=NONE] -->
+    <string name="test_harness_mode_notification_message">Perform a factory reset to disable Test Harness Mode.</string>
+
     <!-- Title of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
     <string name="usb_contaminant_detected_title">Liquid or debris in USB port</string>
     <!-- Message of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6629b4c..59292f6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2060,6 +2060,8 @@
   <java-symbol type="string" name="accessibility_binding_label" />
   <java-symbol type="string" name="adb_active_notification_message" />
   <java-symbol type="string" name="adb_active_notification_title" />
+  <java-symbol type="string" name="test_harness_mode_notification_title" />
+  <java-symbol type="string" name="test_harness_mode_notification_message" />
   <java-symbol type="string" name="taking_remote_bugreport_notification_title" />
   <java-symbol type="string" name="share_remote_bugreport_notification_title" />
   <java-symbol type="string" name="sharing_remote_bugreport_notification_title" />
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 16d8e95..ffbf1ae 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -226,6 +226,10 @@
     // Inform that user that the USB port is free of contaminants.
     NOTE_USB_CONTAMINANT_NOT_DETECTED = 53;
 
+    // Inform the user that Test Harness Mode is active.
+    // Package: android
+    NOTE_TEST_HARNESS_MODE_ENABLED = 54;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index ec62ec7..9f9b797 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -18,6 +18,8 @@
 
 import android.annotation.Nullable;
 import android.app.KeyguardManager;
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -35,6 +37,8 @@
 import android.provider.Settings;
 import android.util.Slog;
 
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.notification.SystemNotificationChannels;
 import com.android.server.LocalServices;
 import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemService;
@@ -87,6 +91,7 @@
             case PHASE_BOOT_COMPLETED:
                 disableAutoSync();
                 configureSettings();
+                showNotification();
                 break;
         }
         super.onBootPhase(phase);
@@ -99,19 +104,29 @@
             // There's no data to apply, so leave it as-is.
             return;
         }
+        PersistentData persistentData;
+        try {
+            persistentData = PersistentData.fromBytes(testHarnessModeData);
+        } catch (SetUpTestHarnessModeException e) {
+            Slog.e(TAG, "Failed to set up Test Harness Mode. Bad data.", e);
+            return;
+        } finally {
+            // Clear out the Test Harness Mode data. It's now in memory if successful or we should
+            // skip setting up.
+            getPersistentDataBlock().clearTestHarnessModeData();
+        }
         mShouldSetUpTestHarnessMode = true;
-        setUpAdb(testHarnessModeData);
+        setUpAdb(persistentData);
         setDeviceProvisioned();
     }
 
-    private void setUpAdb(byte[] testHarnessModeData) {
+    private void setUpAdb(PersistentData persistentData) {
         ContentResolver cr = getContext().getContentResolver();
+
         // Disable the TTL for ADB keys before enabling ADB
         Settings.Global.putLong(cr, Settings.Global.ADB_ALLOWED_CONNECTION_TIME, 0);
 
-        PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData);
-
-        SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0");
+        SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, "1");
         writeAdbKeysFile(persistentData);
     }
 
@@ -145,9 +160,6 @@
 
         writeBytesToFile(persistentData.mAdbKeys, adbManager.getAdbKeysFile().toPath());
         writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath());
-
-        // Clear out the data block so that we don't revert the ADB keys on every boot.
-        getPersistentDataBlock().clearTestHarnessModeData();
     }
 
     private void writeBytesToFile(byte[] keys, Path adbKeys) {
@@ -177,6 +189,36 @@
                 UserHandle.USER_CURRENT);
     }
 
+    private void showNotification() {
+        if (!SystemProperties.getBoolean(TEST_HARNESS_MODE_PROPERTY, false)) {
+            return;
+        }
+        String title = getContext()
+                .getString(com.android.internal.R.string.test_harness_mode_notification_title);
+        String message = getContext()
+                .getString(com.android.internal.R.string.test_harness_mode_notification_message);
+
+        Notification notification =
+                new Notification.Builder(getContext(), SystemNotificationChannels.DEVELOPER)
+                        .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+                        .setWhen(0)
+                        .setOngoing(true)
+                        .setTicker(title)
+                        .setDefaults(0)  // please be quiet
+                        .setColor(getContext().getColor(
+                                com.android.internal.R.color
+                                        .system_notification_accent_color))
+                        .setContentTitle(title)
+                        .setContentText(message)
+                        .setVisibility(Notification.VISIBILITY_PUBLIC)
+                        .build();
+
+        NotificationManager notificationManager =
+                getContext().getSystemService(NotificationManager.class);
+        notificationManager.notifyAsUser(
+                null, SystemMessage.NOTE_TEST_HARNESS_MODE_ENABLED, notification, UserHandle.ALL);
+    }
+
     @Nullable
     private PersistentDataBlockManagerInternal getPersistentDataBlock() {
         if (mPersistentDataBlockManagerInternal == null) {
@@ -248,8 +290,7 @@
                 byte[] adbKeysBytes = getBytesFromFile(adbKeys);
                 byte[] adbTempKeysBytes = getBytesFromFile(adbTempKeys);
 
-                PersistentData persistentData =
-                        new PersistentData(true, adbKeysBytes, adbTempKeysBytes);
+                PersistentData persistentData = new PersistentData(adbKeysBytes, adbTempKeysBytes);
                 getPersistentDataBlock().setTestHarnessModeData(persistentData.toBytes());
             } catch (IOException e) {
                 Slog.e(TAG, "Failed to store ADB keys.", e);
@@ -316,37 +357,40 @@
      */
     public static class PersistentData {
         static final byte VERSION_1 = 1;
+        static final byte VERSION_2 = 2;
 
         final int mVersion;
-        final boolean mEnabled;
         final byte[] mAdbKeys;
         final byte[] mAdbTempKeys;
 
-        PersistentData(boolean enabled, byte[] adbKeys, byte[] adbTempKeys) {
-            this(VERSION_1, enabled, adbKeys, adbTempKeys);
+        PersistentData(byte[] adbKeys, byte[] adbTempKeys) {
+            this(VERSION_2, adbKeys, adbTempKeys);
         }
 
-        PersistentData(int version, boolean enabled, byte[] adbKeys, byte[] adbTempKeys) {
+        PersistentData(int version, byte[] adbKeys, byte[] adbTempKeys) {
             this.mVersion = version;
-            this.mEnabled = enabled;
             this.mAdbKeys = adbKeys;
             this.mAdbTempKeys = adbTempKeys;
         }
 
-        static PersistentData fromBytes(byte[] bytes) {
+        static PersistentData fromBytes(byte[] bytes) throws SetUpTestHarnessModeException {
             try {
                 DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
                 int version = is.readInt();
-                boolean enabled = is.readBoolean();
+                if (version == VERSION_1) {
+                    // Version 1 of Test Harness Mode contained an "enabled" bit that we need to
+                    // skip. If we don't, the binary format will be bad and it will fail to set up.
+                    is.readBoolean();
+                }
                 int adbKeysLength = is.readInt();
                 byte[] adbKeys = new byte[adbKeysLength];
                 is.readFully(adbKeys);
                 int adbTempKeysLength = is.readInt();
                 byte[] adbTempKeys = new byte[adbTempKeysLength];
                 is.readFully(adbTempKeys);
-                return new PersistentData(version, enabled, adbKeys, adbTempKeys);
+                return new PersistentData(version, adbKeys, adbTempKeys);
             } catch (IOException e) {
-                throw new RuntimeException(e);
+                throw new SetUpTestHarnessModeException(e);
             }
         }
 
@@ -354,8 +398,7 @@
             try {
                 ByteArrayOutputStream os = new ByteArrayOutputStream();
                 DataOutputStream dos = new DataOutputStream(os);
-                dos.writeInt(VERSION_1);
-                dos.writeBoolean(mEnabled);
+                dos.writeInt(VERSION_2);
                 dos.writeInt(mAdbKeys.length);
                 dos.write(mAdbKeys);
                 dos.writeInt(mAdbTempKeys.length);
@@ -367,4 +410,18 @@
             }
         }
     }
+
+    /**
+     * An exception thrown when Test Harness Mode fails to set up.
+     *
+     * <p>In the event that Test Harness Mode fails to set up, all of the data should be discarded
+     * and the Test Harness Mode portion of the persistent data block should be wiped. This will
+     * prevent the device from becoming stuck, as there is no way (without rooting the device) to
+     * clear the persistent data block.
+     */
+    private static class SetUpTestHarnessModeException extends Exception {
+        SetUpTestHarnessModeException(Exception e) {
+            super(e);
+        }
+    }
 }