diff options
| -rw-r--r-- | api/current.txt | 3 | ||||
| -rw-r--r-- | core/java/android/app/ActivityManager.java | 20 | ||||
| -rw-r--r-- | core/res/AndroidManifest.xml | 4 | ||||
| -rw-r--r-- | packages/Shell/AndroidManifest.xml | 2 | ||||
| -rw-r--r-- | services/core/java/com/android/server/PersistentDataBlockManagerInternal.java | 13 | ||||
| -rw-r--r-- | services/core/java/com/android/server/PersistentDataBlockService.java | 137 | ||||
| -rw-r--r-- | services/core/java/com/android/server/testharness/TestHarnessModeService.java | 328 | ||||
| -rw-r--r-- | services/java/com/android/server/SystemServer.java | 5 |
8 files changed, 484 insertions, 28 deletions
diff --git a/api/current.txt b/api/current.txt index 5d6544bdcf7e..ea611145b81e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3941,7 +3941,8 @@ package android.app { 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 e0b8d78ebabc..1045b7aa0890 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -3529,12 +3529,32 @@ public class ActivityManager { /** * 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 ea0c8e250fe2..96b8dc222e7e 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 b903142c44c6..c3c3f25a9f03 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 1e9a00743a8b..190fff1f669c 100644 --- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java +++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java @@ -31,6 +31,19 @@ public interface PersistentDataBlockManagerInternal { */ 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 21093b9f6f88..bd5ad960a886 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.UserHandle; 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 @@ import java.util.concurrent.TimeUnit; * * 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 @@ public class PersistentDataBlockService extends SystemService { 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 @@ public class PersistentDataBlockService extends SystemService { 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 @@ public class PersistentDataBlockService extends SystemService { 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 @@ public class PersistentDataBlockService extends SystemService { 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 @@ public class PersistentDataBlockService extends SystemService { @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 @@ public class PersistentDataBlockService extends SystemService { 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 @@ public class PersistentDataBlockService extends SystemService { } } - @Override - public byte[] getFrpCredentialHandle() { + private byte[] readInternal(long offset, int maxLength) { if (!enforceChecksumValidity()) { throw new IllegalStateException("invalid checksum"); } @@ -652,14 +735,14 @@ public class PersistentDataBlockService extends SystemService { 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 @@ public class PersistentDataBlockService extends SystemService { 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 000000000000..23c042a57ac8 --- /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 cef47caff740..32d7649c5b51 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.stats.StatsCompanionService; 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; @@ -1147,6 +1148,10 @@ public final class SystemServer { traceBeginAndSlog("StartPersistentDataBlock"); mSystemServiceManager.startService(PersistentDataBlockService.class); traceEnd(); + + traceBeginAndSlog("StartTestHarnessMode"); + mSystemServiceManager.startService(TestHarnessModeService.class); + traceEnd(); } if (hasPdb || OemLockService.isHalPresent()) { |