Implement test harness mode

Test Harness Mode is a feature for device farms that want to wipe
their devices after each test run. It stores the ADB keys in the
persistent partition (used for Factory Reset Protection) then performs
a factory reset by broadcasting the MASTER_CLEAR intent.

Upon rebooting, the Setup Wizard is skipped, and a few settings are set:
  * Package Verifier is disabled
  * Stay Awake While Charging is enabled
  * OTA Updates are disabled
  * Auto-Sync for accounts is disabled

Other apps may configure themselves differently in Test Harness Mode by
checking ActivityManager.isRunningInUserTestHarness()

Bug: 80137798
Test: make && fastboot flashall -w
Test: adb shell cmd testharness enable
Change-Id: I91285c056666e36ad0caf778bffc140a0656fcfa
This commit is contained in:
William Hester
2018-12-19 13:14:51 -08:00
parent 4e9df81cd3
commit a96d3d3ff4
8 changed files with 484 additions and 28 deletions

View File

@@ -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);

View File

@@ -3529,11 +3529,31 @@ 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

View File

@@ -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 -->
<!-- ================================== -->

View File

@@ -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" />

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}
}

View File

@@ -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()) {