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:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 -->
|
||||
<!-- ================================== -->
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user