diff --git a/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java b/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java index 22b25b34b3ad..829108651a0d 100644 --- a/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java @@ -6,9 +6,12 @@ import android.annotation.SuppressLint; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetManager; import android.os.Build; +import android.os.Bundle; import android.util.SparseArray; import android.util.SparseIntArray; import androidx.annotation.NonNull; @@ -53,6 +56,8 @@ public class PlayStoreDeferredComponentManager implements DeferredComponentManag private @NonNull SparseArray sessionIdToState; private @NonNull Map nameToSessionId; + protected @NonNull SparseArray loadingUnitIdToModuleNames; + private FeatureInstallStateUpdatedListener listener; private class FeatureInstallStateUpdatedListener implements SplitInstallStateUpdatedListener { @@ -202,6 +207,9 @@ public PlayStoreDeferredComponentManager( sessionIdToLoadingUnitId = new SparseIntArray(); sessionIdToState = new SparseArray<>(); nameToSessionId = new HashMap<>(); + + loadingUnitIdToModuleNames = new SparseArray<>(); + initLoadingUnitMappingToModuleNames(); } public void setJNI(@NonNull FlutterJNI flutterJNI) { @@ -222,19 +230,49 @@ public void setDeferredComponentChannel(DeferredComponentChannel channel) { this.channel = channel; } - private String loadingUnitIdToModuleName(int loadingUnitId) { - // Loading unit id to module name mapping stored in android Strings - // resources. - int moduleNameIdentifier = - context - .getResources() - .getIdentifier("loadingUnit" + loadingUnitId, "string", context.getPackageName()); - return context.getResources().getString(moduleNameIdentifier); + @NonNull + private ApplicationInfo getApplicationInfo() { + try { + return context + .getPackageManager() + .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + } catch (NameNotFoundException e) { + throw new RuntimeException(e); + } + } + + // Obtain and parses the metadata string. An example encoded string is: + // + // "2:module2,3:module3,4:module1" + // + // Where loading unit 2 is included in module2, loading unit 3 is + // included in module3, and loading unit 4 is included in module1. + private void initLoadingUnitMappingToModuleNames() { + String mappingKey = DeferredComponentManager.class.getName() + ".loadingUnitMapping"; + ApplicationInfo applicationInfo = getApplicationInfo(); + if (applicationInfo != null) { + Bundle metaData = applicationInfo.metaData; + if (metaData != null) { + String rawMappingString = metaData.getString(mappingKey, null); + if (rawMappingString == null) { + Log.e( + TAG, + "No loading unit to dynamic feature module name found. Ensure '" + + mappingKey + + "' is defined in the base module's AndroidManifest."); + } else { + for (String entry : rawMappingString.split(",")) { + String[] splitEntry = entry.split(":"); + loadingUnitIdToModuleNames.put(Integer.parseInt(splitEntry[0]), splitEntry[1]); + } + } + } + } } public void installDeferredComponent(int loadingUnitId, String moduleName) { String resolvedModuleName = - moduleName != null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); + moduleName != null ? moduleName : loadingUnitIdToModuleNames.get(loadingUnitId); if (resolvedModuleName == null) { Log.e( TAG, @@ -297,7 +335,7 @@ public void installDeferredComponent(int loadingUnitId, String moduleName) { public String getDeferredComponentInstallState(int loadingUnitId, String moduleName) { String resolvedModuleName = - moduleName != null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); + moduleName != null ? moduleName : loadingUnitIdToModuleNames.get(loadingUnitId); if (resolvedModuleName == null) { Log.e( TAG, @@ -400,7 +438,7 @@ public void loadDartLibrary(int loadingUnitId, String moduleName) { public boolean uninstallDeferredComponent(int loadingUnitId, String moduleName) { String resolvedModuleName = - moduleName != null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); + moduleName != null ? moduleName : loadingUnitIdToModuleNames.get(loadingUnitId); if (resolvedModuleName == null) { Log.e( TAG, @@ -410,7 +448,9 @@ public boolean uninstallDeferredComponent(int loadingUnitId, String moduleName) List modulesToUninstall = new ArrayList<>(); modulesToUninstall.add(resolvedModuleName); splitInstallManager.deferredUninstall(modulesToUninstall); - sessionIdToState.delete(nameToSessionId.get(resolvedModuleName)); + if (nameToSessionId.get(resolvedModuleName) != null) { + sessionIdToState.delete(nameToSessionId.get(resolvedModuleName)); + } return true; } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java index b5e98b27e528..3157e9861f25 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java @@ -5,6 +5,7 @@ package io.flutter.embedding.engine.deferredcomponents; import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -19,6 +20,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetManager; import android.os.Bundle; +import android.util.SparseArray; import androidx.annotation.NonNull; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.loader.ApplicationInfoLoader; @@ -70,6 +72,9 @@ public void deferredComponentInstallFailure( private class TestPlayStoreDeferredComponentManager extends PlayStoreDeferredComponentManager { public TestPlayStoreDeferredComponentManager(Context context, FlutterJNI jni) { super(context, jni); + loadingUnitIdToModuleNames = new SparseArray<>(); + loadingUnitIdToModuleNames.put(5, "FakeModuleName5"); + loadingUnitIdToModuleNames.put(2, "FakeModuleName2"); } @Override @@ -223,4 +228,16 @@ public void stateGetterReturnsUnknowByDefault() throws NameNotFoundException { new TestPlayStoreDeferredComponentManager(spyContext, jni); assertEquals(playStoreManager.getDeferredComponentInstallState(-1, "invalidName"), "unknown"); } + + @Test + public void loadingUnitMappingFindsMatch() throws NameNotFoundException { + TestFlutterJNI jni = new TestFlutterJNI(); + Context spyContext = spy(RuntimeEnvironment.application); + TestPlayStoreDeferredComponentManager playStoreManager = + new TestPlayStoreDeferredComponentManager(spyContext, jni); + + assertTrue(playStoreManager.uninstallDeferredComponent(5, null)); + assertTrue(playStoreManager.uninstallDeferredComponent(2, null)); + assertFalse(playStoreManager.uninstallDeferredComponent(3, null)); + } }