Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -53,6 +56,8 @@ public class PlayStoreDeferredComponentManager implements DeferredComponentManag
private @NonNull SparseArray<String> sessionIdToState;
private @NonNull Map<String, Integer> nameToSessionId;

protected @NonNull SparseArray<String> loadingUnitIdToModuleNames;

private FeatureInstallStateUpdatedListener listener;

private class FeatureInstallStateUpdatedListener implements SplitInstallStateUpdatedListener {
Expand Down Expand Up @@ -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) {
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -410,7 +448,9 @@ public boolean uninstallDeferredComponent(int loadingUnitId, String moduleName)
List<String> 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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));
}
}