Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0a856ff1 authored by Yurii Zubrytskyi's avatar Yurii Zubrytskyi
Browse files

[res] Remove global overlays from registered lib paths

When calling Resources.registerResourcePaths(), the passed
application info object contains all asset paths for the lib,
including global system overlays, theming FRROs and so on.

This change filters out those that are common with the current
app's assets - they are already a part of all resource objects
and don't need to be registered and added separately.

This fixes an issue of retaining global overlay paths that got
updated after the register...() function call

Bug: 383713434
Test: unit tests + resources CTS
Flag: EXEMPT bugfix
Change-Id: Ife713e364c3e4b68a9e8c480f3773df40a0cc39c
parent c74a0961
Loading
Loading
Loading
Loading
+29 −3
Original line number Diff line number Diff line
@@ -160,7 +160,9 @@ public class ResourcesManager {
            return;
        }

        final var sharedLibAssets = new SharedLibraryAssets(appInfo);
        final var application = ActivityThread.currentActivityThread().getApplication();
        final var currentAppInfo = application != null ? application.getApplicationInfo() : null;
        final var sharedLibAssets = new SharedLibraryAssets(appInfo, currentAppInfo);
        synchronized (mLock) {
            if (mSharedLibAssetsMap.containsKey(uniqueId)) {
                Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId
@@ -191,7 +193,7 @@ public class ResourcesManager {

        synchronized (mLock) {
            size = mSharedLibAssetsMap.size();
            if (assets == AssetManager.getSystem()) {
            if (size == 0 || assets == AssetManager.getSystem()) {
                return new Pair<>(assets, size);
            }
            collector = new PathCollector(resourcesKeyFromAssets(assets));
@@ -1998,10 +2000,30 @@ public class ResourcesManager {
    public static class SharedLibraryAssets {
        private final ResourcesKey mResourcesKey;

        private SharedLibraryAssets(ApplicationInfo appInfo) {
        private SharedLibraryAssets(@NonNull ApplicationInfo appInfo,
                @Nullable ApplicationInfo baseAppInfo) {
            // We're loading all library's files as shared libs, regardless where they are in
            // its own ApplicationInfo.
            final var collector = new PathCollector(null);
            // Pre-populate the collector's sets with the base app paths so they all get filtered
            // out if they exist in the info that's being registered as well.
            // Note: if someone is registering their own appInfo, we can't filter out anything
            // here and this means any asset path changes are going to be ignored.
            if (baseAppInfo != null && !baseAppInfo.sourceDir.equals(appInfo.sourceDir)) {
                collector.libsSet.add(baseAppInfo.sourceDir);
                if (baseAppInfo.splitSourceDirs != null) {
                    collector.libsSet.addAll(Arrays.asList(baseAppInfo.splitSourceDirs));
                }
                if (baseAppInfo.sharedLibraryFiles != null) {
                    collector.libsSet.addAll(Arrays.asList(baseAppInfo.sharedLibraryFiles));
                }
                if (baseAppInfo.resourceDirs != null) {
                    collector.overlaysSet.addAll(Arrays.asList(baseAppInfo.resourceDirs));
                }
                if (baseAppInfo.overlayPaths != null) {
                    collector.overlaysSet.addAll(Arrays.asList(baseAppInfo.overlayPaths));
                }
            }
            PathCollector.appendNewPath(appInfo.sourceDir, collector.libsSet,
                    collector.orderedLibs);
            PathCollector.appendAllNewPaths(appInfo.splitSourceDirs, collector.libsSet,
@@ -2013,6 +2035,10 @@ public class ResourcesManager {
            PathCollector.appendAllNewPaths(appInfo.overlayPaths, collector.overlaysSet,
                    collector.orderedOverlays);
            mResourcesKey = collector.collectedKey();

            if (DEBUG) {
                Log.i(TAG, "Created shared library assets: " + mResourcesKey);
            }
        }

        /**
+44 −65
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ 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;
@@ -43,12 +42,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

@@ -66,6 +65,7 @@ public class ResourcesManagerTest {
    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();

    private ResourcesManager mResourcesManager;
    private ResourcesManager mOldResourcesManager;
    private Map<Integer, DisplayMetrics> mDisplayMetricsMap;

    @Before
@@ -115,6 +115,12 @@ public class ResourcesManagerTest {
                return mDisplayMetricsMap.get(displayId);
            }
        };
        mOldResourcesManager = ResourcesManager.setInstance(mResourcesManager);
    }

    @After
    public void tearDown() {
        ResourcesManager.setInstance(mOldResourcesManager);
    }

    private PackageManager getPackageManager() {
@@ -363,11 +369,6 @@ public class ResourcesManagerTest {
    @DisabledOnRavenwood(blockedBy = PackageManager.class)
    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,
@@ -380,16 +381,11 @@ public class ResourcesManagerTest {

        assertNotSame(oriResImpl, resources.getImpl());

        String[] resourcePaths = appInfo.getAllApkPaths();
        resourcePaths = removeDuplicates(resourcePaths);
        ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
        assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
        assertTrue(containsPath(TEST_LIB, loadedAssets));

        // Package resources' paths should be cached in ResourcesManager.
        assertNotNull(ResourcesManager.getInstance().getRegisteredResourcePaths().get(TEST_LIB));

        // Revert the ResourcesManager instance back.
        ResourcesManager.setInstance(oriResourcesManager);
    }

    @Test
@@ -398,11 +394,6 @@ public class ResourcesManagerTest {
    @DisabledOnRavenwood(blockedBy = PackageManager.class)
    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 = getPackageManager().getApplicationInfo(TEST_LIB, 0);
        Resources.registerResourcePaths(TEST_LIB, appInfo);

@@ -412,15 +403,11 @@ public class ResourcesManagerTest {
                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
        assertNotNull(resources);

        String[] resourcePaths = appInfo.getAllApkPaths();
        resourcePaths = removeDuplicates(resourcePaths);
        ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
        assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
        assertTrue(containsPath(TEST_LIB, loadedAssets));

        // Package resources' paths should be cached in ResourcesManager.
        assertNotNull(ResourcesManager.getInstance().getRegisteredResourcePaths().get(TEST_LIB));
        // Revert the ResourcesManager instance back.
        ResourcesManager.setInstance(oriResourcesManager);
    }

    @Test
@@ -429,11 +416,6 @@ public class ResourcesManagerTest {
    @DisabledOnRavenwood(blockedBy = PackageManager.class)
    public void testExistingResourcesCreatedByConstructorAfterResourcePathsRegistration()
            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 through constructor directly before register resources' paths.
        final DisplayMetrics metrics = new DisplayMetrics();
        metrics.setToDefaults();
@@ -449,15 +431,11 @@ public class ResourcesManagerTest {

        assertNotSame(oriResImpl, resources.getImpl());

        String[] resourcePaths = appInfo.getAllApkPaths();
        resourcePaths = removeDuplicates(resourcePaths);
        ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
        assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
        assertTrue(containsPath(TEST_LIB, loadedAssets));

        // Package resources' paths should be cached in ResourcesManager.
        assertNotNull(ResourcesManager.getInstance().getRegisteredResourcePaths().get(TEST_LIB));
        // Revert the ResourcesManager instance back.
        ResourcesManager.setInstance(oriResourcesManager);
    }

    @Test
@@ -509,9 +487,6 @@ public class ResourcesManagerTest {
    @DisabledOnRavenwood(blockedBy = PackageManager.class)
    public void testNewResourcesWithOutdatedImplAfterResourcePathsRegistration()
            throws PackageManager.NameNotFoundException {
        ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
        ResourcesManager.setInstance(mResourcesManager);

        Resources old_resources = mResourcesManager.getResources(
                null, APP_ONE_RES_DIR, null, null, null, null, null, null,
                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
@@ -532,44 +507,48 @@ public class ResourcesManagerTest {
        // which has proper asset paths appended.
        assertNotSame(oldImpl, resources.getImpl());

        String[] resourcePaths = appInfo.getAllApkPaths();
        resourcePaths = removeDuplicates(resourcePaths);
        ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
        assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
        assertTrue(containsPath(TEST_LIB, loadedAssets));

        // Package resources' paths should be cached in ResourcesManager.
        assertNotNull(ResourcesManager.getInstance().getRegisteredResourcePaths().get(TEST_LIB));
        // 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;

    @Test
    @SmallTest
    @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
    @DisabledOnRavenwood(blockedBy = PackageManager.class)
    public void testRegisteringOwnApplicationInfo() {
        Resources old_resources = mResourcesManager.getResources(
                null, APP_ONE_RES_DIR, null, null, null, null, null, null,
                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
        assertNotNull(old_resources);
        ResourcesImpl oldImpl = old_resources.getImpl();

        ApplicationInfo appInfo =
                InstrumentationRegistry.getInstrumentation().getContext().getApplicationInfo();
        Resources.registerResourcePaths(TEST_LIB, appInfo);

        // Create another resources with identical parameters.
        Resources resources = mResourcesManager.getResources(
                null, APP_ONE_RES_DIR, null, null, null, null, null, null,
                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
        assertNotNull(resources);
        assertNotSame(oldImpl, resources.getImpl());

        ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
        assertTrue(containsPath(appInfo.sourceDir, loadedAssets));

        assertNotNull(ResourcesManager.getInstance().getRegisteredResourcePaths().get(TEST_LIB));
    }

    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]);
    private static boolean containsPath(String substring, ApkAssets[] assets) {
        for (final var asset : assets) {
            if (asset.getAssetPath().contains(substring)) {
                return true;
            }
        }
        return pathList.toArray(new String[0]);
        return false;
    }
}