45 private static final String TAG =
"PlayStoreDeferredComponentManager";
50 private @NonNull SplitInstallManager splitInstallManager;
52 private @Nullable DeferredComponentChannel channel;
53 private @NonNull
Context context;
54 private @NonNull FlutterApplicationInfo flutterApplicationInfo;
57 private @NonNull SparseArray<String> sessionIdToName;
58 private @NonNull SparseIntArray sessionIdToLoadingUnitId;
59 private @NonNull SparseArray<String> sessionIdToState;
60 private @NonNull Map<String, Integer> nameToSessionId;
65 private FeatureInstallStateUpdatedListener listener;
67 private class FeatureInstallStateUpdatedListener
implements SplitInstallStateUpdatedListener {
68 @SuppressLint(
"DefaultLocale")
69 public void onStateUpdate(@NonNull SplitInstallSessionState
state) {
70 int sessionId =
state.sessionId();
71 if (sessionIdToName.get(sessionId) !=
null) {
72 switch (
state.status()) {
73 case SplitInstallSessionStatus.FAILED:
78 "Module \"%s\" (sessionId %d) install failed with: %s",
79 sessionIdToName.get(sessionId), sessionId,
state.errorCode()));
80 flutterJNI.deferredComponentInstallFailure(
81 sessionIdToLoadingUnitId.get(sessionId),
82 "Module install failed with " +
state.errorCode(),
84 if (channel !=
null) {
85 channel.completeInstallError(
86 sessionIdToName.get(sessionId),
87 "Android Deferred Component failed to install.");
89 sessionIdToName.delete(sessionId);
90 sessionIdToLoadingUnitId.delete(sessionId);
91 sessionIdToState.put(sessionId,
"failed");
94 case SplitInstallSessionStatus.INSTALLED:
99 "Module \"%s\" (sessionId %d) install successfully.",
100 sessionIdToName.get(sessionId), sessionId));
101 loadAssets(sessionIdToLoadingUnitId.get(sessionId), sessionIdToName.get(sessionId));
106 if (sessionIdToLoadingUnitId.get(sessionId) > 0) {
108 sessionIdToLoadingUnitId.get(sessionId), sessionIdToName.get(sessionId));
110 if (channel !=
null) {
111 channel.completeInstallSuccess(sessionIdToName.get(sessionId));
113 sessionIdToName.delete(sessionId);
114 sessionIdToLoadingUnitId.delete(sessionId);
115 sessionIdToState.put(sessionId,
"installed");
118 case SplitInstallSessionStatus.CANCELED:
123 "Module \"%s\" (sessionId %d) install canceled.",
124 sessionIdToName.get(sessionId), sessionId));
125 if (channel !=
null) {
126 channel.completeInstallError(
127 sessionIdToName.get(sessionId),
128 "Android Deferred Component installation canceled.");
130 sessionIdToName.delete(sessionId);
131 sessionIdToLoadingUnitId.delete(sessionId);
132 sessionIdToState.put(sessionId,
"cancelled");
135 case SplitInstallSessionStatus.CANCELING:
140 "Module \"%s\" (sessionId %d) install canceling.",
141 sessionIdToName.get(sessionId), sessionId));
142 sessionIdToState.put(sessionId,
"canceling");
145 case SplitInstallSessionStatus.PENDING:
150 "Module \"%s\" (sessionId %d) install pending.",
151 sessionIdToName.get(sessionId), sessionId));
152 sessionIdToState.put(sessionId,
"pending");
155 case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION:
160 "Module \"%s\" (sessionId %d) install requires user confirmation.",
161 sessionIdToName.get(sessionId), sessionId));
162 sessionIdToState.put(sessionId,
"requiresUserConfirmation");
165 case SplitInstallSessionStatus.DOWNLOADING:
170 "Module \"%s\" (sessionId %d) downloading.",
171 sessionIdToName.get(sessionId), sessionId));
172 sessionIdToState.put(sessionId,
"downloading");
175 case SplitInstallSessionStatus.DOWNLOADED:
180 "Module \"%s\" (sessionId %d) downloaded.",
181 sessionIdToName.get(sessionId), sessionId));
182 sessionIdToState.put(sessionId,
"downloaded");
185 case SplitInstallSessionStatus.INSTALLING:
190 "Module \"%s\" (sessionId %d) installing.",
191 sessionIdToName.get(sessionId), sessionId));
192 sessionIdToState.put(sessionId,
"installing");
204 this.context = context;
205 this.flutterJNI = flutterJNI;
206 this.flutterApplicationInfo = ApplicationInfoLoader.load(context);
207 splitInstallManager = SplitInstallManagerFactory.create(context);
208 listener =
new FeatureInstallStateUpdatedListener();
209 splitInstallManager.registerListener(listener);
210 sessionIdToName =
new SparseArray<>();
211 sessionIdToLoadingUnitId =
new SparseIntArray();
212 sessionIdToState =
new SparseArray<>();
213 nameToSessionId =
new HashMap<>();
217 initLoadingUnitMappingToComponentNames();
221 this.flutterJNI = flutterJNI;
224 private boolean verifyJNI() {
225 if (flutterJNI ==
null) {
228 "No FlutterJNI provided. `setJNI` must be called on the DeferredComponentManager before attempting to load dart libraries or invoking with platform channels.");
235 this.channel = channel;
239 private ApplicationInfo getApplicationInfo() {
243 .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
244 }
catch (NameNotFoundException e) {
245 throw new RuntimeException(e);
259 private void initLoadingUnitMappingToComponentNames() {
260 String mappingKey = DeferredComponentManager.class.getName() +
".loadingUnitMapping";
261 ApplicationInfo applicationInfo = getApplicationInfo();
262 if (applicationInfo !=
null) {
263 Bundle metaData = applicationInfo.metaData;
264 if (metaData !=
null) {
265 String rawMappingString = metaData.getString(
MAPPING_KEY,
null);
266 if (rawMappingString ==
null) {
269 "No loading unit to dynamic feature module name found. Ensure '"
271 +
"' is defined in the base module's AndroidManifest.");
274 if (rawMappingString.equals(
"")) {
278 for (String entry : rawMappingString.split(
",")) {
280 String[] splitEntry = entry.split(
":", -1);
281 int loadingUnitId = Integer.parseInt(splitEntry[0]);
283 if (splitEntry.length > 2) {
292 String resolvedComponentName =
294 if (resolvedComponentName ==
null) {
296 TAG,
"Deferred component name was null and could not be resolved from loading unit id.");
301 if (resolvedComponentName.equals(
"") && loadingUnitId > 0) {
307 SplitInstallRequest request =
308 SplitInstallRequest.newBuilder().addModule(resolvedComponentName).build();
314 .startInstall(request)
317 .addOnSuccessListener(
319 sessionIdToName.put(sessionId, resolvedComponentName);
320 sessionIdToLoadingUnitId.put(sessionId, loadingUnitId);
321 if (nameToSessionId.containsKey(resolvedComponentName)) {
322 sessionIdToState.remove(nameToSessionId.get(resolvedComponentName));
324 nameToSessionId.put(resolvedComponentName, sessionId);
325 sessionIdToState.put(sessionId,
"Requested");
327 .addOnFailureListener(
329 switch (((SplitInstallException) exception).getErrorCode()) {
330 case SplitInstallErrorCode.NETWORK_ERROR:
331 flutterJNI.deferredComponentInstallFailure(
333 "Install of deferred component module \""
335 +
"\" failed with a network error",
338 case SplitInstallErrorCode.MODULE_UNAVAILABLE:
339 flutterJNI.deferredComponentInstallFailure(
341 "Install of deferred component module \""
343 +
"\" failed as it is unavailable",
347 flutterJNI.deferredComponentInstallFailure(
350 "Install of deferred component module \"%s\" failed with error %d: %s",
352 ((SplitInstallException) exception).getErrorCode(),
353 ((SplitInstallException) exception).getMessage()),
362 int loadingUnitId, @Nullable String componentName) {
363 String resolvedComponentName =
365 if (resolvedComponentName ==
null) {
367 TAG,
"Deferred component name was null and could not be resolved from loading unit id.");
370 if (!nameToSessionId.containsKey(resolvedComponentName)) {
371 if (splitInstallManager.getInstalledModules().contains(resolvedComponentName)) {
372 return "installedPendingLoad";
376 int sessionId = nameToSessionId.get(resolvedComponentName);
377 return sessionIdToState.get(sessionId);
380 public void loadAssets(
int loadingUnitId, @NonNull String componentName) {
389 context = context.createPackageContext(context.getPackageName(), 0);
391 AssetManager assetManager = context.getAssets();
392 flutterJNI.updateJavaAssetManager(assetManager, flutterApplicationInfo.flutterAssetsDir);
393 }
catch (NameNotFoundException e) {
394 throw new RuntimeException(e);
403 if (loadingUnitId < 0) {
408 if (aotSharedLibraryName ==
null) {
410 aotSharedLibraryName =
411 flutterApplicationInfo.aotSharedLibraryName +
"-" + loadingUnitId +
".part.so";
415 String abi =
Build.SUPPORTED_ABIS[0];
416 String pathAbi = abi.replace(
"-",
"_");
426 Queue<File> searchFiles =
new LinkedList<>();
428 searchFiles.add(context.getFilesDir());
433 for (String path : context.getApplicationInfo().splitSourceDirs) {
434 searchFiles.add(
new File(path));
437 while (!searchFiles.isEmpty()) {
438 File file = searchFiles.remove();
439 if (file !=
null && file.isDirectory() && file.listFiles() !=
null) {
440 for (File f : file.listFiles()) {
445 String
name = file.getName();
448 if (
name.endsWith(
".apk")
449 && (
name.startsWith(componentName) ||
name.startsWith(
"split_config"))
450 &&
name.contains(pathAbi)) {
451 apkPaths.
add(file.getAbsolutePath());
454 if (
name.equals(aotSharedLibraryName)) {
455 soPaths.
add(file.getAbsolutePath());
463 searchPaths.
add(aotSharedLibraryName);
465 for (String path : apkPaths) {
466 searchPaths.
add(path +
"!lib/" + abi +
"/" + aotSharedLibraryName);
468 for (String path : soPaths) {
469 searchPaths.
add(path);
472 flutterJNI.loadDartDeferredLibrary(
473 loadingUnitId, searchPaths.toArray(
new String[searchPaths.size()]));
477 String resolvedComponentName =
479 if (resolvedComponentName ==
null) {
481 TAG,
"Deferred component name was null and could not be resolved from loading unit id.");
485 modulesToUninstall.
add(resolvedComponentName);
486 splitInstallManager.deferredUninstall(modulesToUninstall);
487 if (nameToSessionId.get(resolvedComponentName) !=
null) {
488 sessionIdToState.delete(nameToSessionId.get(resolvedComponentName));
494 splitInstallManager.unregisterListener(listener);