diff options
| -rw-r--r-- | AconfigFlags.bp | 7 | ||||
| -rw-r--r-- | core/java/android/app/ResourcesManager.java | 137 | ||||
| -rw-r--r-- | core/java/android/content/res/AssetManager.java | 10 | ||||
| -rw-r--r-- | core/java/android/content/res/Resources.java | 8 | ||||
| -rw-r--r-- | core/java/android/content/res/ResourcesImpl.java | 10 | ||||
| -rw-r--r-- | core/java/android/webkit/WebViewDelegate.java | 8 | ||||
| -rw-r--r-- | core/java/android/webkit/WebViewFactory.java | 11 | ||||
| -rw-r--r-- | core/tests/coretests/Android.bp | 1 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/content/res/ResourcesManagerTest.java | 132 |
9 files changed, 319 insertions, 5 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index eed248bcd2b9..7317ecd82bd3 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -492,6 +492,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "android.content.res.flags-aconfig-java-host", + aconfig_declarations: "android.content.res.flags-aconfig", + host_supported: true, + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Media BetterTogether aconfig_declarations { name: "com.android.media.flags.bettertogether-aconfig", diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 625526047212..8b84f062b7b5 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -124,6 +124,32 @@ public class ResourcesManager { */ private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList()); + private final ArrayMap<String, SharedLibraryAssets> mSharedLibAssetsMap = + new ArrayMap<>(); + + /** + * The internal function to register the resources paths of a package (e.g. a shared library). + * This will collect the package resources' paths from its ApplicationInfo and add them to all + * existing and future contexts while the application is running. + */ + public void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) { + SharedLibraryAssets sharedLibAssets = new SharedLibraryAssets(appInfo.sourceDir, + appInfo.splitSourceDirs, appInfo.sharedLibraryFiles, + appInfo.resourceDirs, appInfo.overlayPaths); + + synchronized (mLock) { + if (mSharedLibAssetsMap.containsKey(uniqueId)) { + Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId + + " has already been registered, this is a no-op."); + return; + } + mSharedLibAssetsMap.put(uniqueId, sharedLibAssets); + appendLibAssetsLocked(sharedLibAssets.getAllAssetPaths()); + Slog.v(TAG, "The following resources' paths have been added: " + + Arrays.toString(sharedLibAssets.getAllAssetPaths())); + } + } + private static class ApkKey { public final String path; public final boolean sharedLib; @@ -278,6 +304,21 @@ public class ResourcesManager { public ResourcesManager() { } + /** + * Inject a customized ResourcesManager instance for testing, return the old ResourcesManager + * instance. + */ + @UnsupportedAppUsage + @VisibleForTesting + public static ResourcesManager setInstance(ResourcesManager resourcesManager) { + synchronized (ResourcesManager.class) { + ResourcesManager oldResourceManager = sResourcesManager; + sResourcesManager = resourcesManager; + return oldResourceManager; + } + + } + @UnsupportedAppUsage public static ResourcesManager getInstance() { synchronized (ResourcesManager.class) { @@ -1480,6 +1521,56 @@ public class ResourcesManager { } } + private void appendLibAssetsLocked(String[] libAssets) { + synchronized (mLock) { + // Record which ResourcesImpl need updating + // (and what ResourcesKey they should update to). + final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); + + final int implCount = mResourceImpls.size(); + for (int i = 0; i < implCount; i++) { + final ResourcesKey key = mResourceImpls.keyAt(i); + final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); + final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; + if (impl == null) { + Slog.w(TAG, "Found a ResourcesImpl which is null, skip it and continue to " + + "append shared library assets for next ResourcesImpl."); + continue; + } + + var newDirs = new ArrayList<String>(); + var dirsSet = new ArraySet<String>(); + if (key.mLibDirs != null) { + final int dirsLength = key.mLibDirs.length; + for (int k = 0; k < dirsLength; k++) { + newDirs.add(key.mLibDirs[k]); + dirsSet.add(key.mLibDirs[k]); + } + } + final int assetsLength = libAssets.length; + for (int j = 0; j < assetsLength; j++) { + if (dirsSet.add(libAssets[j])) { + newDirs.add(libAssets[j]); + } + } + String[] newLibAssets = newDirs.toArray(new String[0]); + if (!Arrays.equals(newLibAssets, key.mLibDirs)) { + updatedResourceKeys.put(impl, new ResourcesKey( + key.mResDir, + key.mSplitResDirs, + key.mOverlayPaths, + newLibAssets, + key.mDisplayId, + key.mOverrideConfiguration, + key.mCompatInfo, + key.mLoaders)); + } + } + + redirectResourcesToNewImplLocked(updatedResourceKeys); + } + } + private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs, @NonNull final ApplicationInfo appInfo) { try { @@ -1689,4 +1780,50 @@ public class ResourcesManager { } } } + + public static class SharedLibraryAssets{ + private final String[] mAssetPaths; + + SharedLibraryAssets(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles, + String[] resourceDirs, String[] overlayPaths) { + mAssetPaths = collectAssetPaths(sourceDir, splitSourceDirs, sharedLibraryFiles, + resourceDirs, overlayPaths); + } + + private @NonNull String[] collectAssetPaths(String sourceDir, String[] splitSourceDirs, + String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) { + final String[][] inputLists = { + splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths + }; + + final ArraySet<String> assetPathSet = new ArraySet<>(); + final List<String> assetPathList = new ArrayList<>(); + if (sourceDir != null) { + assetPathSet.add(sourceDir); + assetPathList.add(sourceDir); + } + + for (int i = 0; i < inputLists.length; i++) { + if (inputLists[i] != null) { + for (int j = 0; j < inputLists[i].length; j++) { + if (assetPathSet.add(inputLists[i][j])) { + assetPathList.add(inputLists[i][j]); + } + } + } + } + return assetPathList.toArray(new String[0]); + } + + /** + * @return all the asset paths of this collected in this class. + */ + public @NonNull String[] getAllAssetPaths() { + return mAssetPaths; + } + } + + public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() { + return new ArrayMap<>(mSharedLibAssetsMap); + } } diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index d259e9755a41..273e40a21bb2 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -471,6 +471,16 @@ public final class AssetManager implements AutoCloseable { return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/); } + /** + * @hide + */ + public void addSharedLibraryPaths(@NonNull String[] paths) { + final int length = paths.length; + for (int i = 0; i < length; i++) { + addAssetPathInternal(paths[i], false, true); + } + } + private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) { Objects.requireNonNull(path, "path"); synchronized (this) { diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 7fba3e890ec6..1f5f88f51d55 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -43,6 +43,7 @@ import android.annotation.StyleRes; import android.annotation.StyleableRes; import android.annotation.XmlRes; import android.app.Application; +import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.ActivityInfo; @@ -2854,6 +2855,11 @@ public class Resources { @FlaggedApi(android.content.res.Flags.FLAG_REGISTER_RESOURCE_PATHS) public static void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) { - throw new UnsupportedOperationException("The implementation has not been done yet."); + if (Flags.registerResourcePaths()) { + ResourcesManager.getInstance().registerResourcePaths(uniqueId, appInfo); + } else { + throw new UnsupportedOperationException("Flag " + Flags.FLAG_REGISTER_RESOURCE_PATHS + + " is disabled."); + } } } diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 079c2c1ab7c9..8d045aaf4d81 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -29,6 +29,7 @@ import android.annotation.StyleRes; import android.annotation.StyleableRes; import android.app.LocaleConfig; import android.app.ResourcesManager; +import android.app.ResourcesManager.SharedLibraryAssets; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; @@ -47,6 +48,7 @@ import android.os.Build; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.os.Trace; +import android.util.ArrayMap; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -197,6 +199,14 @@ public class ResourcesImpl { public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) { mAssets = assets; + if (Flags.registerResourcePaths()) { + ArrayMap<String, SharedLibraryAssets> sharedLibMap = + ResourcesManager.getInstance().getSharedLibAssetsMap(); + final int size = sharedLibMap.size(); + for (int i = 0; i < size; i++) { + assets.addSharedLibraryPaths(sharedLibMap.valueAt(i).getAllAssetPaths()); + } + } mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java index 3fc0a305c8e6..8501474b70a6 100644 --- a/core/java/android/webkit/WebViewDelegate.java +++ b/core/java/android/webkit/WebViewDelegate.java @@ -175,8 +175,16 @@ public final class WebViewDelegate { /** * Adds the WebView asset path to {@link android.content.res.AssetManager}. + * If {@link android.content.res.Flags#FLAG_REGISTER_RESOURCE_PATHS} is enabled, this function + * will be a no-op because the asset paths appending work will only be handled by + * {@link android.content.res.Resources#registerResourcePaths(String, ApplicationInfo)}, + * otherwise it behaves the old way. */ public void addWebViewAssetPath(Context context) { + if (android.content.res.Flags.registerResourcePaths()) { + return; + } + final String[] newAssetPaths = WebViewFactory.getLoadedPackageInfo().applicationInfo.getAllApkPaths(); final ApplicationInfo appInfo = context.getApplicationInfo(); diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index c748a57dce74..01fdd1d4038c 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -30,6 +30,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; +import android.content.res.Resources; import android.os.Build; import android.os.RemoteException; import android.os.ServiceManager; @@ -544,8 +545,14 @@ public final class WebViewFactory { Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()"); try { sTimestamps.mAddAssetsStart = SystemClock.uptimeMillis(); - for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) { - initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath); + if (android.content.res.Flags.registerResourcePaths()) { + Resources.registerResourcePaths(webViewContext.getPackageName(), + webViewContext.getApplicationInfo()); + } else { + for (String newAssetPath : webViewContext.getApplicationInfo() + .getAllApkPaths()) { + initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath); + } } sTimestamps.mAddAssetsEnd = sTimestamps.mGetClassLoaderStart = SystemClock.uptimeMillis(); diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index e72beee71e50..24031cad0a3e 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -101,6 +101,7 @@ android_test { "flickerlib-trace_processor_shell", "mockito-target-extended-minus-junit4", "TestParameterInjector", + "android.content.res.flags-aconfig-java", ], libs: [ diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java index 4a9cb7180a3f..0c1e8793bfc9 100644 --- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java +++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java @@ -18,34 +18,52 @@ package android.content.res; import android.annotation.NonNull; import android.app.ResourcesManager; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.os.Binder; import android.os.LocaleList; import android.platform.test.annotations.Postsubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.Display; import android.view.DisplayAdjustments; +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; @Postsubmit +@RunWith(AndroidJUnit4.class) public class ResourcesManagerTest extends TestCase { private static final int SECONDARY_DISPLAY_ID = 1; private static final String APP_ONE_RES_DIR = "app_one.apk"; private static final String APP_ONE_RES_SPLIT_DIR = "app_one_split.apk"; private static final String APP_TWO_RES_DIR = "app_two.apk"; private static final String LIB_RES_DIR = "lib.apk"; + private static final String TEST_LIB = "com.android.frameworks.coretests.bdr_helper_app1"; private ResourcesManager mResourcesManager; private Map<Integer, DisplayMetrics> mDisplayMetricsMap; + private PackageManager mPackageManager; - @Override - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { super.setUp(); mDisplayMetricsMap = new HashMap<>(); @@ -93,8 +111,14 @@ public class ResourcesManagerTest extends TestCase { return mDisplayMetricsMap.get(displayId); } }; + + mPackageManager = InstrumentationRegistry.getContext().getPackageManager(); } + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Test @SmallTest public void testMultipleCallsWithIdenticalParametersCacheReference() { Resources resources = mResourcesManager.getResources( @@ -109,6 +133,7 @@ public class ResourcesManagerTest extends TestCase { assertSame(resources.getImpl(), newResources.getImpl()); } + @Test @SmallTest public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() { Resources resources = mResourcesManager.getResources( @@ -125,6 +150,7 @@ public class ResourcesManagerTest extends TestCase { assertNotSame(resources, newResources); } + @Test @SmallTest public void testAddingASplitCreatesANewImpl() { Resources resources1 = mResourcesManager.getResources( @@ -142,6 +168,7 @@ public class ResourcesManagerTest extends TestCase { assertNotSame(resources1.getImpl(), resources2.getImpl()); } + @Test @SmallTest public void testUpdateConfigurationUpdatesAllAssetManagers() { Resources resources1 = mResourcesManager.getResources( @@ -187,6 +214,7 @@ public class ResourcesManagerTest extends TestCase { assertEquals(expectedConfig, resources3.getConfiguration()); } + @Test @SmallTest public void testTwoActivitiesWithIdenticalParametersShareImpl() { Binder activity1 = new Binder(); @@ -208,6 +236,7 @@ public class ResourcesManagerTest extends TestCase { assertSame(resources1.getImpl(), resources2.getImpl()); } + @Test @SmallTest public void testThemesGetUpdatedWithNewImpl() { Binder activity1 = new Binder(); @@ -237,6 +266,7 @@ public class ResourcesManagerTest extends TestCase { assertTrue(value.data != 0); } + @Test @SmallTest public void testMultipleResourcesForOneActivityGetUpdatedWhenActivityBaseUpdates() { Binder activity1 = new Binder(); @@ -286,6 +316,7 @@ public class ResourcesManagerTest extends TestCase { assertEquals(expectedConfig2, resources2.getConfiguration()); } + @Test @SmallTest public void testChangingActivityDisplayDoesntOverrideDisplayRequestedByResources() { Binder activity = new Binder(); @@ -322,4 +353,101 @@ public class ResourcesManagerTest extends TestCase { assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels, defaultDisplayResources.getDisplayMetrics().widthPixels); } + + @Test + @SmallTest + @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS) + public void testExistingResourcesAfterResourcePathsRegistration() + throws PackageManager.NameNotFoundException { + // Inject ResourcesManager instance from this test to the ResourcesManager class so that all + // the static method can interact with this test smoothly. + ResourcesManager oriResourcesManager = ResourcesManager.getInstance(); + ResourcesManager.setInstance(mResourcesManager); + + // Create a Resources before register resources' paths for a package. + Resources resources = mResourcesManager.getResources( + null, APP_ONE_RES_DIR, null, null, null, null, null, null, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); + assertNotNull(resources); + ResourcesImpl oriResImpl = resources.getImpl(); + + ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0); + Resources.registerResourcePaths(TEST_LIB, appInfo); + + assertNotSame(oriResImpl, resources.getImpl()); + + String[] resourcePaths = appInfo.getAllApkPaths(); + resourcePaths = removeDuplicates(resourcePaths); + ApkAssets[] loadedAssets = resources.getAssets().getApkAssets(); + assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets)); + + // Package resources' paths should be cached in ResourcesManager. + assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance() + .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths())); + + // Revert the ResourcesManager instance back. + ResourcesManager.setInstance(oriResourcesManager); + } + + @Test + @SmallTest + @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS) + public void testNewResourcesAfterResourcePathsRegistration() + throws PackageManager.NameNotFoundException { + // Inject ResourcesManager instance from this test to the ResourcesManager class so that all + // the static method can interact with this test smoothly. + ResourcesManager oriResourcesManager = ResourcesManager.getInstance(); + ResourcesManager.setInstance(mResourcesManager); + + ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0); + Resources.registerResourcePaths(TEST_LIB, appInfo); + + // Create a Resources after register resources' paths for a package. + Resources resources = mResourcesManager.getResources( + null, APP_ONE_RES_DIR, null, null, null, null, null, null, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); + assertNotNull(resources); + + String[] resourcePaths = appInfo.getAllApkPaths(); + resourcePaths = removeDuplicates(resourcePaths); + ApkAssets[] loadedAssets = resources.getAssets().getApkAssets(); + assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets)); + + // Package resources' paths should be cached in ResourcesManager. + assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance() + .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths())); + + // Revert the ResourcesManager instance back. + ResourcesManager.setInstance(oriResourcesManager); + } + + private static boolean allResourcePathsLoaded(String[] resourcePaths, ApkAssets[] loadedAsset) { + for (int i = 0; i < resourcePaths.length; i++) { + if (!resourcePaths[i].endsWith(".apk")) { + continue; + } + boolean found = false; + for (int j = 0; j < loadedAsset.length; j++) { + if (loadedAsset[j].getAssetPath().equals(resourcePaths[i])) { + found = true; + } + } + if (!found) { + return false; + } + } + return true; + } + + private static String[] removeDuplicates(String[] paths) { + var pathList = new ArrayList<String>(); + var pathSet = new ArraySet<String>(); + final int pathsLen = paths.length; + for (int i = 0; i < pathsLen; i++) { + if (pathSet.add(paths[i])) { + pathList.add(paths[i]); + } + } + return pathList.toArray(new String[0]); + } } |