Merge "Implement test harness mode"
diff --git a/api/current.txt b/api/current.txt
index 1520a01..491f677 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3941,7 +3941,8 @@
     method public boolean isBackgroundRestricted();
     method @Deprecated public boolean isInLockTaskMode();
     method public boolean isLowRamDevice();
-    method public static boolean isRunningInTestHarness();
+    method @Deprecated public static boolean isRunningInTestHarness();
+    method public static boolean isRunningInUserTestHarness();
     method public static boolean isUserAMonkey();
     method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String);
     method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e0b8d78..1045b7a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3529,12 +3529,32 @@
 
     /**
      * Returns "true" if device is running in a test harness.
+     *
+     * @deprecated this method is false for all user builds. Users looking to check if their device
+     * is running in a device farm should see {@link #isRunningInUserTestHarness()}.
      */
+    @Deprecated
     public static boolean isRunningInTestHarness() {
         return SystemProperties.getBoolean("ro.test_harness", false);
     }
 
     /**
+     * Returns "true" if the device is running in Test Harness Mode.
+     *
+     * <p>Test Harness Mode is a feature that allows devices to run without human interaction in a
+     * device farm/testing harness (such as Firebase Test Lab). You should check this method if you
+     * want your app to behave differently when running in a test harness to skip setup screens that
+     * would impede UI testing. e.g. a keyboard application that has a full screen setup page for
+     * the first time it is launched.
+     *
+     * <p>Note that you should <em>not</em> use this to determine whether or not your app is running
+     * an instrumentation test, as it is not set for a standard device running a test.
+     */
+    public static boolean isRunningInUserTestHarness() {
+        return SystemProperties.getBoolean("persist.sys.test_harness", false);
+    }
+
+    /**
      * Unsupported compiled sdk warning should always be shown for the intput activity
      * even in cases where the system would normally not show the warning. E.g. when running in a
      * test harness.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ea0c8e2..96b8dc2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1748,6 +1748,10 @@
     <permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED"
         android:protectionLevel="signature" />
 
+    <!-- @hide Allows the device to be reset, clearing all data and enables Test Harness Mode. -->
+    <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE"
+        android:protectionLevel="signature" />
+
     <!-- ================================== -->
     <!-- Permissions for accessing accounts -->
     <!-- ================================== -->
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b903142..c3c3f25 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -166,6 +166,8 @@
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
     <uses-permission android:name="android.permission.SUSPEND_APPS" />
     <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+    <!-- Permission needed to wipe the device for Test Harness Mode -->
+    <uses-permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" />
 
     <uses-permission android:name="android.permission.MANAGE_APPOPS" />
 
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
index 1e9a007..190fff1 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -31,6 +31,19 @@
      */
     byte[] getFrpCredentialHandle();
 
+    /** Stores the data used to enable the Test Harness Mode after factory-resetting. */
+    void setTestHarnessModeData(byte[] data);
+
+    /**
+     * Retrieves the data used to place the device into Test Harness Mode.
+     *
+     * @throws IllegalStateException if the underlying storage is corrupt or inaccessible.
+     */
+    byte[] getTestHarnessModeData();
+
+    /** Clear out the Test Harness Mode data. */
+    void clearTestHarnessModeData();
+
     /** Update the OEM unlock enabled bit, bypassing user restriction checks. */
     void forceOemUnlockEnabled(boolean enabled);
 }
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 21093b9..bd5ad96 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import android.Manifest;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -28,12 +30,10 @@
 import android.os.UserManager;
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
 
 import libcore.io.IoUtils;
 
@@ -65,6 +65,40 @@
  *
  * Clients can read any number of bytes from the currently written block up to its total size by
  * invoking {@link IPersistentDataBlockService#read}
+ *
+ * The persistent data block is currently laid out as follows:
+ * | ---------BEGINNING OF PARTITION-------------|
+ * | Partition digest (32 bytes)                 |
+ * | --------------------------------------------|
+ * | PARTITION_TYPE_MARKER (4 bytes)             |
+ * | --------------------------------------------|
+ * | FRP data block length (4 bytes)             |
+ * | --------------------------------------------|
+ * | FRP data (variable length)                  |
+ * | --------------------------------------------|
+ * | ...                                         |
+ * | --------------------------------------------|
+ * | Test mode data block (10000 bytes)          |
+ * | --------------------------------------------|
+ * |     | Test mode data length (4 bytes)       |
+ * | --------------------------------------------|
+ * |     | Test mode data (variable length)      |
+ * |     | ...                                   |
+ * | --------------------------------------------|
+ * | FRP credential handle block (1000 bytes)    |
+ * | --------------------------------------------|
+ * |     | FRP credential handle length (4 bytes)|
+ * | --------------------------------------------|
+ * |     | FRP credential handle (variable len)  |
+ * |     | ...                                   |
+ * | --------------------------------------------|
+ * | OEM Unlock bit (1 byte)                     |
+ * | ---------END OF PARTITION-------------------|
+ *
+ * TODO: now that the persistent partition contains several blocks, next time someone wants a new
+ * block, we should look at adding more generic block definitions and get rid of the various raw
+ * XXX_RESERVED_SIZE and XXX_DATA_SIZE constants. That will ensure the code is easier to maintain
+ * and less likely to introduce out-of-bounds read/write.
  */
 public class PersistentDataBlockService extends SystemService {
     private static final String TAG = PersistentDataBlockService.class.getSimpleName();
@@ -73,10 +107,16 @@
     private static final int HEADER_SIZE = 8;
     // Magic number to mark block device as adhering to the format consumed by this service
     private static final int PARTITION_TYPE_MARKER = 0x19901873;
-    /** Size of the block reserved for FPR credential, including 4 bytes for the size header. */
+    /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */
     private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000;
     /** Maximum size of the FRP credential handle that can be stored. */
     private static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4;
+    /**
+     * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header.
+     */
+    private static final int TEST_MODE_RESERVED_SIZE = 10000;
+    /** Maximum size of the Test Harness Mode data that can be stored. */
+    private static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4;
     // Limit to 100k as blocks larger than this might cause strain on Binder.
     private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
 
@@ -221,6 +261,14 @@
         return mBlockDeviceSize;
     }
 
+    private long getFrpCredentialDataOffset() {
+        return getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE;
+    }
+
+    private long getTestHarnessModeDataOffset() {
+        return getFrpCredentialDataOffset() - TEST_MODE_RESERVED_SIZE;
+    }
+
     private boolean enforceChecksumValidity() {
         byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
 
@@ -383,7 +431,7 @@
 
     private long doGetMaximumDataBlockSize() {
         long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES
-                - FRP_CREDENTIAL_RESERVED_SIZE - 1;
+                - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1;
         return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
     }
 
@@ -391,6 +439,13 @@
     private native int nativeWipe(String path);
 
     private final IBinder mService = new IPersistentDataBlockService.Stub() {
+
+        /**
+         * Write the data to the persistent data block.
+         *
+         * @return a positive integer of the number of bytes that were written if successful,
+         * otherwise a negative integer indicating there was a problem
+         */
         @Override
         public int write(byte[] data) throws RemoteException {
             enforceUid(Binder.getCallingUid());
@@ -597,12 +652,51 @@
 
         @Override
         public void setFrpCredentialHandle(byte[] handle) {
-            Preconditions.checkArgument(handle == null || handle.length > 0,
-                    "handle must be null or non-empty");
-            Preconditions.checkArgument(handle == null
-                            || handle.length <= MAX_FRP_CREDENTIAL_HANDLE_SIZE,
-                    "handle must not be longer than " + MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+            writeInternal(handle, getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+        }
 
+        @Override
+        public byte[] getFrpCredentialHandle() {
+            return readInternal(getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+        }
+
+        @Override
+        public void setTestHarnessModeData(byte[] data) {
+            writeInternal(data, getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE);
+        }
+
+        @Override
+        public byte[] getTestHarnessModeData() {
+            byte[] data = readInternal(getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE);
+            if (data == null) {
+                return new byte[0];
+            }
+            return data;
+        }
+
+        @Override
+        public void clearTestHarnessModeData() {
+            int size = Math.min(MAX_TEST_MODE_DATA_SIZE, getTestHarnessModeData().length) + 4;
+            writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size));
+        }
+
+        private void writeInternal(byte[] data, long offset, int dataLength) {
+            checkArgument(data == null || data.length > 0, "data must be null or non-empty");
+            checkArgument(
+                    data == null || data.length <= dataLength,
+                    "data must not be longer than " + dataLength);
+
+            ByteBuffer dataBuffer = ByteBuffer.allocate(dataLength + 4);
+            dataBuffer.putInt(data == null ? 0 : data.length);
+            if (data != null) {
+                dataBuffer.put(data);
+            }
+            dataBuffer.flip();
+
+            writeDataBuffer(offset, dataBuffer);
+        }
+
+        private void writeDataBuffer(long offset, ByteBuffer dataBuffer) {
             FileOutputStream outputStream;
             try {
                 outputStream = new FileOutputStream(new File(mDataBlockFile));
@@ -610,25 +704,15 @@
                 Slog.e(TAG, "partition not available", e);
                 return;
             }
-
-            ByteBuffer data = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE);
-            data.putInt(handle == null ? 0 : handle.length);
-            if (handle != null) {
-                data.put(handle);
-            }
-            data.flip();
-
             synchronized (mLock) {
                 if (!mIsWritable) {
                     IoUtils.closeQuietly(outputStream);
                     return;
                 }
-
                 try {
                     FileChannel channel = outputStream.getChannel();
-
-                    channel.position(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
-                    channel.write(data);
+                    channel.position(offset);
+                    channel.write(dataBuffer);
                     outputStream.flush();
                 } catch (IOException e) {
                     Slog.e(TAG, "unable to access persistent partition", e);
@@ -641,8 +725,7 @@
             }
         }
 
-        @Override
-        public byte[] getFrpCredentialHandle() {
+        private byte[] readInternal(long offset, int maxLength) {
             if (!enforceChecksumValidity()) {
                 throw new IllegalStateException("invalid checksum");
             }
@@ -652,14 +735,14 @@
                 inputStream = new DataInputStream(
                         new FileInputStream(new File(mDataBlockFile)));
             } catch (FileNotFoundException e) {
-                throw new IllegalStateException("frp partition not available");
+                throw new IllegalStateException("persistent partition not available");
             }
 
             try {
                 synchronized (mLock) {
-                    inputStream.skip(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
+                    inputStream.skip(offset);
                     int length = inputStream.readInt();
-                    if (length <= 0 || length > MAX_FRP_CREDENTIAL_HANDLE_SIZE) {
+                    if (length <= 0 || length > maxLength) {
                         return null;
                     }
                     byte[] bytes = new byte[length];
@@ -667,7 +750,7 @@
                     return bytes;
                 }
             } catch (IOException e) {
-                throw new IllegalStateException("frp handle not readable", e);
+                throw new IllegalStateException("persistent partition not readable", e);
             } finally {
                 IoUtils.closeQuietly(inputStream);
             }
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
new file mode 100644
index 0000000..23c042a
--- /dev/null
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.testharness;
+
+import android.annotation.Nullable;
+import android.app.KeyguardManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.BatteryManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.SystemService;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Set;
+
+/**
+ * Manages the Test Harness Mode service for setting up test harness mode on the device.
+ *
+ * <p>Test Harness Mode is a feature that allows the user to clean their device, retain ADB keys,
+ * and provision the device for Instrumentation testing. This means that all parts of the device
+ * that would otherwise interfere with testing (auto-syncing accounts, package verification,
+ * automatic updates, etc.) are all disabled by default but may be re-enabled by the user.
+ */
+public class TestHarnessModeService extends SystemService {
+    private static final String TAG = TestHarnessModeService.class.getSimpleName();
+    private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness";
+
+    private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
+
+    public TestHarnessModeService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService("testharness", mService);
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        switch (phase) {
+            case PHASE_SYSTEM_SERVICES_READY:
+                setUpTestHarnessMode();
+                break;
+            case PHASE_BOOT_COMPLETED:
+                disableAutoSync();
+                break;
+        }
+        super.onBootPhase(phase);
+    }
+
+    private void setUpTestHarnessMode() {
+        Slog.d(TAG, "Setting up test harness mode");
+        byte[] testHarnessModeData = getPersistentDataBlock().getTestHarnessModeData();
+        if (testHarnessModeData == null || testHarnessModeData.length == 0) {
+            // There's no data to apply, so leave it as-is.
+            return;
+        }
+        PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData);
+
+        SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0");
+        writeAdbKeysFile(persistentData);
+        // Clear out the data block so that we don't revert the ADB keys on every boot.
+        getPersistentDataBlock().clearTestHarnessModeData();
+
+        ContentResolver cr = getContext().getContentResolver();
+        if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) {
+            // Enable ADB
+            Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1);
+        } else {
+            // ADB is already enabled, we should restart the service so it picks up the new keys
+            android.os.SystemService.restart("adbd");
+        }
+
+        Settings.Global.putInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 0);
+        Settings.Global.putInt(
+                cr,
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
+                BatteryManager.BATTERY_PLUGGED_ANY);
+        Settings.Global.putInt(cr, Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, 1);
+        Settings.Global.putInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+
+        setDeviceProvisioned();
+    }
+
+    private void disableAutoSync() {
+        UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
+        ContentResolver
+            .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier());
+    }
+
+    private void writeAdbKeysFile(PersistentData persistentData) {
+        Path adbKeys = Paths.get("/data/misc/adb/adb_keys");
+        try {
+            OutputStream fileOutputStream = Files.newOutputStream(adbKeys);
+            fileOutputStream.write(persistentData.mAdbKeys);
+            fileOutputStream.close();
+
+            Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(adbKeys);
+            permissions.add(PosixFilePermission.GROUP_READ);
+            Files.setPosixFilePermissions(adbKeys, permissions);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to set up adb keys", e);
+            // Note: if a device enters this block, it will remain UNAUTHORIZED in ADB, but all
+            // other settings will be set up.
+        }
+    }
+
+    // Setting the device as provisioned skips the setup wizard.
+    private void setDeviceProvisioned() {
+        ContentResolver cr = getContext().getContentResolver();
+        Settings.Global.putInt(cr, Settings.Global.DEVICE_PROVISIONED, 1);
+        Settings.Secure.putIntForUser(
+                cr,
+                Settings.Secure.USER_SETUP_COMPLETE,
+                1,
+                UserHandle.USER_CURRENT);
+    }
+
+    @Nullable
+    private PersistentDataBlockManagerInternal getPersistentDataBlock() {
+        if (mPersistentDataBlockManagerInternal == null) {
+            Slog.d(TAG, "Getting PersistentDataBlockManagerInternal from LocalServices");
+            mPersistentDataBlockManagerInternal =
+                    LocalServices.getService(PersistentDataBlockManagerInternal.class);
+        }
+        return mPersistentDataBlockManagerInternal;
+    }
+
+    private final IBinder mService = new Binder() {
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+            (new TestHarnessModeShellCommand())
+                .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+    };
+
+    private class TestHarnessModeShellCommand extends ShellCommand {
+        @Override
+        public int onCommand(String cmd) {
+            switch (cmd) {
+                case "enable":
+                case "restore":
+                    checkPermissions();
+                    final long originalId = Binder.clearCallingIdentity();
+                    try {
+                        if (isDeviceSecure()) {
+                            getErrPrintWriter().println(
+                                    "Test Harness Mode cannot be enabled if there is a lock "
+                                            + "screen");
+                            return 2;
+                        }
+                        return handleEnable();
+                    } finally {
+                        Binder.restoreCallingIdentity(originalId);
+                    }
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        }
+
+        private void checkPermissions() {
+            getContext().enforceCallingPermission(
+                    android.Manifest.permission.ENABLE_TEST_HARNESS_MODE,
+                    "You must hold android.permission.ENABLE_TEST_HARNESS_MODE "
+                            + "to enable Test Harness Mode");
+        }
+
+        private boolean isDeviceSecure() {
+            UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
+            KeyguardManager keyguardManager = getContext().getSystemService(KeyguardManager.class);
+            return keyguardManager.isDeviceSecure(primaryUser.id);
+        }
+
+        private int handleEnable() {
+            Path adbKeys = Paths.get("/data/misc/adb/adb_keys");
+            if (!Files.exists(adbKeys)) {
+                // This should only be accessible on eng builds that haven't yet set up ADB keys
+                getErrPrintWriter()
+                    .println("No ADB keys stored; not enabling test harness mode");
+                return 1;
+            }
+
+            try (InputStream inputStream = Files.newInputStream(adbKeys)) {
+                long size = Files.size(adbKeys);
+                byte[] adbKeysBytes = new byte[(int) size];
+                int numBytes = inputStream.read(adbKeysBytes);
+                if (numBytes != size) {
+                    getErrPrintWriter().println("Failed to read all bytes of adb_keys");
+                    return 1;
+                }
+                PersistentData persistentData = new PersistentData(true, adbKeysBytes);
+                getPersistentDataBlock().setTestHarnessModeData(persistentData.toBytes());
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to store ADB keys.", e);
+                getErrPrintWriter().println("Failed to enable Test Harness Mode");
+                return 1;
+            }
+
+            Intent i = new Intent(Intent.ACTION_FACTORY_RESET);
+            i.setPackage("android");
+            i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            i.putExtra(Intent.EXTRA_REASON, TAG);
+            i.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true);
+            getContext().sendBroadcastAsUser(i, UserHandle.SYSTEM);
+            return 0;
+        }
+
+        @Override
+        public void onHelp() {
+            PrintWriter pw = getOutPrintWriter();
+            pw.println("About:");
+            pw.println("  Test Harness Mode is a mode that the device can be placed in to prepare");
+            pw.println("  the device for running UI tests. The device is placed into this mode by");
+            pw.println("  first wiping all data from the device, preserving ADB keys.");
+            pw.println();
+            pw.println("  By default, the following settings are configured:");
+            pw.println("    * Package Verifier is disabled");
+            pw.println("    * Stay Awake While Charging is enabled");
+            pw.println("    * OTA Updates are disabled");
+            pw.println("    * Auto-Sync for accounts is disabled");
+            pw.println();
+            pw.println("  Other apps may configure themselves differently in Test Harness Mode by");
+            pw.println("  checking ActivityManager.isRunningInUserTestHarness()");
+            pw.println();
+            pw.println("Test Harness Mode commands:");
+            pw.println("  help");
+            pw.println("    Print this help text.");
+            pw.println();
+            pw.println("  enable|restore");
+            pw.println("    Erase all data from this device and enable Test Harness Mode,");
+            pw.println("    preserving the stored ADB keys currently on the device and toggling");
+            pw.println("    settings in a way that are conducive to Instrumentation testing.");
+        }
+    }
+
+    /**
+     * The object that will serialize/deserialize the Test Harness Mode data to and from the
+     * persistent data block.
+     */
+    public static class PersistentData {
+        static final byte VERSION_1 = 1;
+
+        final int mVersion;
+        final boolean mEnabled;
+        final byte[] mAdbKeys;
+
+        PersistentData(boolean enabled, byte[] adbKeys) {
+            this(VERSION_1, enabled, adbKeys);
+        }
+
+        PersistentData(int version, boolean enabled, byte[] adbKeys) {
+            this.mVersion = version;
+            this.mEnabled = enabled;
+            this.mAdbKeys = adbKeys;
+        }
+
+        static PersistentData fromBytes(byte[] bytes) {
+            try {
+                DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
+                int version = is.readInt();
+                boolean enabled = is.readBoolean();
+                int adbKeysLength = is.readInt();
+                byte[] adbKeys = new byte[adbKeysLength];
+                is.readFully(adbKeys);
+                return new PersistentData(version, enabled, adbKeys);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        byte[] toBytes() {
+            try {
+                ByteArrayOutputStream os = new ByteArrayOutputStream();
+                DataOutputStream dos = new DataOutputStream(os);
+                dos.writeInt(VERSION_1);
+                dos.writeBoolean(mEnabled);
+                dos.writeInt(mAdbKeys.length);
+                dos.write(mAdbKeys);
+                dos.close();
+                return os.toByteArray();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 98385c9..623990b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -136,6 +136,7 @@
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
 import com.android.server.telecom.TelecomLoaderService;
+import com.android.server.testharness.TestHarnessModeService;
 import com.android.server.textclassifier.TextClassificationManagerService;
 import com.android.server.textservices.TextServicesManagerService;
 import com.android.server.trust.TrustManagerService;
@@ -1157,6 +1158,10 @@
                 traceBeginAndSlog("StartPersistentDataBlock");
                 mSystemServiceManager.startService(PersistentDataBlockService.class);
                 traceEnd();
+
+                traceBeginAndSlog("StartTestHarnessMode");
+                mSystemServiceManager.startService(TestHarnessModeService.class);
+                traceEnd();
             }
 
             if (hasPdb || OemLockService.isHalPresent()) {