Merge "Fix original-package support" into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-06-10 20:51:39 +00:00
committed by Android (Google) Code Review
17 changed files with 754 additions and 153 deletions

View File

@@ -19,6 +19,9 @@ java_test_host {
"tradefed",
"junit",
],
static_libs: [
"frameworks-base-hostutils",
],
test_suites: ["general-tests"],
java_resources: [
":com.android.overlaytest.overlaid",

View File

@@ -18,6 +18,7 @@ package com.android.overlaytest.remounted;
import static org.junit.Assert.fail;
import com.android.internal.util.test.SystemPreparer;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;

View File

@@ -1,151 +0,0 @@
/*
* 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.overlaytest.remounted;
import static org.junit.Assert.assertTrue;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import org.junit.Assert;
import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
class SystemPreparer extends ExternalResource {
private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000;
// The paths of the files pushed onto the device through this rule.
private ArrayList<String> mPushedFiles = new ArrayList<>();
// The package names of packages installed through this rule.
private ArrayList<String> mInstalledPackages = new ArrayList<>();
private final TemporaryFolder mHostTempFolder;
private final DeviceProvider mDeviceProvider;
SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) {
mHostTempFolder = hostTempFolder;
mDeviceProvider = deviceProvider;
}
/** Copies a file within the host test jar to a path on device. */
SystemPreparer pushResourceFile(String resourcePath,
String outputPath) throws DeviceNotAvailableException, IOException {
final ITestDevice device = mDeviceProvider.getDevice();
remount();
assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath));
mPushedFiles.add(outputPath);
return this;
}
/** Installs an APK within the host test jar onto the device. */
SystemPreparer installResourceApk(String resourcePath, String packageName)
throws DeviceNotAvailableException, IOException {
final ITestDevice device = mDeviceProvider.getDevice();
final File tmpFile = copyResourceToTemp(resourcePath);
final String result = device.installPackage(tmpFile, true /* reinstall */);
Assert.assertNull(result);
mInstalledPackages.add(packageName);
return this;
}
/** Sets the enable state of an overlay package. */
SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
throws DeviceNotAvailableException {
final ITestDevice device = mDeviceProvider.getDevice();
final String enable = enabled ? "enable" : "disable";
// Wait for the overlay to change its enabled state.
final long endMillis = System.currentTimeMillis() + OVERLAY_ENABLE_TIMEOUT_MS;
String result;
while (System.currentTimeMillis() <= endMillis) {
device.executeShellCommand(String.format("cmd overlay %s %s", enable, packageName));
result = device.executeShellCommand("cmd overlay dump isenabled "
+ packageName);
if (((enabled) ? "true\n" : "false\n").equals(result)) {
return this;
}
try {
Thread.sleep(200);
} catch (InterruptedException ignore) {
}
}
throw new IllegalStateException(String.format("Failed to %s overlay %s:\n%s", enable,
packageName, device.executeShellCommand("cmd overlay list")));
}
/** Restarts the device and waits until after boot is completed. */
SystemPreparer reboot() throws DeviceNotAvailableException {
final ITestDevice device = mDeviceProvider.getDevice();
device.reboot();
return this;
}
SystemPreparer remount() throws DeviceNotAvailableException {
mDeviceProvider.getDevice().executeAdbCommand("remount");
return this;
}
/** Copies a file within the host test jar to a temporary file on the host machine. */
private File copyResourceToTemp(String resourcePath) throws IOException {
final File tempFile = mHostTempFolder.newFile(resourcePath);
final ClassLoader classLoader = getClass().getClassLoader();
try (InputStream assetIs = classLoader.getResource(resourcePath).openStream();
FileOutputStream assetOs = new FileOutputStream(tempFile)) {
if (assetIs == null) {
throw new IllegalStateException("Failed to find resource " + resourcePath);
}
int b;
while ((b = assetIs.read()) >= 0) {
assetOs.write(b);
}
}
return tempFile;
}
/** Removes installed packages and files that were pushed to the device. */
@Override
protected void after() {
final ITestDevice device = mDeviceProvider.getDevice();
try {
remount();
for (final String file : mPushedFiles) {
device.deleteFile(file);
}
for (final String packageName : mInstalledPackages) {
device.uninstallPackage(packageName);
}
device.reboot();
} catch (DeviceNotAvailableException e) {
Assert.fail(e.toString());
}
}
interface DeviceProvider {
ITestDevice getDevice();
}
}

View File

@@ -11067,7 +11067,7 @@ public class PackageManagerService extends IPackageManager.Stub
} else {
pkgSetting = result.pkgSetting;
if (originalPkgSetting != null) {
mSettings.addRenamedPackageLPw(parsedPackage.getPackageName(),
mSettings.addRenamedPackageLPw(parsedPackage.getRealPackage(),
originalPkgSetting.name);
mTransferredPackages.add(originalPkgSetting.name);
}
@@ -11176,7 +11176,7 @@ public class PackageManagerService extends IPackageManager.Stub
@GuardedBy("mLock")
private @Nullable PackageSetting getOriginalPackageLocked(@NonNull AndroidPackage pkg,
@Nullable String renamedPkgName) {
if (!isPackageRenamed(pkg, renamedPkgName)) {
if (isPackageRenamed(pkg, renamedPkgName)) {
return null;
}
for (int i = ArrayUtils.size(pkg.getOriginalPackages()) - 1; i >= 0; --i) {

View File

@@ -69,6 +69,9 @@
"exclude-annotation": "androidx.test.filters.Suppress"
}
]
},
{
"name": "PackageManagerServiceHostTests"
}
],
"postsubmit": [

View File

@@ -0,0 +1,33 @@
// Copyright (C) 2020 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.
java_test_host {
name: "PackageManagerServiceHostTests",
srcs: ["src/**/*.kt"],
libs: [
"tradefed",
"junit",
"truth-prebuilt",
],
static_libs: [
"frameworks-base-hostutils",
],
test_suites: ["general-tests"],
java_resources: [
":PackageManagerDummyAppVersion1",
":PackageManagerDummyAppVersion2",
":PackageManagerDummyAppVersion3",
":PackageManagerDummyAppOriginalOverride",
]
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<configuration description="Test module config for PackageManagerServiceHostTests">
<option name="test-tag" value="PackageManagerServiceHostTests" />
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<test class="com.android.tradefed.testtype.HostTest">
<option name="jar" value="PackageManagerServiceHostTests.jar" />
</test>
</configuration>

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2020 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.pm.test
import com.android.internal.util.test.SystemPreparer
import com.android.tradefed.device.ITestDevice
import java.io.File
internal fun SystemPreparer.pushApk(file: String, partition: Partition) =
pushResourceFile(file, HostUtils.makePathForApk(file, partition))
internal fun SystemPreparer.deleteApk(file: String, partition: Partition) =
deleteFile(partition.baseFolder.resolve(file.removeSuffix(".apk")).toString())
internal object HostUtils {
fun getDataDir(device: ITestDevice, pkgName: String) =
device.executeShellCommand("dumpsys package $pkgName")
.lineSequence()
.map(String::trim)
.single { it.startsWith("dataDir=") }
.removePrefix("dataDir=")
fun makePathForApk(fileName: String, partition: Partition) =
makePathForApk(File(fileName), partition)
fun makePathForApk(file: File, partition: Partition) =
partition.baseFolder
.resolve(file.nameWithoutExtension)
.resolve(file.name)
.toString()
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2020 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.pm.test
import com.android.internal.util.test.SystemPreparer
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
import com.google.common.truth.Truth.assertThat
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
@RunWith(DeviceJUnit4ClassRunner::class)
class OriginalPackageMigrationTest : BaseHostJUnit4Test() {
companion object {
private const val TEST_PKG_NAME = "com.android.server.pm.test.dummy_app"
private const val VERSION_ONE = "PackageManagerDummyAppVersion1.apk"
private const val VERSION_TWO = "PackageManagerDummyAppVersion2.apk"
private const val VERSION_THREE = "PackageManagerDummyAppVersion3.apk"
private const val NEW_PKG = "PackageManagerDummyAppOriginalOverride.apk"
@get:ClassRule
val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
}
private val tempFolder = TemporaryFolder()
private val preparer: SystemPreparer = SystemPreparer(tempFolder,
SystemPreparer.RebootStrategy.START_STOP, deviceRebootRule) { this.device }
@get:Rule
val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
@Test
fun lowerVersion() {
runForApk(VERSION_ONE)
}
@Test
fun sameVersion() {
runForApk(VERSION_TWO)
}
@Test
fun higherVersion() {
runForApk(VERSION_THREE)
}
// A bug was found where renamed the package during parsing was leading to an invalid version
// code check at scan time. A lower version package was being dropped after reboot. To test
// this, the override APK is defined as versionCode 2 and the original package is given
// versionCode 1, 2, and 3 from the other methods.
private fun runForApk(apk: String) {
preparer.pushApk(apk, Partition.SYSTEM)
.reboot()
device.getAppPackageInfo(TEST_PKG_NAME).run {
assertThat(codePath).contains(apk.removeSuffix(".apk"))
}
// Ensure data is preserved by writing to the original dataDir
val file = tempFolder.newFile().apply { writeText("Test") }
device.pushFile(file, "${HostUtils.getDataDir(device, TEST_PKG_NAME)}/files/test.txt")
preparer.deleteApk(apk, Partition.SYSTEM)
.pushApk(NEW_PKG, Partition.SYSTEM)
.reboot()
device.getAppPackageInfo(TEST_PKG_NAME)
.run {
assertThat(this.toString()).isNotEmpty()
assertThat(codePath)
.contains(NEW_PKG.removeSuffix(".apk"))
}
// And then reading the data contents back
assertThat(device.pullFileContents(
"${HostUtils.getDataDir(device, TEST_PKG_NAME)}/files/test.txt"))
.isEqualTo("Test")
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2020 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.pm.test
import java.nio.file.Path
import java.nio.file.Paths
// Unfortunately no easy way to access PMS SystemPartitions, so mock them here
internal enum class Partition(val baseFolder: Path) {
SYSTEM("/system/app"),
VENDOR("/vendor/app"),
PRODUCT("/product/app"),
SYSTEM_EXT("/system_ext/app")
;
constructor(baseFolder: String) : this(Paths.get(baseFolder))
}

View File

@@ -0,0 +1,33 @@
// Copyright (C) 2020 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.
android_test_helper_app {
name: "PackageManagerDummyAppVersion1",
manifest: "AndroidManifestVersion1.xml"
}
android_test_helper_app {
name: "PackageManagerDummyAppVersion2",
manifest: "AndroidManifestVersion2.xml"
}
android_test_helper_app {
name: "PackageManagerDummyAppVersion3",
manifest: "AndroidManifestVersion3.xml"
}
android_test_helper_app {
name: "PackageManagerDummyAppOriginalOverride",
manifest: "AndroidManifestOriginalOverride.xml"
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.server.pm.test.dummy_app.override"
android:versionCode="2"
>
<original-package android:name="com.android.server.pm.test.dummy_app"/>
</manifest>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.server.pm.test.dummy_app"
android:versionCode="1"
/>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.server.pm.test.dummy_app"
android:versionCode="2"
/>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.server.pm.test.dummy_app"
android:versionCode="3"
/>

View File

@@ -0,0 +1,28 @@
// Copyright (C) 2020 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.
//
java_library_host {
name: "frameworks-base-hostutils",
srcs: [
"src/**/*.java",
"src/**/*.kt",
],
libs: [
"tradefed",
"junit",
],
}

View File

@@ -0,0 +1,362 @@
/*
* Copyright (C) 2020 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.internal.util.test;
import static org.junit.Assert.assertTrue;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import javax.annotation.Nullable;
/**
* Allows pushing files onto the device and various options for rebooting. Useful for installing
* APKs/files to system partitions which otherwise wouldn't be easily changed.
*
* It's strongly recommended to pass in a {@link ClassRule} annotated {@link TestRuleDelegate} to
* do a full reboot at the end of a test to ensure the device is in a valid state, assuming the
* default {@link RebootStrategy#FULL} isn't used.
*/
public class SystemPreparer extends ExternalResource {
private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000;
// The paths of the files pushed onto the device through this rule.
private ArrayList<String> mPushedFiles = new ArrayList<>();
// The package names of packages installed through this rule.
private ArrayList<String> mInstalledPackages = new ArrayList<>();
private final TemporaryFolder mHostTempFolder;
private final DeviceProvider mDeviceProvider;
private final RebootStrategy mRebootStrategy;
private final TearDownRule mTearDownRule;
public SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) {
this(hostTempFolder, RebootStrategy.FULL, null, deviceProvider);
}
public SystemPreparer(TemporaryFolder hostTempFolder, RebootStrategy rebootStrategy,
@Nullable TestRuleDelegate testRuleDelegate, DeviceProvider deviceProvider) {
mHostTempFolder = hostTempFolder;
mDeviceProvider = deviceProvider;
mRebootStrategy = rebootStrategy;
mTearDownRule = new TearDownRule(mDeviceProvider);
if (testRuleDelegate != null) {
testRuleDelegate.setDelegate(mTearDownRule);
}
}
/** Copies a file within the host test jar to a path on device. */
public SystemPreparer pushResourceFile(String filePath, String outputPath)
throws DeviceNotAvailableException, IOException {
final ITestDevice device = mDeviceProvider.getDevice();
remount();
assertTrue(device.pushFile(copyResourceToTemp(filePath), outputPath));
mPushedFiles.add(outputPath);
return this;
}
/** Copies a file directly from the host file system to a path on device. */
public SystemPreparer pushFile(File file, String outputPath)
throws DeviceNotAvailableException {
final ITestDevice device = mDeviceProvider.getDevice();
remount();
assertTrue(device.pushFile(file, outputPath));
mPushedFiles.add(outputPath);
return this;
}
/** Deletes the given path from the device */
public SystemPreparer deleteFile(String file) throws DeviceNotAvailableException {
final ITestDevice device = mDeviceProvider.getDevice();
remount();
device.deleteFile(file);
return this;
}
/** Installs an APK within the host test jar onto the device. */
public SystemPreparer installResourceApk(String resourcePath, String packageName)
throws DeviceNotAvailableException, IOException {
final ITestDevice device = mDeviceProvider.getDevice();
final File tmpFile = copyResourceToTemp(resourcePath);
final String result = device.installPackage(tmpFile, true /* reinstall */);
Assert.assertNull(result);
mInstalledPackages.add(packageName);
return this;
}
/** Sets the enable state of an overlay package. */
public SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
throws DeviceNotAvailableException {
final ITestDevice device = mDeviceProvider.getDevice();
final String enable = enabled ? "enable" : "disable";
// Wait for the overlay to change its enabled state.
final long endMillis = System.currentTimeMillis() + OVERLAY_ENABLE_TIMEOUT_MS;
String result;
while (System.currentTimeMillis() <= endMillis) {
device.executeShellCommand(String.format("cmd overlay %s %s", enable, packageName));
result = device.executeShellCommand("cmd overlay dump isenabled "
+ packageName);
if (((enabled) ? "true\n" : "false\n").equals(result)) {
return this;
}
try {
Thread.sleep(200);
} catch (InterruptedException ignore) {
}
}
throw new IllegalStateException(String.format("Failed to %s overlay %s:\n%s", enable,
packageName, device.executeShellCommand("cmd overlay list")));
}
/** Restarts the device and waits until after boot is completed. */
public SystemPreparer reboot() throws DeviceNotAvailableException {
ITestDevice device = mDeviceProvider.getDevice();
switch (mRebootStrategy) {
case FULL:
device.reboot();
break;
case UNTIL_ONLINE:
device.rebootUntilOnline();
break;
case USERSPACE:
device.rebootUserspace();
break;
case USERSPACE_UNTIL_ONLINE:
device.rebootUserspaceUntilOnline();
break;
case START_STOP:
device.executeShellCommand("stop");
device.executeShellCommand("start");
ITestDevice.RecoveryMode cachedRecoveryMode = device.getRecoveryMode();
device.setRecoveryMode(ITestDevice.RecoveryMode.ONLINE);
if (device.isEncryptionSupported()) {
if (device.isDeviceEncrypted()) {
LogUtil.CLog.e("Device is encrypted after userspace reboot!");
device.unlockDevice();
}
}
device.setRecoveryMode(cachedRecoveryMode);
device.waitForDeviceAvailable();
break;
}
return this;
}
public SystemPreparer remount() throws DeviceNotAvailableException {
mTearDownRule.remount();
return this;
}
/** Copies a file within the host test jar to a temporary file on the host machine. */
private File copyResourceToTemp(String resourcePath) throws IOException {
final File tempFile = mHostTempFolder.newFile();
final ClassLoader classLoader = getClass().getClassLoader();
try (InputStream assetIs = classLoader.getResource(resourcePath).openStream();
FileOutputStream assetOs = new FileOutputStream(tempFile)) {
if (assetIs == null) {
throw new IllegalStateException("Failed to find resource " + resourcePath);
}
int b;
while ((b = assetIs.read()) >= 0) {
assetOs.write(b);
}
}
return tempFile;
}
/** Removes installed packages and files that were pushed to the device. */
@Override
protected void after() {
final ITestDevice device = mDeviceProvider.getDevice();
try {
remount();
for (final String file : mPushedFiles) {
device.deleteFile(file);
}
for (final String packageName : mInstalledPackages) {
device.uninstallPackage(packageName);
}
reboot();
} catch (DeviceNotAvailableException e) {
Assert.fail(e.toString());
}
}
/**
* A hacky workaround since {@link org.junit.AfterClass} and {@link ClassRule} require static
* members. Will defer assignment of the actual {@link TestRule} to execute until after any
* test case has been run.
*
* In effect, this makes the {@link ITestDevice} to be accessible after all test cases have
* been executed, allowing {@link ITestDevice#reboot()} to be used to fully restore the device.
*/
public static class TestRuleDelegate implements TestRule {
private boolean mThrowOnNull;
@Nullable
private TestRule mTestRule;
public TestRuleDelegate(boolean throwOnNull) {
mThrowOnNull = throwOnNull;
}
public void setDelegate(TestRule testRule) {
mTestRule = testRule;
}
@Override
public Statement apply(Statement base, Description description) {
if (mTestRule == null) {
if (mThrowOnNull) {
throw new IllegalStateException("TestRule delegate was not set");
} else {
return new Statement() {
@Override
public void evaluate() throws Throwable {
base.evaluate();
}
};
}
}
Statement statement = mTestRule.apply(base, description);
mTestRule = null;
return statement;
}
}
/**
* Forces a full reboot at the end of the test class to restore any device state.
*/
private static class TearDownRule extends ExternalResource {
private DeviceProvider mDeviceProvider;
private boolean mInitialized;
private boolean mWasVerityEnabled;
private boolean mWasAdbRoot;
private boolean mIsVerityEnabled;
TearDownRule(DeviceProvider deviceProvider) {
mDeviceProvider = deviceProvider;
}
@Override
protected void before() {
// This method will never be run
}
@Override
protected void after() {
try {
initialize();
ITestDevice device = mDeviceProvider.getDevice();
if (mWasVerityEnabled != mIsVerityEnabled) {
device.executeShellCommand(
mWasVerityEnabled ? "enable-verity" : "disable-verity");
}
device.reboot();
if (!mWasAdbRoot) {
device.disableAdbRoot();
}
} catch (DeviceNotAvailableException e) {
Assert.fail(e.toString());
}
}
/**
* Remount is done inside this class so that the verity state can be tracked.
*/
public void remount() throws DeviceNotAvailableException {
initialize();
ITestDevice device = mDeviceProvider.getDevice();
device.enableAdbRoot();
if (mIsVerityEnabled) {
mIsVerityEnabled = false;
device.executeShellCommand("disable-verity");
device.reboot();
}
device.executeShellCommand("remount");
device.waitForDeviceAvailable();
}
private void initialize() throws DeviceNotAvailableException {
if (mInitialized) {
return;
}
mInitialized = true;
ITestDevice device = mDeviceProvider.getDevice();
mWasAdbRoot = device.isAdbRoot();
device.enableAdbRoot();
String veritySystem = device.getProperty("partition.system.verified");
String verityVendor = device.getProperty("partition.vendor.verified");
mWasVerityEnabled = (veritySystem != null && !veritySystem.isEmpty())
|| (verityVendor != null && !verityVendor.isEmpty());
mIsVerityEnabled = mWasVerityEnabled;
}
}
public interface DeviceProvider {
ITestDevice getDevice();
}
/**
* How to reboot the device. Ordered from slowest to fastest.
*/
public enum RebootStrategy {
/** @see ITestDevice#reboot() */
FULL,
/** @see ITestDevice#rebootUntilOnline() () */
UNTIL_ONLINE,
/** @see ITestDevice#rebootUserspace() */
USERSPACE,
/** @see ITestDevice#rebootUserspaceUntilOnline() () */
USERSPACE_UNTIL_ONLINE,
/**
* Uses shell stop && start to "reboot" the device. May leave invalid state after each test.
* Whether this matters or not depends on what's being tested.
*/
START_STOP
}
}