summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ryan Mitchell <rtmitchell@google.com> 2019-10-16 08:32:55 -0700
committer Ryan Mitchell <rtmitchell@google.com> 2020-01-10 23:20:19 +0000
commitee4a564d4ff2d16b6cdb6972e3aa92cd72668229 (patch)
tree7b5fa4c8bbb6df56b4445056236decafc1d9e9db
parentfe50d739f75e13ebf64c010bf6ef504bcc81d860 (diff)
Allow for overlaying dynamic shared libraries
Overlays targeting shared libraries should be loaded into the resources of every target that depends on the shared library. Static shared libraries are currently not supported because overlays should override all versions of static shared libraries and there is not currently support for an overlay targeting multiple APKs. Also created a test instrumentation and host test suite for testing overlays and packages on the system image. Bug: 140790224 Test: atest OverlayRemountedTest Change-Id: I20a217b6368d6cf92b2b9f46908fd58012933f72
-rw-r--r--cmds/idmap2/libidmap2/ResourceMapping.cpp12
-rw-r--r--core/java/android/app/ResourcesManager.java26
-rw-r--r--core/java/android/content/pm/PackageParser.java2
-rw-r--r--core/java/android/content/pm/PackageUserState.java62
-rw-r--r--core/java/android/content/pm/parsing/PackageInfoUtils.java4
-rw-r--r--core/tests/overlaytests/remount/TEST_MAPPING7
-rw-r--r--core/tests/overlaytests/remount/host/Android.bp28
-rw-r--r--core/tests/overlaytests/remount/host/AndroidTest.xml29
-rw-r--r--core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java114
-rw-r--r--core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java90
-rw-r--r--core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java158
-rw-r--r--core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp19
-rw-r--r--core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml23
-rw-r--r--core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml24
-rw-r--r--core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml20
-rw-r--r--core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml20
-rw-r--r--core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp18
-rw-r--r--core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml23
-rw-r--r--core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml20
-rw-r--r--core/tests/overlaytests/remount/target/Android.bp20
-rw-r--r--core/tests/overlaytests/remount/target/AndroidManifest.xml30
-rw-r--r--core/tests/overlaytests/remount/target/res/values/values.xml20
-rw-r--r--core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java140
-rw-r--r--libs/androidfw/AssetManager2.cpp7
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerService.java12
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerServiceImpl.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java79
-rw-r--r--services/core/java/com/android/server/pm/PackageSettingBase.java16
-rw-r--r--services/core/java/com/android/server/pm/Settings.java16
-rw-r--r--tools/aapt2/cmd/Link.cpp2
-rw-r--r--tools/aapt2/process/SymbolTable.cpp15
-rw-r--r--tools/aapt2/process/SymbolTable.h2
33 files changed, 1029 insertions, 42 deletions
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 229628c7dd8b..407478945151 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -33,6 +33,8 @@ namespace android::idmap2 {
namespace {
+#define REWRITE_PACKAGE(resid, package_id) \
+ (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
std::string ConcatPolicies(const std::vector<std::string>& policies) {
@@ -154,6 +156,7 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManage
return Error("root element is not <overlay> tag");
}
+ const uint8_t target_package_id = target_package->GetPackageId();
const uint8_t overlay_package_id = overlay_package->GetPackageId();
auto overlay_it_end = root_it.end();
for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
@@ -187,6 +190,9 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManage
continue;
}
+ // Retrieve the compile-time resource id of the target resource.
+ target_id = REWRITE_PACKAGE(target_id, target_package_id);
+
if (overlay_resource->dataType == Res_value::TYPE_STRING) {
overlay_resource->data += string_pool_offset;
}
@@ -214,6 +220,7 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy(
const AssetManager2* target_am, const AssetManager2* overlay_am,
const LoadedPackage* target_package, const LoadedPackage* overlay_package) {
ResourceMapping resource_mapping;
+ const uint8_t target_package_id = target_package->GetPackageId();
const auto end = overlay_package->end();
for (auto iter = overlay_package->begin(); iter != end; ++iter) {
const ResourceId overlay_resid = *iter;
@@ -225,11 +232,14 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy(
// Find the resource with the same type and entry name within the target package.
const std::string full_name =
base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
- const ResourceId target_resource = target_am->GetResourceId(full_name);
+ ResourceId target_resource = target_am->GetResourceId(full_name);
if (target_resource == 0U) {
continue;
}
+ // Retrieve the compile-time resource id of the target resource.
+ target_resource = REWRITE_PACKAGE(target_resource, target_package_id);
+
resource_mapping.AddMapping(target_resource, Res_value::TYPE_REFERENCE, overlay_resid,
/* rewrite_overlay_reference */ false);
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index aa115984b5e1..d23754e9d433 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -482,19 +482,6 @@ public class ResourcesManager {
}
}
- if (key.mOverlayDirs != null) {
- for (final String idmapPath : key.mOverlayDirs) {
- try {
- builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
- true /*overlay*/));
- } catch (IOException e) {
- Log.w(TAG, "failed to add overlay path " + idmapPath);
-
- // continue.
- }
- }
- }
-
if (key.mLibDirs != null) {
for (final String libDir : key.mLibDirs) {
if (libDir.endsWith(".apk")) {
@@ -513,6 +500,19 @@ public class ResourcesManager {
}
}
+ if (key.mOverlayDirs != null) {
+ for (final String idmapPath : key.mOverlayDirs) {
+ try {
+ builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
+ true /*overlay*/));
+ } catch (IOException e) {
+ Log.w(TAG, "failed to add overlay path " + idmapPath);
+
+ // continue.
+ }
+ }
+ }
+
return builder.build();
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 32803ab8f859..87acbc146f49 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -7803,7 +7803,7 @@ public class PackageParser {
ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
}
ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
- ai.resourceDirs = state.overlayPaths;
+ ai.resourceDirs = state.getAllOverlayPaths();
ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes;
}
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index f0f6753fc93e..da7623a1426e 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -46,6 +46,8 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Objects;
/**
@@ -77,7 +79,9 @@ public class PackageUserState {
public ArraySet<String> disabledComponents;
public ArraySet<String> enabledComponents;
- public String[] overlayPaths;
+ private String[] overlayPaths;
+ private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths
+ private String[] cachedOverlayPaths;
@UnsupportedAppUsage
public PackageUserState() {
@@ -112,9 +116,33 @@ public class PackageUserState {
enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
overlayPaths =
o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
+ if (o.sharedLibraryOverlayPaths != null) {
+ sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths);
+ }
harmfulAppWarning = o.harmfulAppWarning;
}
+ public String[] getOverlayPaths() {
+ return overlayPaths;
+ }
+
+ public void setOverlayPaths(String[] paths) {
+ overlayPaths = paths;
+ cachedOverlayPaths = null;
+ }
+
+ public Map<String, String[]> getSharedLibraryOverlayPaths() {
+ return sharedLibraryOverlayPaths;
+ }
+
+ public void setSharedLibraryOverlayPaths(String library, String[] paths) {
+ if (sharedLibraryOverlayPaths == null) {
+ sharedLibraryOverlayPaths = new ArrayMap<>();
+ }
+ sharedLibraryOverlayPaths.put(library, paths);
+ cachedOverlayPaths = null;
+ }
+
/**
* Test if this package is installed.
*/
@@ -235,6 +263,38 @@ public class PackageUserState {
return isComponentEnabled;
}
+ public String[] getAllOverlayPaths() {
+ if (overlayPaths == null && sharedLibraryOverlayPaths == null) {
+ return null;
+ }
+
+ if (cachedOverlayPaths != null) {
+ return cachedOverlayPaths;
+ }
+
+ final LinkedHashSet<String> paths = new LinkedHashSet<>();
+ if (overlayPaths != null) {
+ final int N = overlayPaths.length;
+ for (int i = 0; i < N; i++) {
+ paths.add(overlayPaths[i]);
+ }
+ }
+
+ if (sharedLibraryOverlayPaths != null) {
+ for (String[] libOverlayPaths : sharedLibraryOverlayPaths.values()) {
+ if (libOverlayPaths != null) {
+ final int N = libOverlayPaths.length;
+ for (int i = 0; i < N; i++) {
+ paths.add(libOverlayPaths[i]);
+ }
+ }
+ }
+ }
+
+ cachedOverlayPaths = paths.toArray(new String[0]);
+ return cachedOverlayPaths;
+ }
+
@Override
final public boolean equals(Object obj) {
if (!(obj instanceof PackageUserState)) {
diff --git a/core/java/android/content/pm/parsing/PackageInfoUtils.java b/core/java/android/content/pm/parsing/PackageInfoUtils.java
index f2cf9a484c6e..73a8d2a7dc0f 100644
--- a/core/java/android/content/pm/parsing/PackageInfoUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoUtils.java
@@ -41,9 +41,11 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
import android.content.pm.parsing.ComponentParseUtils.ParsedInstrumentation;
import android.content.pm.parsing.ComponentParseUtils.ParsedPermission;
import android.content.pm.parsing.ComponentParseUtils.ParsedPermissionGroup;
+import android.util.ArraySet;
import com.android.internal.util.ArrayUtils;
+import java.util.LinkedHashSet;
import java.util.Set;
/** @hide */
@@ -545,7 +547,7 @@ public class PackageInfoUtils {
ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
}
ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
- ai.resourceDirs = state.overlayPaths;
+ ai.resourceDirs = state.getAllOverlayPaths();
ai.icon = (PackageParser.sUseRoundIcon && ai.roundIconRes != 0)
? ai.roundIconRes : ai.iconRes;
}
diff --git a/core/tests/overlaytests/remount/TEST_MAPPING b/core/tests/overlaytests/remount/TEST_MAPPING
new file mode 100644
index 000000000000..54dd4310bd6e
--- /dev/null
+++ b/core/tests/overlaytests/remount/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name" : "OverlayRemountedTest"
+ }
+ ]
+} \ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/Android.bp b/core/tests/overlaytests/remount/host/Android.bp
new file mode 100644
index 000000000000..3825c55f3b4b
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+java_test_host {
+ name: "OverlayRemountedTest",
+ srcs: ["src/**/*.java"],
+ libs: [
+ "tradefed",
+ "junit",
+ ],
+ test_suites: ["general-tests"],
+ java_resources: [
+ ":OverlayRemountedTest_SharedLibrary",
+ ":OverlayRemountedTest_SharedLibraryOverlay",
+ ":OverlayRemountedTest_Target",
+ ],
+}
diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/host/AndroidTest.xml
new file mode 100644
index 000000000000..11eadf1a4659
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<configuration description="Test module config for OverlayRemountedTest">
+ <option name="test-tag" value="OverlayRemountedTest" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="remount" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.HostTest">
+ <option name="jar" value="OverlayRemountedTest.jar" />
+ </test>
+</configuration>
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java
new file mode 100644
index 000000000000..84af18710fe6
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class OverlayHostTest extends BaseHostJUnit4Test {
+ private static final long TIME_OUT_MS = 30000;
+ private static final String RES_INSTRUMENTATION_ARG = "res";
+ private static final String OVERLAY_INSTRUMENTATION_ARG = "overlays";
+ private static final String RESOURCES_TYPE_SUFFIX = "_type";
+ private static final String RESOURCES_DATA_SUFFIX = "_data";
+
+ public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ public final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice);
+
+ @Rule
+ public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer);
+ private Map<String, String> mLastResults;
+
+ /**
+ * Retrieves the values of the resources in the test package. The test package must use the
+ * {@link com.android.overlaytest.remounted.target.ResourceRetrievalRunner} instrumentation.
+ **/
+ void retrieveResource(String testPackageName, List<String> requiredOverlayPaths,
+ String... resourceNames) throws DeviceNotAvailableException {
+ final HashMap<String, String> args = new HashMap<>();
+ if (!requiredOverlayPaths.isEmpty()) {
+ // Enclose the require overlay paths in quotes so the arguments will be string arguments
+ // rather than file arguments.
+ args.put(OVERLAY_INSTRUMENTATION_ARG,
+ String.format("\"%s\"", String.join(" ", requiredOverlayPaths)));
+ }
+
+ if (resourceNames.length == 0) {
+ throw new IllegalArgumentException("Must specify at least one resource to retrieve.");
+ }
+
+ // Pass the names of the resources to retrieve into the test as one string.
+ args.put(RES_INSTRUMENTATION_ARG,
+ String.format("\"%s\"", String.join(" ", resourceNames)));
+
+ runDeviceTests(getDevice(), null, testPackageName, null, null, null, TIME_OUT_MS,
+ TIME_OUT_MS, TIME_OUT_MS, false, false, args);
+
+ // Retrieve the results of the most recently run test.
+ mLastResults = (getLastDeviceRunResults().getRunMetrics() == mLastResults) ? null :
+ getLastDeviceRunResults().getRunMetrics();
+ }
+
+ /** Returns the base resource directories of the specified packages. */
+ List<String> getPackagePaths(String... packageNames)
+ throws DeviceNotAvailableException {
+ final ArrayList<String> paths = new ArrayList<>();
+ for (String packageName : packageNames) {
+ // Use the package manager shell command to find the path of the package.
+ final String result = getDevice().executeShellCommand(
+ String.format("pm dump %s | grep \"resourcePath=\"", packageName));
+ assertNotNull("Failed to find path for package " + packageName, result);
+ int splitIndex = result.indexOf('=');
+ assertTrue(splitIndex >= 0);
+ paths.add(result.substring(splitIndex + 1).trim());
+ }
+ return paths;
+ }
+
+ /** Builds the full name of a resource in the form package:type/entry. */
+ String resourceName(String pkg, String type, String entry) {
+ return String.format("%s:%s/%s", pkg, type, entry);
+ }
+
+ /**
+ * Asserts that the type and data of a a previously retrieved is the same as expected.
+ * @param resourceName the full name of the resource in the form package:type/entry
+ * @param type the expected {@link android.util.TypedValue} type of the resource
+ * @param data the expected value of the resource when coerced to a string using
+ * {@link android.util.TypedValue#coerceToString()}
+ **/
+ void assertResource(String resourceName, int type, String data) {
+ assertNotNull("Failed to get test results", mLastResults);
+ assertNotEquals("No resource values were retrieved", mLastResults.size(), 0);
+ assertEquals("" + type, mLastResults.get(resourceName + RESOURCES_TYPE_SUFFIX));
+ assertEquals("" + data, mLastResults.get(resourceName + RESOURCES_DATA_SUFFIX));
+ }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
new file mode 100644
index 000000000000..4939e160612e
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OverlaySharedLibraryTest extends OverlayHostTest {
+ private static final String TARGET_APK = "OverlayRemountedTest_Target.apk";
+ private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target";
+ private static final String SHARED_LIBRARY_APK =
+ "OverlayRemountedTest_SharedLibrary.apk";
+ private static final String SHARED_LIBRARY_PACKAGE =
+ "com.android.overlaytest.remounted.shared_library";
+ private static final String SHARED_LIBRARY_OVERLAY_APK =
+ "OverlayRemountedTest_SharedLibraryOverlay.apk";
+ private static final String SHARED_LIBRARY_OVERLAY_PACKAGE =
+ "com.android.overlaytest.remounted.shared_library.overlay";
+
+ @Test
+ public void testSharedLibrary() throws Exception {
+ final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+ "uses_shared_library_overlaid");
+ final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+ "shared_library_overlaid");
+
+ mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+ .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+ .reboot()
+ .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false)
+ .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+ // The shared library resource is not currently overlaid.
+ retrieveResource(Collections.emptyList(), targetResource, libraryResource);
+ assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "false");
+ assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "false");
+
+ // Overlay the shared library resource.
+ mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
+ retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource,
+ libraryResource);
+ assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ }
+
+ @Test
+ public void testSharedLibraryPreEnabled() throws Exception {
+ final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+ "uses_shared_library_overlaid");
+ final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+ "shared_library_overlaid");
+
+ mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+ .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+ .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true)
+ .reboot()
+ .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+ retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource,
+ libraryResource);
+ assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ }
+
+ private void retrieveResource(List<String> requiredOverlayPaths, String... resourceNames)
+ throws DeviceNotAvailableException {
+ retrieveResource(TARGET_PACKAGE, requiredOverlayPaths, resourceNames);
+ }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
new file mode 100644
index 000000000000..7028f2f1d554
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
@@ -0,0 +1,158 @@
+/*
+ * 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 static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+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;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeoutException;
+
+class SystemPreparer extends ExternalResource {
+ private static final long REBOOT_SLEEP_MS = 30000;
+ private static final long OVERLAY_ENABLE_TIMEOUT_MS = 20000;
+
+ // 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();
+ 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);
+ Assert.assertNull(result);
+ mInstalledPackages.add(packageName);
+ return this;
+ }
+
+ /** Sets the enable state of an overlay pacakage. */
+ SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
+ throws ExecutionException, TimeoutException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+
+ // Wait for the overlay to change its enabled state.
+ final FutureTask<Boolean> enabledListener = new FutureTask<>(() -> {
+ while (true) {
+ device.executeShellCommand(String.format("cmd overlay %s %s",
+ enabled ? "enable" : "disable", packageName));
+
+ final String pattern = (enabled ? "[x]" : "[ ]") + " " + packageName;
+ if (device.executeShellCommand("cmd overlay list").contains(pattern)) {
+ return true;
+ }
+ }
+ });
+
+ final Executor executor = (cmd) -> new Thread(cmd).start();
+ executor.execute(enabledListener);
+ try {
+ enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS);
+ } catch (InterruptedException ignored) {
+ }
+
+ return this;
+ }
+
+ /** Restarts the device and waits until after boot is completed. */
+ SystemPreparer reboot() throws DeviceNotAvailableException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ device.executeShellCommand("stop");
+ device.executeShellCommand("start");
+ try {
+ // Sleep until the device is ready for test execution.
+ Thread.sleep(REBOOT_SLEEP_MS);
+ } catch (InterruptedException ignored) {
+ }
+
+ 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 {
+ for (final String file : mPushedFiles) {
+ device.deleteFile(file);
+ }
+ for (final String packageName : mInstalledPackages) {
+ device.uninstallPackage(packageName);
+ }
+ } catch (DeviceNotAvailableException e) {
+ Assert.fail(e.toString());
+ }
+ }
+
+ interface DeviceProvider {
+ ITestDevice getDevice();
+ }
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
new file mode 100644
index 000000000000..ffb0572c3fd0
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
@@ -0,0 +1,19 @@
+// 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.
+
+android_test_helper_app {
+ name: "OverlayRemountedTest_SharedLibrary",
+ sdk_version: "current",
+ aaptflags: ["--shared-lib"],
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
new file mode 100644
index 000000000000..06e3f6a99410
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest.remounted.shared_library">
+ <application>
+ <library android:name="com.android.overlaytest.remounted.shared_library" />
+ </application>
+</manifest> \ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
new file mode 100644
index 000000000000..1b06f6d7530b
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <overlayable name="TestResources">
+ <policy type="public">
+ <item type="bool" name="shared_library_overlaid" />
+ </policy>
+ </overlayable>
+</resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
new file mode 100644
index 000000000000..5b9db163a274
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <public type="bool" name="shared_library_overlaid" id="0x00050001"/>
+</resources> \ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
new file mode 100644
index 000000000000..2dc47a7ecf61
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <bool name="shared_library_overlaid">false</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
new file mode 100644
index 000000000000..0d29aec909d5
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
@@ -0,0 +1,18 @@
+// 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.
+
+android_test_helper_app {
+ name: "OverlayRemountedTest_SharedLibraryOverlay",
+ sdk_version: "current",
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
new file mode 100644
index 000000000000..53a4e61949da
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest.remounted.shared_library.overlay">
+ <application android:hasCode="false" />
+ <overlay android:targetPackage="com.android.overlaytest.remounted.shared_library"
+ android:targetName="TestResources" />
+</manifest> \ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
new file mode 100644
index 000000000000..f66448a8d991
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <bool name="shared_library_overlaid">true</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/target/Android.bp
new file mode 100644
index 000000000000..83f9f28b3f48
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 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: "OverlayRemountedTest_Target",
+ srcs: ["src/**/*.java"],
+ sdk_version: "test_current",
+ libs: ["OverlayRemountedTest_SharedLibrary"],
+}
diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/target/AndroidManifest.xml
new file mode 100644
index 000000000000..32fec43593f8
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest.remounted.target">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <uses-library android:name="com.android.overlaytest.remounted.shared_library"
+ android:required="true" />
+ </application>
+
+ <instrumentation android:name="com.android.overlaytest.remounted.target.ResourceRetrievalRunner"
+ android:targetPackage="com.android.overlaytest.remounted.target"
+ android:label="Remounted system RRO tests" />
+</manifest>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/target/res/values/values.xml
new file mode 100644
index 000000000000..b5f444a5eb72
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
+ <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java
new file mode 100644
index 000000000000..2e4c211d6a0a
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java
@@ -0,0 +1,140 @@
+/*
+ * 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.target;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An {@link Instrumentation} that retrieves the value of specified resources within the
+ * application.
+ **/
+public class ResourceRetrievalRunner extends Instrumentation {
+ private static final String TAG = ResourceRetrievalRunner.class.getSimpleName();
+
+ // A list of whitespace separated resource names of which to retrieve the resource values.
+ private static final String RESOURCE_LIST_TAG = "res";
+
+ // A list of whitespace separated overlay package paths that must be present before retrieving
+ // resource values.
+ private static final String REQUIRED_OVERLAYS_LIST_TAG = "overlays";
+
+ // The suffixes of the keys returned from the instrumentation. To retrieve the type of a
+ // resource looked up with the instrumentation, append the {@link #RESOURCES_TYPE_SUFFIX} suffix
+ // to the end of the name of the resource. For the value of a resource, use
+ // {@link #RESOURCES_DATA_SUFFIX} instead.
+ private static final String RESOURCES_TYPE_SUFFIX = "_type";
+ private static final String RESOURCES_DATA_SUFFIX = "_data";
+
+ // The amount of time in seconds to wait for the overlays to be present in the AssetManager.
+ private static final int OVERLAY_PATH_TIMEOUT = 60;
+
+ private final ArrayList<String> mResourceNames = new ArrayList<>();
+ private final ArrayList<String> mOverlayPaths = new ArrayList<>();
+ private final Bundle mResult = new Bundle();
+
+ /**
+ * Receives the instrumentation arguments and runs the resource retrieval.
+ * The entry with key {@link #RESOURCE_LIST_TAG} in the {@link Bundle} arguments is a
+ * whitespace separated string of resource names of which to retrieve the resource values.
+ * The entry with key {@link #REQUIRED_OVERLAYS_LIST_TAG} in the {@link Bundle} arguments is a
+ * whitespace separated string of overlay package paths prefixes that must be present before
+ * retrieving the resource values.
+ */
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+ mResourceNames.addAll(Arrays.asList(arguments.getString(RESOURCE_LIST_TAG).split(" ")));
+ if (arguments.containsKey(REQUIRED_OVERLAYS_LIST_TAG)) {
+ mOverlayPaths.addAll(Arrays.asList(
+ arguments.getString(REQUIRED_OVERLAYS_LIST_TAG).split(" ")));
+ }
+ start();
+ }
+
+ @Override
+ public void onStart() {
+ final Resources res = getContext().getResources();
+ res.getAssets().setResourceResolutionLoggingEnabled(true);
+
+ if (!mOverlayPaths.isEmpty()) {
+ Log.d(TAG, String.format("Waiting for overlay paths [%s]",
+ String.join(",", mOverlayPaths)));
+
+ // Wait for all required overlays to be present in the AssetManager.
+ final FutureTask<Boolean> overlayListener = new FutureTask<>(() -> {
+ while (!mOverlayPaths.isEmpty()) {
+ final String[] apkPaths = res.getAssets().getApkPaths();
+ for (String path : apkPaths) {
+ for (String overlayPath : mOverlayPaths) {
+ if (path.startsWith(overlayPath)) {
+ mOverlayPaths.remove(overlayPath);
+ break;
+ }
+ }
+ }
+ }
+ return true;
+ });
+
+ try {
+ final Executor executor = (t) -> new Thread(t).start();
+ executor.execute(overlayListener);
+ overlayListener.get(OVERLAY_PATH_TIMEOUT, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ Log.e(TAG, String.format("Failed to wait for required overlays [%s]",
+ String.join(",", mOverlayPaths)), e);
+ finish(Activity.RESULT_CANCELED, mResult);
+ }
+ }
+
+ // Retrieve the values for each resource passed in.
+ final TypedValue typedValue = new TypedValue();
+ for (final String resourceName : mResourceNames) {
+ try {
+ final int resId = res.getIdentifier(resourceName, null, null);
+ res.getValue(resId, typedValue, true);
+ Log.d(TAG, String.format("Resolution for 0x%s: %s", Integer.toHexString(resId),
+ res.getAssets().getLastResourceResolution()));
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Failed to retrieve value for resource " + resourceName, e);
+ finish(Activity.RESULT_CANCELED, mResult);
+ }
+
+ putValue(resourceName, typedValue);
+ }
+
+ finish(Activity.RESULT_OK, mResult);
+ }
+
+ private void putValue(String resourceName, TypedValue value) {
+ mResult.putInt(resourceName + RESOURCES_TYPE_SUFFIX, value.type);
+ final CharSequence textValue = value.coerceToString();
+ mResult.putString(resourceName + RESOURCES_DATA_SUFFIX,
+ textValue == null ? "null" : textValue.toString());
+ }
+}
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 2c6be41052e8..8cfd2d8ca696 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -141,7 +141,10 @@ void AssetManager2::BuildDynamicRefTable() {
// to take effect.
const auto& loaded_idmap = apk_assets->GetLoadedIdmap();
auto target_package_iter = apk_assets_package_ids.find(loaded_idmap->TargetApkPath());
- if (target_package_iter != apk_assets_package_ids.end()) {
+ if (target_package_iter == apk_assets_package_ids.end()) {
+ LOG(INFO) << "failed to find target package for overlay "
+ << loaded_idmap->OverlayApkPath();
+ } else {
const uint8_t target_package_id = target_package_iter->second;
const uint8_t target_idx = package_ids_[target_package_id];
CHECK(target_idx != 0xff) << "overlay added to apk_assets_package_ids but does not"
@@ -591,7 +594,7 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
if (resource_resolution_logging_enabled_) {
last_resolution_.steps.push_back(
Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result.config.toString(),
- &package_group.packages_[0].loaded_package_->GetPackageName()});
+ overlay_result.package_name});
}
}
}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 312dd46fbc73..41653e9a85ba 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -41,6 +41,7 @@ import com.android.server.pm.PackageList;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
@@ -478,10 +479,12 @@ public abstract class PackageManagerInternal {
* will be disabled. Pass in null or an empty list to disable
* all overlays. The order of the items is significant if several
* overlays modify the same resource.
+ * @param outUpdatedPackageNames An output list that contains the package names of packages
+ * affected by the update of enabled overlays.
* @return true if all packages names were known by the package manager, false otherwise
*/
public abstract boolean setEnabledOverlayPackages(int userId, String targetPackageName,
- List<String> overlayPackageNames);
+ List<String> overlayPackageNames, Collection<String> outUpdatedPackageNames);
/**
* Resolves an activity intent, allowing instant apps to be resolved.
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index b782ca96ae88..3c31f6a7f0d7 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -83,6 +83,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -925,7 +926,7 @@ public final class OverlayManagerService extends SystemService {
/**
* Updates the target packages' set of enabled overlays in PackageManager.
*/
- private void updateOverlayPaths(int userId, List<String> targetPackageNames) {
+ private ArrayList<String> updateOverlayPaths(int userId, List<String> targetPackageNames) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames);
if (DEBUG) {
@@ -955,6 +956,7 @@ public final class OverlayManagerService extends SystemService {
}
}
+ final HashSet<String> updatedPackages = new HashSet<>();
final int n = targetPackageNames.size();
for (int i = 0; i < n; i++) {
final String targetPackageName = targetPackageNames.get(i);
@@ -965,11 +967,13 @@ public final class OverlayManagerService extends SystemService {
}
if (!pm.setEnabledOverlayPackages(
- userId, targetPackageName, pendingChanges.get(targetPackageName))) {
+ userId, targetPackageName, pendingChanges.get(targetPackageName),
+ updatedPackages)) {
Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
targetPackageName, userId));
}
}
+ return new ArrayList<>(updatedPackages);
} finally {
traceEnd(TRACE_TAG_RRO);
}
@@ -980,10 +984,10 @@ public final class OverlayManagerService extends SystemService {
}
private void updateAssets(final int userId, List<String> targetPackageNames) {
- updateOverlayPaths(userId, targetPackageNames);
final IActivityManager am = ActivityManager.getService();
try {
- am.scheduleApplicationInfoChanged(targetPackageNames, userId);
+ final ArrayList<String> updatedPaths = updateOverlayPaths(userId, targetPackageNames);
+ am.scheduleApplicationInfoChanged(updatedPaths, userId);
} catch (RemoteException e) {
// Intentionally left empty.
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 019c9528f8ab..9623542a2900 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -685,7 +685,7 @@ final class OverlayManagerServiceImpl {
// Static RROs targeting to "android", ie framework-res.apk, are handled by native layers.
if (targetPackage != null && overlayPackage != null
&& !("android".equals(targetPackageName)
- && overlayPackage.isStaticOverlayPackage())) {
+ && overlayPackage.isStaticOverlayPackage())) {
mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
}
@@ -703,9 +703,9 @@ final class OverlayManagerServiceImpl {
if (currentState != newState) {
if (DEBUG) {
Slog.d(TAG, String.format("%s:%d: %s -> %s",
- overlayPackageName, userId,
- OverlayInfo.stateToString(currentState),
- OverlayInfo.stateToString(newState)));
+ overlayPackageName, userId,
+ OverlayInfo.stateToString(currentState),
+ OverlayInfo.stateToString(newState)));
}
modified |= mSettings.setState(overlayPackageName, userId, newState);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6bd9c4847a77..d29ec4cae00c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11595,6 +11595,23 @@ public class PackageManagerService extends IPackageManager.Stub
return false;
}
SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx);
+
+ // Remove the shared library overlays from its dependent packages.
+ for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+ final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
+ libraryInfo, 0, currentUserId);
+ if (dependents == null) {
+ continue;
+ }
+ for (VersionedPackage dependentPackage : dependents) {
+ final PackageSetting ps = mSettings.mPackages.get(
+ dependentPackage.getPackageName());
+ if (ps != null) {
+ ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId);
+ }
+ }
+ }
+
versionedLib.remove(version);
if (versionedLib.size() <= 0) {
mSharedLibraries.remove(name);
@@ -15200,6 +15217,29 @@ public class PackageManagerService extends IPackageManager.Stub
// upcoming call to mSettings.writeLPr().
}
}
+
+ // Retrieve the overlays for shared libraries of the package.
+ if (pkg.getUsesLibraryInfos() != null) {
+ for (SharedLibraryInfo sharedLib : pkg.getUsesLibraryInfos()) {
+ for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+ if (!sharedLib.isDynamic()) {
+ // TODO(146804378): Support overlaying static shared libraries
+ continue;
+ }
+ final PackageSetting libPs = mSettings.mPackages.get(
+ sharedLib.getPackageName());
+ if (libPs == null) {
+ continue;
+ }
+ final String[] overlayPaths = libPs.getOverlayPaths(currentUserId);
+ if (overlayPaths != null) {
+ ps.setOverlayPathsForLibrary(sharedLib.getName(),
+ Arrays.asList(overlayPaths), currentUserId);
+ }
+ }
+ }
+ }
+
// It's implied that when a user requests installation, they want the app to be
// installed and enabled.
if (userId != UserHandle.USER_ALL) {
@@ -23226,9 +23266,11 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName,
- @Nullable List<String> overlayPackageNames) {
+ @Nullable List<String> overlayPackageNames,
+ @NonNull Collection<String> outUpdatedPackageNames) {
synchronized (mLock) {
- if (targetPackageName == null || mPackages.get(targetPackageName) == null) {
+ final AndroidPackage targetPkg = mPackages.get(targetPackageName);
+ if (targetPackageName == null || targetPkg == null) {
Slog.e(TAG, "failed to find package " + targetPackageName);
return false;
}
@@ -23247,8 +23289,41 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
+ ArraySet<String> updatedPackageNames = null;
+ if (targetPkg.getLibraryNames() != null) {
+ // Set the overlay paths for dependencies of the shared library.
+ updatedPackageNames = new ArraySet<>();
+ for (String libName : targetPkg.getLibraryNames()) {
+ final SharedLibraryInfo info = getSharedLibraryInfoLPr(libName,
+ SharedLibraryInfo.VERSION_UNDEFINED);
+ if (info == null) {
+ continue;
+ }
+ final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
+ info, 0, userId);
+ if (dependents == null) {
+ continue;
+ }
+ for (VersionedPackage dependent : dependents) {
+ final PackageSetting ps = mSettings.mPackages.get(
+ dependent.getPackageName());
+ if (ps == null) {
+ continue;
+ }
+ ps.setOverlayPathsForLibrary(libName, overlayPaths, userId);
+ updatedPackageNames.add(dependent.getPackageName());
+ }
+ }
+ }
+
final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
ps.setOverlayPaths(overlayPaths, userId);
+
+ outUpdatedPackageNames.add(targetPackageName);
+ if (updatedPackageNames != null) {
+ outUpdatedPackageNames.addAll(updatedPackageNames);
+ }
+
return true;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 0c0b93b18f84..f1ac0afa5dfd 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -41,6 +41,7 @@ import com.android.internal.util.Preconditions;
import java.io.File;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -312,12 +313,21 @@ public abstract class PackageSettingBase extends SettingBase {
}
void setOverlayPaths(List<String> overlayPaths, int userId) {
- modifyUserState(userId).overlayPaths = overlayPaths == null ? null :
- overlayPaths.toArray(new String[overlayPaths.size()]);
+ modifyUserState(userId).setOverlayPaths(overlayPaths == null ? null :
+ overlayPaths.toArray(new String[overlayPaths.size()]));
}
String[] getOverlayPaths(int userId) {
- return readUserState(userId).overlayPaths;
+ return readUserState(userId).getOverlayPaths();
+ }
+
+ void setOverlayPathsForLibrary(String libName, List<String> overlayPaths, int userId) {
+ modifyUserState(userId).setSharedLibraryOverlayPaths(libName,
+ overlayPaths == null ? null : overlayPaths.toArray(new String[0]));
+ }
+
+ Map<String, String[]> getOverlayPathsForLibrary(int userId) {
+ return readUserState(userId).getSharedLibraryOverlayPaths();
}
/** Only use for testing. Do NOT use in production code. */
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f9a336166825..c799a52559c4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4725,6 +4725,22 @@ public final class Settings {
}
}
+ Map<String, String[]> sharedLibraryOverlayPaths =
+ ps.getOverlayPathsForLibrary(user.id);
+ if (sharedLibraryOverlayPaths != null) {
+ for (Map.Entry<String, String[]> libOverlayPaths :
+ sharedLibraryOverlayPaths.entrySet()) {
+ if (libOverlayPaths.getValue() == null) {
+ continue;
+ }
+ pw.print(prefix); pw.print(" ");
+ pw.print(libOverlayPaths.getKey()); pw.println(" overlay paths:");
+ for (String path : libOverlayPaths.getValue()) {
+ pw.print(prefix); pw.print(" "); pw.println(path);
+ }
+ }
+ }
+
String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
if (lastDisabledAppCaller != null) {
pw.print(prefix); pw.print(" lastDisabledCaller: ");
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 4555caafb478..5b6935bafe71 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -895,7 +895,7 @@ class Linker {
// android:versionCode from the framework AndroidManifest.xml.
ExtractCompileSdkVersions(asset_source->GetAssetManager());
}
- } else if (asset_source->IsPackageDynamic(entry.first)) {
+ } else if (asset_source->IsPackageDynamic(entry.first, entry.second)) {
final_table_.included_packages_[entry.first] = entry.second;
}
}
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 83e20b5833b9..897fa80ffedb 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -245,7 +245,8 @@ std::map<size_t, std::string> AssetManagerSymbolSource::GetAssignedPackageIds()
return package_map;
}
-bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const {
+bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId,
+ const std::string& package_name) const {
if (packageId == 0) {
return true;
}
@@ -253,7 +254,7 @@ bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const {
for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) {
for (const std::unique_ptr<const android::LoadedPackage>& loaded_package
: assets->GetLoadedArsc()->GetPackages()) {
- if (packageId == loaded_package->GetPackageId() && loaded_package->IsDynamic()) {
+ if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) {
return true;
}
}
@@ -328,12 +329,12 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName(
bool found = false;
ResourceId res_id = 0;
uint32_t type_spec_flags;
+ ResourceName real_name;
// There can be mangled resources embedded within other packages. Here we will
// look into each package and look-up the mangled name until we find the resource.
asset_manager_.ForEachPackage([&](const std::string& package_name, uint8_t id) -> bool {
- ResourceName real_name(name.package, name.type, name.entry);
-
+ real_name = ResourceName(name.package, name.type, name.entry);
if (package_name != name.package) {
real_name.entry = mangled_entry;
real_name.package = package_name;
@@ -353,12 +354,12 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName(
}
std::unique_ptr<SymbolTable::Symbol> s;
- if (name.type == ResourceType::kAttr) {
+ if (real_name.type == ResourceType::kAttr) {
s = LookupAttributeInTable(asset_manager_, res_id);
} else {
s = util::make_unique<SymbolTable::Symbol>();
s->id = res_id;
- s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id());
+ s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package);
}
if (s) {
@@ -406,7 +407,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById(
} else {
s = util::make_unique<SymbolTable::Symbol>();
s->id = id;
- s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id());
+ s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package);
}
if (s) {
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index 6997cd6714a8..06eaf63ad442 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -194,7 +194,7 @@ class AssetManagerSymbolSource : public ISymbolSource {
bool AddAssetPath(const android::StringPiece& path);
std::map<size_t, std::string> GetAssignedPackageIds() const;
- bool IsPackageDynamic(uint32_t packageId) const;
+ bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const;
std::unique_ptr<SymbolTable::Symbol> FindByName(
const ResourceName& name) override;