From d421473b16ecea4a2c282fd845b27f91075127b0 Mon Sep 17 00:00:00 2001 From: garyqian Date: Wed, 2 Dec 2020 15:07:49 -0800 Subject: [PATCH 01/24] Initial code --- ci/licenses_golden/licenses_flutter | 1 + shell/platform/android/BUILD.gn | 1 + .../embedding/engine/FlutterEngine.java | 3 + .../systemchannels/SplitAotChannel.java | 65 +++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index e87e78e4dbadd..b9289202633ef 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -792,6 +792,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/system FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SystemChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/ActivityLifecycleListener.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index e84c60754f61a..c85f7e65c72b8 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -197,6 +197,7 @@ android_java_sources = [ "io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java", "io/flutter/embedding/engine/systemchannels/RestorationChannel.java", "io/flutter/embedding/engine/systemchannels/SettingsChannel.java", + "io/flutter/embedding/engine/systemchannels/SplitAotChannel.java", "io/flutter/embedding/engine/systemchannels/SystemChannel.java", "io/flutter/embedding/engine/systemchannels/TextInputChannel.java", "io/flutter/plugin/common/ActivityLifecycleListener.java", diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 556b4cd3af021..3637077d2d5c6 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -29,6 +29,7 @@ import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.embedding.engine.systemchannels.RestorationChannel; import io.flutter.embedding.engine.systemchannels.SettingsChannel; +import io.flutter.embedding.engine.systemchannels.SplitAotChannel; import io.flutter.embedding.engine.systemchannels.SystemChannel; import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.plugin.localization.LocalizationPlugin; @@ -89,6 +90,7 @@ public class FlutterEngine { @NonNull private final RestorationChannel restorationChannel; @NonNull private final PlatformChannel platformChannel; @NonNull private final SettingsChannel settingsChannel; + @NonNull private final SplitAotChannel splitAotChannel; @NonNull private final SystemChannel systemChannel; @NonNull private final TextInputChannel textInputChannel; @@ -284,6 +286,7 @@ public FlutterEngine( platformChannel = new PlatformChannel(dartExecutor); restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData); settingsChannel = new SettingsChannel(dartExecutor); + splitAotChannel = new SplitAotChannel(dartExecutor); systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java new file mode 100644 index 0000000000000..828184f2766c0 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java @@ -0,0 +1,65 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.embedding.engine.systemchannels; + +import android.os.Build; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.FlutterInjector; +import io.flutter.Log; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.dynamicfeatures.DynamicFeatureManager; +import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.plugin.common.MethodChannel; + +/** Sends the platform's locales to Dart. */ +public class SplitAotChannel { + private static final String TAG = "SplitAotChannel"; + + @NonNull public final MethodChannel channel; + @Nullable DynamicFeatureManager dynamicFeatureManager; + + + private final MethodChannel.MethodCallHandler parsingMethodHandler = + new MethodChannel.MethodCallHandler() { + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + if (dynamicFeatureManager == null) { + // If no DynamicFeatureManager has been injected, then this channel is a no-op. + return; + } + + String method = call.method; + Object args = call.arguments; + Log.v(TAG, "Received '" + method + "' message."); + switch (method) { + case "SplitAot.installModule": + result.success(null); + break; + case "SplitAot.installLoadingUnit": + result.success(null); + break; + default: + result.notImplemented(); + break; + } + } + }; + + /** + * Constructs a {@code SplitAotChannel} that connects Android to the Dart code running in {@code + * dartExecutor}. + * + *

The given {@code dartExecutor} is permitted to be idle or executing code. + * + *

See {@link DartExecutor}. + */ + public SplitAotChannel(@NonNull DartExecutor dartExecutor) { + this.channel = + new MethodChannel(dartExecutor, "flutter/splitaot", JSONMethodCodec.INSTANCE); + channel.setMethodCallHandler(parsingMethodHandler); + dynamicFeatureManager = FlutterInjector.instance().dynamicFeatureManager(); + } +} From 34721f510cb06a207dbae20e568467d04419d148 Mon Sep 17 00:00:00 2001 From: garyqian Date: Wed, 2 Dec 2020 19:21:07 -0800 Subject: [PATCH 02/24] State tracking and basic method channels --- .../flutter/embedding/engine/FlutterJNI.java | 2 +- .../DynamicFeatureManager.java | 37 ++++++++- .../PlayStoreDynamicFeatureManager.java | 83 +++++++++++++------ .../systemchannels/SplitAotChannel.java | 47 ++++++++++- .../PlayStoreDynamicFeatureManagerTest.java | 10 +-- 5 files changed, 140 insertions(+), 39 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 94576fb302f10..85d49909cea9d 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -1010,7 +1010,7 @@ public void setDynamicFeatureManager(@Nullable DynamicFeatureManager dynamicFeat @UiThread public void requestDartDeferredLibrary(int loadingUnitId) { if (dynamicFeatureManager != null) { - dynamicFeatureManager.downloadDynamicFeature(loadingUnitId, null); + dynamicFeatureManager.installDynamicFeature(loadingUnitId, null); } else { // TODO(garyq): Add link to setup/instructions guide wiki. Log.e( diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java index 747f6df01d56f..789d595069616 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -20,14 +20,14 @@ * deferred imported library. See https://dart.dev/guides/language/language-tour#deferred-loading * This call retrieves a unique identifier called the loading unit id, which is assigned by * gen_snapshot during compilation. The loading unit id is passed down through the engine and - * invokes downloadDynamicFeature. Once the feature module is downloaded, loadAssets and + * invokes installDynamicFeature. Once the feature module is downloaded, loadAssets and * loadDartLibrary should be invoked. loadDartLibrary should find shared library .so files for the * engine to open and pass the .so path to FlutterJNI.loadDartDeferredLibrary. loadAssets should * typically ensure the new assets are available to the engine's asset manager by passing an updated * Android AssetManager to the engine via FlutterJNI.updateAssetManager. * *

The loadAssets and loadDartLibrary methods are separated out because they may also be called - * manually via platform channel messages. A full downloadDynamicFeature implementation should call + * manually via platform channel messages. A full installDynamicFeature implementation should call * these two methods as needed. * *

A dynamic feature module is uniquely identified by a module name as defined in @@ -85,7 +85,36 @@ public interface DynamicFeatureManager { * associated Dart deferred library, loading unit id should a negative value and moduleName * must be non-null. */ - public abstract void downloadDynamicFeature(int loadingUnitId, String moduleName); + public abstract void installDynamicFeature(int loadingUnitId, String moduleName); + + /** + * Gets the current state of the installation session corresponding to the specified + * loadingUnitId and/or moduleName. + * + *

Invocations of {@link installDynamicFeature} typically result in asynchronous downloading + * and other tasks. This method enables querying of the state of the installation. If no dynamic + * feature has been installed or requested to be installed by the provided loadingUnitId or + * moduleName, then this method will return null. + * + *

Depending on the implementation, the returned String may vary. The Play store default + * implementation begins in the "Requested" state before transitioning to the "Downloading" and + * "Installed" states. + * + *

Only sucessfully requested modules have state. Modules that are invalid or have not been + * requested with {@link installDynamicFeature} will not have a state. Due to the asynchronous + * nature of the download process, modules may not immediately have a valid state upon return of + * {@link installDynamicFeature}, though valid modules will eventually obtain a state. + * + *

Both parameters are not always necessary to identify which module to install. Asset-only + * modules do not have an associated loadingUnitId. Instead, an invalid ID like -1 may be passed + * to query only with moduleName. On the other hand, it can be possible to resolve the + * moduleName based on the loadingUnitId. This resolution is done if moduleName is null. At least + * one of loadingUnitId or moduleName must be valid or non-null. + * + * @param loadingUnitId The unique identifier associated with a Dart deferred library. + * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. + */ + public abstract String getDynamicFeatureInstallState(int loadingUnitId, String moduleName); /** * Extract and load any assets and resources from the module for use by Flutter. @@ -102,7 +131,7 @@ public interface DynamicFeatureManager { * *

Assets shoud be loaded before the Dart deferred library is loaded, as successful loading of * the Dart loading unit indicates the dynamic feature is fully loaded. Implementations of - * downloadDynamicFeature should invoke this after successful download. + * installDynamicFeature should invoke this after successful download. * * @param loadingUnitId The unique identifier associated with a Dart deferred library. * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index 406392cd28b53..3fbe5819eb2e0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -24,8 +24,10 @@ import io.flutter.embedding.engine.FlutterJNI; import java.io.File; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Queue; /** @@ -42,12 +44,15 @@ public class PlayStoreDynamicFeatureManager implements DynamicFeatureManager { // the session ID with the loading unit and module name that was requested. private @NonNull SparseArray sessionIdToName; private @NonNull SparseIntArray sessionIdToLoadingUnitId; + private @NonNull SparseArray sessionIdToState; + private @NonNull Map nameToSessionId; private FeatureInstallStateUpdatedListener listener; private class FeatureInstallStateUpdatedListener implements SplitInstallStateUpdatedListener { public void onStateUpdate(SplitInstallSessionState state) { - if (sessionIdToName.get(state.sessionId()) != null) { + int sessionId = state.sessionId(); + if (sessionIdToName.get(sessionId) != null) { // TODO(garyq): Add system channel for split aot messages. switch (state.status()) { case SplitInstallSessionStatus.FAILED: @@ -56,15 +61,16 @@ public void onStateUpdate(SplitInstallSessionState state) { TAG, String.format( "Module \"%s\" (sessionId %d) install failed with: %s", - sessionIdToName.get(state.sessionId()), - state.sessionId(), + sessionIdToName.get(sessionId), + sessionId, state.errorCode())); flutterJNI.dynamicFeatureInstallFailure( - sessionIdToLoadingUnitId.get(state.sessionId()), + sessionIdToLoadingUnitId.get(sessionId), "Module install failed with " + state.errorCode(), true); - sessionIdToName.delete(state.sessionId()); - sessionIdToLoadingUnitId.delete(state.sessionId()); + sessionIdToName.delete(sessionId); + sessionIdToLoadingUnitId.delete(sessionId); + sessionIdToState.put(sessionId, "Failed"); break; } case SplitInstallSessionStatus.INSTALLED: @@ -73,18 +79,19 @@ public void onStateUpdate(SplitInstallSessionState state) { TAG, String.format( "Module \"%s\" (sessionId %d) install successfully.", - sessionIdToName.get(state.sessionId()), state.sessionId())); + sessionIdToName.get(sessionId), sessionId)); loadAssets( - sessionIdToLoadingUnitId.get(state.sessionId()), - sessionIdToName.get(state.sessionId())); + sessionIdToLoadingUnitId.get(sessionId), + sessionIdToName.get(sessionId)); // We only load Dart shared lib for the loading unit id requested. Other loading units // (if present) in the dynamic feature module are not loaded, but can be loaded by // calling again with their loading unit id. loadDartLibrary( - sessionIdToLoadingUnitId.get(state.sessionId()), - sessionIdToName.get(state.sessionId())); - sessionIdToName.delete(state.sessionId()); - sessionIdToLoadingUnitId.delete(state.sessionId()); + sessionIdToLoadingUnitId.get(sessionId), + sessionIdToName.get(sessionId)); + sessionIdToName.delete(sessionId); + sessionIdToLoadingUnitId.delete(sessionId); + sessionIdToState.put(sessionId, "Installed"); break; } case SplitInstallSessionStatus.CANCELED: @@ -93,9 +100,10 @@ public void onStateUpdate(SplitInstallSessionState state) { TAG, String.format( "Module \"%s\" (sessionId %d) install canceled.", - sessionIdToName.get(state.sessionId()), state.sessionId())); - sessionIdToName.delete(state.sessionId()); - sessionIdToLoadingUnitId.delete(state.sessionId()); + sessionIdToName.get(sessionId), sessionId)); + sessionIdToName.delete(sessionId); + sessionIdToLoadingUnitId.delete(sessionId); + sessionIdToState.put(sessionId, "Cancelled"); break; } case SplitInstallSessionStatus.CANCELING: @@ -104,7 +112,8 @@ public void onStateUpdate(SplitInstallSessionState state) { TAG, String.format( "Module \"%s\" (sessionId %d) install canceling.", - sessionIdToName.get(state.sessionId()), state.sessionId())); + sessionIdToName.get(sessionId), sessionId)); + sessionIdToState.put(sessionId, "Canceling"); break; } case SplitInstallSessionStatus.PENDING: @@ -113,7 +122,8 @@ public void onStateUpdate(SplitInstallSessionState state) { TAG, String.format( "Module \"%s\" (sessionId %d) install pending.", - sessionIdToName.get(state.sessionId()), state.sessionId())); + sessionIdToName.get(sessionId), sessionId)); + sessionIdToState.put(sessionId, "Pending"); break; } case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION: @@ -122,7 +132,8 @@ public void onStateUpdate(SplitInstallSessionState state) { TAG, String.format( "Module \"%s\" (sessionId %d) install requires user confirmation.", - sessionIdToName.get(state.sessionId()), state.sessionId())); + sessionIdToName.get(sessionId), sessionId)); + sessionIdToState.put(sessionId, "Requires User Confirmation"); break; } case SplitInstallSessionStatus.DOWNLOADING: @@ -131,7 +142,8 @@ public void onStateUpdate(SplitInstallSessionState state) { TAG, String.format( "Module \"%s\" (sessionId %d) downloading.", - sessionIdToName.get(state.sessionId()), state.sessionId())); + sessionIdToName.get(sessionId), sessionId)); + sessionIdToState.put(sessionId, "Downloading"); break; } case SplitInstallSessionStatus.DOWNLOADED: @@ -140,7 +152,8 @@ public void onStateUpdate(SplitInstallSessionState state) { TAG, String.format( "Module \"%s\" (sessionId %d) downloaded.", - sessionIdToName.get(state.sessionId()), state.sessionId())); + sessionIdToName.get(sessionId), sessionId)); + sessionIdToState.put(sessionId, "Downloaded"); break; } case SplitInstallSessionStatus.INSTALLING: @@ -149,7 +162,8 @@ public void onStateUpdate(SplitInstallSessionState state) { TAG, String.format( "Module \"%s\" (sessionId %d) installing.", - sessionIdToName.get(state.sessionId()), state.sessionId())); + sessionIdToName.get(sessionId), sessionId)); + sessionIdToState.put(sessionId, "Installing"); break; } default: @@ -167,6 +181,8 @@ public PlayStoreDynamicFeatureManager(@NonNull Context context, @Nullable Flutte splitInstallManager.registerListener(listener); sessionIdToName = new SparseArray<>(); sessionIdToLoadingUnitId = new SparseIntArray(); + sessionIdToState = new SparseArray<>(); + nameToSessionId = new HashMap<>(); } public void setJNI(@NonNull FlutterJNI flutterJNI) { @@ -193,7 +209,7 @@ private String loadingUnitIdToModuleName(int loadingUnitId) { return context.getResources().getString(moduleNameIdentifier); } - public void downloadDynamicFeature(int loadingUnitId, String moduleName) { + public void installDynamicFeature(int loadingUnitId, String moduleName) { String resolvedModuleName = moduleName != null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); if (resolvedModuleName == null) { @@ -213,8 +229,11 @@ public void downloadDynamicFeature(int loadingUnitId, String moduleName) { // install which is handled in FeatureInstallStateUpdatedListener. .addOnSuccessListener( sessionId -> { - this.sessionIdToName.put(sessionId, resolvedModuleName); - this.sessionIdToLoadingUnitId.put(sessionId, loadingUnitId); + sessionIdToName.put(sessionId, resolvedModuleName); + sessionIdToLoadingUnitId.put(sessionId, loadingUnitId); + sessionIdToState.remove(nameToSessionId.get(resolvedModuleName)); + nameToSessionId.put(resolvedModuleName, sessionId); + sessionIdToState.put(sessionId, "Requested"); }) .addOnFailureListener( exception -> { @@ -249,6 +268,20 @@ public void downloadDynamicFeature(int loadingUnitId, String moduleName) { }); } + public String getDynamicFeatureInstallState(int loadingUnitId, String moduleName) { + String resolvedModuleName = + moduleName != null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); + if (resolvedModuleName == null) { + Log.d(TAG, "Dynamic feature module name was null."); + return null; + } + if (!nameToSessionId.containsKey(resolvedModuleName)) { + return null; + } + int sessionId = nameToSessionId.get(resolvedModuleName); + return sessionIdToState.get(sessionId); + } + public void loadAssets(int loadingUnitId, String moduleName) { if (!verifyJNI()) { return; diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java index 828184f2766c0..ca47ad9862188 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java @@ -12,9 +12,11 @@ import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dynamicfeatures.DynamicFeatureManager; import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; +import org.json.JSONException; +import org.json.JSONObject; -/** Sends the platform's locales to Dart. */ public class SplitAotChannel { private static final String TAG = "SplitAotChannel"; @@ -30,15 +32,52 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result // If no DynamicFeatureManager has been injected, then this channel is a no-op. return; } - String method = call.method; Object args = call.arguments; Log.v(TAG, "Received '" + method + "' message."); switch (method) { - case "SplitAot.installModule": + case "SplitAot.installDynamicFeature": + try { + final JSONObject arguments = (JSONObject) args; + final int loadingUnitId = arguments.getInt("loadingUnitId"); + final String moduleName = arguments.getString("moduleName"); + dynamicFeatureManager.installDynamicFeature(loadingUnitId, moduleName); + } catch (JSONException exception) { + result.error("error", exception.getMessage(), null); + } + result.success(null); + break; + case "SplitAot.getDynamicFeatureInstallState": + try { + final JSONObject arguments = (JSONObject) args; + final int loadingUnitId = arguments.getInt("loadingUnitId"); + final String moduleName = arguments.getString("moduleName"); + dynamicFeatureManager.getDynamicFeatureInstallState(loadingUnitId, moduleName); + } catch (JSONException exception) { + result.error("error", exception.getMessage(), null); + } + result.success(null); + break; + case "SplitAot.loadAssets": + try { + final JSONObject arguments = (JSONObject) args; + final int loadingUnitId = arguments.getInt("loadingUnitId"); + final String moduleName = arguments.getString("moduleName"); + dynamicFeatureManager.loadAssets(loadingUnitId, moduleName); + } catch (JSONException exception) { + result.error("error", exception.getMessage(), null); + } result.success(null); break; - case "SplitAot.installLoadingUnit": + case "SplitAot.loadDartLibrary": + try { + final JSONObject arguments = (JSONObject) args; + final int loadingUnitId = arguments.getInt("loadingUnitId"); + final String moduleName = arguments.getString("moduleName"); + dynamicFeatureManager.loadAssets(loadingUnitId, moduleName); + } catch (JSONException exception) { + result.error("error", exception.getMessage(), null); + } result.success(null); break; default: diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java index 1fafbb3437890..d6c7bea3c160f 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java @@ -65,7 +65,7 @@ public TestPlayStoreDynamicFeatureManager(Context context, FlutterJNI jni) { } @Override - public void downloadDynamicFeature(int loadingUnitId, String moduleName) { + public void installDynamicFeature(int loadingUnitId, String moduleName) { // Override this to skip the online SplitInstallManager portion. loadAssets(loadingUnitId, moduleName); loadDartLibrary(loadingUnitId, moduleName); @@ -85,7 +85,7 @@ public void downloadCallsJNIFunctions() throws NameNotFoundException { jni.setDynamicFeatureManager(playStoreManager); assertEquals(jni.loadingUnitId, 0); - playStoreManager.downloadDynamicFeature(123, "TestModuleName"); + playStoreManager.installDynamicFeature(123, "TestModuleName"); assertEquals(jni.loadDartDeferredLibraryCalled, 1); assertEquals(jni.updateAssetManagerCalled, 1); assertEquals(jni.dynamicFeatureInstallFailureCalled, 0); @@ -109,7 +109,7 @@ public void searchPathsAddsApks() throws NameNotFoundException { assertEquals(jni.loadingUnitId, 0); - playStoreManager.downloadDynamicFeature(123, "TestModuleName"); + playStoreManager.installDynamicFeature(123, "TestModuleName"); assertEquals(jni.loadDartDeferredLibraryCalled, 1); assertEquals(jni.updateAssetManagerCalled, 1); assertEquals(jni.dynamicFeatureInstallFailureCalled, 0); @@ -133,7 +133,7 @@ public void invalidSearchPathsAreIgnored() throws NameNotFoundException { assertEquals(jni.loadingUnitId, 0); - playStoreManager.downloadDynamicFeature(123, "TestModuleName"); + playStoreManager.installDynamicFeature(123, "TestModuleName"); assertEquals(jni.loadDartDeferredLibraryCalled, 1); assertEquals(jni.updateAssetManagerCalled, 1); assertEquals(jni.dynamicFeatureInstallFailureCalled, 0); @@ -156,7 +156,7 @@ public void assetManagerUpdateInvoked() throws NameNotFoundException { assertEquals(jni.loadingUnitId, 0); - playStoreManager.downloadDynamicFeature(123, "TestModuleName"); + playStoreManager.installDynamicFeature(123, "TestModuleName"); assertEquals(jni.loadDartDeferredLibraryCalled, 1); assertEquals(jni.updateAssetManagerCalled, 1); assertEquals(jni.dynamicFeatureInstallFailureCalled, 0); From d27270aaa561f900f4238efd8201c62307424fdc Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 3 Dec 2020 10:48:56 -0800 Subject: [PATCH 03/24] REname to DynamicFeaturesChannel --- shell/platform/android/BUILD.gn | 2 +- .../io/flutter/embedding/engine/FlutterEngine.java | 12 +++++++++--- ...itAotChannel.java => DynamicFeaturesChannel.java} | 8 ++++---- 3 files changed, 14 insertions(+), 8 deletions(-) rename shell/platform/android/io/flutter/embedding/engine/systemchannels/{SplitAotChannel.java => DynamicFeaturesChannel.java} (93%) diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index c85f7e65c72b8..809c247c86b7e 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -188,6 +188,7 @@ android_java_sources = [ "io/flutter/embedding/engine/renderer/RenderSurface.java", "io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java", "io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java", + "io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java", "io/flutter/embedding/engine/systemchannels/KeyEventChannel.java", "io/flutter/embedding/engine/systemchannels/LifecycleChannel.java", "io/flutter/embedding/engine/systemchannels/LocalizationChannel.java", @@ -197,7 +198,6 @@ android_java_sources = [ "io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java", "io/flutter/embedding/engine/systemchannels/RestorationChannel.java", "io/flutter/embedding/engine/systemchannels/SettingsChannel.java", - "io/flutter/embedding/engine/systemchannels/SplitAotChannel.java", "io/flutter/embedding/engine/systemchannels/SystemChannel.java", "io/flutter/embedding/engine/systemchannels/TextInputChannel.java", "io/flutter/plugin/common/ActivityLifecycleListener.java", diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 3637077d2d5c6..3b7498a4b28c2 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -29,7 +29,7 @@ import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.embedding.engine.systemchannels.RestorationChannel; import io.flutter.embedding.engine.systemchannels.SettingsChannel; -import io.flutter.embedding.engine.systemchannels.SplitAotChannel; +import io.flutter.embedding.engine.systemchannels.DynamicFeaturesChannel; import io.flutter.embedding.engine.systemchannels.SystemChannel; import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.plugin.localization.LocalizationPlugin; @@ -90,7 +90,7 @@ public class FlutterEngine { @NonNull private final RestorationChannel restorationChannel; @NonNull private final PlatformChannel platformChannel; @NonNull private final SettingsChannel settingsChannel; - @NonNull private final SplitAotChannel splitAotChannel; + @NonNull private final DynamicFeaturesChannel DynamicFeaturesChannel; @NonNull private final SystemChannel systemChannel; @NonNull private final TextInputChannel textInputChannel; @@ -286,7 +286,7 @@ public FlutterEngine( platformChannel = new PlatformChannel(dartExecutor); restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData); settingsChannel = new SettingsChannel(dartExecutor); - splitAotChannel = new SplitAotChannel(dartExecutor); + DynamicFeaturesChannel = new DynamicFeaturesChannel(dartExecutor); systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); @@ -487,6 +487,12 @@ public SettingsChannel getSettingsChannel() { return settingsChannel; } + /** System channel that allows manual installation and state querying of dynamic features. */ + @NonNull + public DynamicFeaturesChannel getDynamicFeaturesChannel() { + return DynamicFeaturesChannel; + } + /** System channel that sends memory pressure warnings from Android to Flutter. */ @NonNull public SystemChannel getSystemChannel() { diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java similarity index 93% rename from shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java rename to shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java index ca47ad9862188..4793fcf68f0de 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java @@ -17,8 +17,8 @@ import org.json.JSONException; import org.json.JSONObject; -public class SplitAotChannel { - private static final String TAG = "SplitAotChannel"; +public class DynamicFeaturesChannel { + private static final String TAG = "DynamicFeaturesChannel"; @NonNull public final MethodChannel channel; @Nullable DynamicFeatureManager dynamicFeatureManager; @@ -88,14 +88,14 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result }; /** - * Constructs a {@code SplitAotChannel} that connects Android to the Dart code running in {@code + * Constructs a {@code DynamicFeaturesChannel} that connects Android to the Dart code running in {@code * dartExecutor}. * *

The given {@code dartExecutor} is permitted to be idle or executing code. * *

See {@link DartExecutor}. */ - public SplitAotChannel(@NonNull DartExecutor dartExecutor) { + public DynamicFeaturesChannel(@NonNull DartExecutor dartExecutor) { this.channel = new MethodChannel(dartExecutor, "flutter/splitaot", JSONMethodCodec.INSTANCE); channel.setMethodCallHandler(parsingMethodHandler); From 49257488fbddc595ebba1aae9f790d520a2d83ce Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 3 Dec 2020 15:20:39 -0800 Subject: [PATCH 04/24] Licenses fix --- ci/licenses_golden/licenses_flutter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index b9289202633ef..b2c46e394f8f1 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -783,6 +783,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/render FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/RenderSurface.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java @@ -792,7 +793,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/system FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java -FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SplitAotChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SystemChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/ActivityLifecycleListener.java From 096084388ea5dbbc6857f55ea67ffcc4b59e6c6f Mon Sep 17 00:00:00 2001 From: garyqian Date: Mon, 7 Dec 2020 01:38:33 -0800 Subject: [PATCH 05/24] Improved code reuse --- .../DynamicFeaturesChannel.java | 67 +++++++------------ 1 file changed, 23 insertions(+), 44 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java index 4793fcf68f0de..5312f09da2fd4 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java @@ -35,54 +35,33 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result String method = call.method; Object args = call.arguments; Log.v(TAG, "Received '" + method + "' message."); - switch (method) { - case "SplitAot.installDynamicFeature": - try { - final JSONObject arguments = (JSONObject) args; - final int loadingUnitId = arguments.getInt("loadingUnitId"); - final String moduleName = arguments.getString("moduleName"); + try { + final JSONObject arguments = (JSONObject) args; + final int loadingUnitId = arguments.getInt("loadingUnitId"); + final String moduleName = arguments.getString("moduleName"); + switch (method) { + case "SplitAot.installDynamicFeature": dynamicFeatureManager.installDynamicFeature(loadingUnitId, moduleName); - } catch (JSONException exception) { - result.error("error", exception.getMessage(), null); - } - result.success(null); - break; - case "SplitAot.getDynamicFeatureInstallState": - try { - final JSONObject arguments = (JSONObject) args; - final int loadingUnitId = arguments.getInt("loadingUnitId"); - final String moduleName = arguments.getString("moduleName"); + result.success(null); + break; + case "SplitAot.getDynamicFeatureInstallState": dynamicFeatureManager.getDynamicFeatureInstallState(loadingUnitId, moduleName); - } catch (JSONException exception) { - result.error("error", exception.getMessage(), null); - } - result.success(null); - break; - case "SplitAot.loadAssets": - try { - final JSONObject arguments = (JSONObject) args; - final int loadingUnitId = arguments.getInt("loadingUnitId"); - final String moduleName = arguments.getString("moduleName"); + result.success(null); + break; + case "SplitAot.loadAssets": dynamicFeatureManager.loadAssets(loadingUnitId, moduleName); - } catch (JSONException exception) { - result.error("error", exception.getMessage(), null); - } - result.success(null); - break; - case "SplitAot.loadDartLibrary": - try { - final JSONObject arguments = (JSONObject) args; - final int loadingUnitId = arguments.getInt("loadingUnitId"); - final String moduleName = arguments.getString("moduleName"); + result.success(null); + break; + case "SplitAot.loadDartLibrary": dynamicFeatureManager.loadAssets(loadingUnitId, moduleName); - } catch (JSONException exception) { - result.error("error", exception.getMessage(), null); - } - result.success(null); - break; - default: - result.notImplemented(); - break; + result.success(null); + break; + default: + result.notImplemented(); + break; + } + } catch (JSONException exception) { + result.error("error", exception.getMessage(), null); } } }; From 1b039b4f337dbee6fb49e85925944b70b4f83bae Mon Sep 17 00:00:00 2001 From: garyqian Date: Mon, 7 Dec 2020 19:50:51 -0800 Subject: [PATCH 06/24] Implement result tracking and future completion --- .../embedding/engine/FlutterEngine.java | 10 ++-- .../DynamicFeatureManager.java | 13 +++++ .../PlayStoreDynamicFeatureManager.java | 22 ++++++++- .../DynamicFeaturesChannel.java | 48 ++++++++++++++----- 4 files changed, 74 insertions(+), 19 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 3b7498a4b28c2..b89e359f0d9d0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -21,6 +21,7 @@ import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; +import io.flutter.embedding.engine.systemchannels.DynamicFeaturesChannel; import io.flutter.embedding.engine.systemchannels.KeyEventChannel; import io.flutter.embedding.engine.systemchannels.LifecycleChannel; import io.flutter.embedding.engine.systemchannels.LocalizationChannel; @@ -29,7 +30,6 @@ import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.embedding.engine.systemchannels.RestorationChannel; import io.flutter.embedding.engine.systemchannels.SettingsChannel; -import io.flutter.embedding.engine.systemchannels.DynamicFeaturesChannel; import io.flutter.embedding.engine.systemchannels.SystemChannel; import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.plugin.localization.LocalizationPlugin; @@ -82,6 +82,7 @@ public class FlutterEngine { // System channels. @NonNull private final AccessibilityChannel accessibilityChannel; + @NonNull private final DynamicFeaturesChannel dynamicFeaturesChannel; @NonNull private final KeyEventChannel keyEventChannel; @NonNull private final LifecycleChannel lifecycleChannel; @NonNull private final LocalizationChannel localizationChannel; @@ -90,7 +91,6 @@ public class FlutterEngine { @NonNull private final RestorationChannel restorationChannel; @NonNull private final PlatformChannel platformChannel; @NonNull private final SettingsChannel settingsChannel; - @NonNull private final DynamicFeaturesChannel DynamicFeaturesChannel; @NonNull private final SystemChannel systemChannel; @NonNull private final TextInputChannel textInputChannel; @@ -278,6 +278,7 @@ public FlutterEngine( this.dartExecutor.onAttachedToJNI(); accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI); + dynamicFeaturesChannel = new DynamicFeaturesChannel(dartExecutor); keyEventChannel = new KeyEventChannel(dartExecutor); lifecycleChannel = new LifecycleChannel(dartExecutor); localizationChannel = new LocalizationChannel(dartExecutor); @@ -286,10 +287,11 @@ public FlutterEngine( platformChannel = new PlatformChannel(dartExecutor); restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData); settingsChannel = new SettingsChannel(dartExecutor); - DynamicFeaturesChannel = new DynamicFeaturesChannel(dartExecutor); systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); + FlutterInjector.instance().dynamicFeatureManager().setDynamicFeaturesChannel(dynamicFeaturesChannel); + this.localizationPlugin = new LocalizationPlugin(context, localizationChannel); this.flutterJNI = flutterJNI; @@ -490,7 +492,7 @@ public SettingsChannel getSettingsChannel() { /** System channel that allows manual installation and state querying of dynamic features. */ @NonNull public DynamicFeaturesChannel getDynamicFeaturesChannel() { - return DynamicFeaturesChannel; + return dynamicFeaturesChannel; } /** System channel that sends memory pressure warnings from Android to Flutter. */ diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java index 789d595069616..498d895e6cb38 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -5,6 +5,7 @@ package io.flutter.embedding.engine.dynamicfeatures; import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.systemchannels.DynamicFeaturesChannel; // TODO: add links to external documentation on how to use split aot features. /** @@ -46,6 +47,18 @@ public interface DynamicFeatureManager { */ public abstract void setJNI(FlutterJNI flutterJNI); + /** + * Sets the DynamicFeaturesChannel system channel to handle the framework API to directly call + * methods in DynamicFeatureManager. + * + *

A DynamicFeaturesChannel is required to handle assets-only dynamic features and manually + * installed dynamic features. + * + *

Since this class may be instantiated for injection before the FlutterEngine and System + * Channels are initialized, this method should be called to provide the DynamicFeaturesChannel. + */ + public abstract void setDynamicFeaturesChannel(DynamicFeaturesChannel channel); + /** * Request that the feature module be downloaded and installed. * diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index 3fbe5819eb2e0..c5a1499b9b985 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -22,6 +22,7 @@ import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus; import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.systemchannels.DynamicFeaturesChannel; import java.io.File; import java.util.ArrayList; import java.util.HashMap; @@ -39,6 +40,7 @@ public class PlayStoreDynamicFeatureManager implements DynamicFeatureManager { private @NonNull SplitInstallManager splitInstallManager; private @Nullable FlutterJNI flutterJNI; + private @Nullable DynamicFeaturesChannel channel; private @NonNull Context context; // Each request to install a feature module gets a session ID. These maps associate // the session ID with the loading unit and module name that was requested. @@ -53,7 +55,6 @@ private class FeatureInstallStateUpdatedListener implements SplitInstallStateUpd public void onStateUpdate(SplitInstallSessionState state) { int sessionId = state.sessionId(); if (sessionIdToName.get(sessionId) != null) { - // TODO(garyq): Add system channel for split aot messages. switch (state.status()) { case SplitInstallSessionStatus.FAILED: { @@ -68,6 +69,11 @@ public void onStateUpdate(SplitInstallSessionState state) { sessionIdToLoadingUnitId.get(sessionId), "Module install failed with " + state.errorCode(), true); + if (channel != null) { + channel.completeInstallError( + sessionIdToName.get(sessionId), + "Android Dynamic Feature failed to install."); + } sessionIdToName.delete(sessionId); sessionIdToLoadingUnitId.delete(sessionId); sessionIdToState.put(sessionId, "Failed"); @@ -89,6 +95,9 @@ public void onStateUpdate(SplitInstallSessionState state) { loadDartLibrary( sessionIdToLoadingUnitId.get(sessionId), sessionIdToName.get(sessionId)); + if (channel != null) { + channel.completeInstallSuccess(sessionIdToName.get(sessionId)); + } sessionIdToName.delete(sessionId); sessionIdToLoadingUnitId.delete(sessionId); sessionIdToState.put(sessionId, "Installed"); @@ -101,6 +110,11 @@ public void onStateUpdate(SplitInstallSessionState state) { String.format( "Module \"%s\" (sessionId %d) install canceled.", sessionIdToName.get(sessionId), sessionId)); + if (channel != null) { + channel.completeInstallError( + sessionIdToName.get(sessionId), + "Android Dynamic Feature installation canceled."); + } sessionIdToName.delete(sessionId); sessionIdToLoadingUnitId.delete(sessionId); sessionIdToState.put(sessionId, "Cancelled"); @@ -167,7 +181,7 @@ public void onStateUpdate(SplitInstallSessionState state) { break; } default: - Log.d(TAG, "Status: " + state.status()); + Log.d(TAG, "Unknown status: " + state.status()); } } } @@ -199,6 +213,10 @@ private boolean verifyJNI() { return true; } + public void setDynamicFeaturesChannel(DynamicFeaturesChannel channel) { + this.channel = channel; + } + private String loadingUnitIdToModuleName(int loadingUnitId) { // Loading unit id to module name mapping stored in android Strings // resources. diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java index 5312f09da2fd4..de94ba31905b0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java @@ -16,12 +16,17 @@ import io.flutter.plugin.common.MethodChannel; import org.json.JSONException; import org.json.JSONObject; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class DynamicFeaturesChannel { private static final String TAG = "DynamicFeaturesChannel"; @NonNull public final MethodChannel channel; @Nullable DynamicFeatureManager dynamicFeatureManager; + @NonNull Map> moduleNameToResults; private final MethodChannel.MethodCallHandler parsingMethodHandler = @@ -40,21 +45,16 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result final int loadingUnitId = arguments.getInt("loadingUnitId"); final String moduleName = arguments.getString("moduleName"); switch (method) { - case "SplitAot.installDynamicFeature": + case "DynamicFeatures.installDynamicFeature": dynamicFeatureManager.installDynamicFeature(loadingUnitId, moduleName); - result.success(null); + if (!moduleNameToResults.containsKey(moduleName)) { + moduleNameToResults.put(moduleName, new ArrayList<>()); + } else { + moduleNameToResults.get(moduleName).add(result); + } break; - case "SplitAot.getDynamicFeatureInstallState": - dynamicFeatureManager.getDynamicFeatureInstallState(loadingUnitId, moduleName); - result.success(null); - break; - case "SplitAot.loadAssets": - dynamicFeatureManager.loadAssets(loadingUnitId, moduleName); - result.success(null); - break; - case "SplitAot.loadDartLibrary": - dynamicFeatureManager.loadAssets(loadingUnitId, moduleName); - result.success(null); + case "DynamicFeatures.getDynamicFeatureInstallState": + result.success(dynamicFeatureManager.getDynamicFeatureInstallState(loadingUnitId, moduleName)); break; default: result.notImplemented(); @@ -79,5 +79,27 @@ public DynamicFeaturesChannel(@NonNull DartExecutor dartExecutor) { new MethodChannel(dartExecutor, "flutter/splitaot", JSONMethodCodec.INSTANCE); channel.setMethodCallHandler(parsingMethodHandler); dynamicFeatureManager = FlutterInjector.instance().dynamicFeatureManager(); + moduleNameToResults = new HashMap<>(); + } + + public void completeInstallSuccess(String moduleName) { + if (moduleNameToResults.containsKey(moduleName)) { + for (MethodChannel.Result result : moduleNameToResults.get(moduleName)) { + result.success(null); + } + moduleNameToResults.get(moduleName).clear(); + } + return; } + + public void completeInstallError(String moduleName, String errorMessage) { + if (moduleNameToResults.containsKey(moduleName)) { + for (MethodChannel.Result result : moduleNameToResults.get(moduleName)) { + result.error("DynamicFeature Install failure", errorMessage, null); + } + moduleNameToResults.get(moduleName).clear(); + } + return; + } + } From 3fb1ef08d1bab1b908f15e63649686094dde3ad1 Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 8 Dec 2020 00:12:33 -0800 Subject: [PATCH 07/24] Docs and begin tests --- .../DynamicFeaturesChannel.java | 14 +++++- .../DynamicFeaturesChannelTest.java | 45 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannelTest.java diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java index de94ba31905b0..8ca3b1bd8b360 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java @@ -82,6 +82,12 @@ public DynamicFeaturesChannel(@NonNull DartExecutor dartExecutor) { moduleNameToResults = new HashMap<>(); } + /** + * Finishes the `installDynamicFeature` method channel call for the specified moduleName + * with a success. + * + * @param moduleName The name of the android dynamic feature module install request to complete. + */ public void completeInstallSuccess(String moduleName) { if (moduleNameToResults.containsKey(moduleName)) { for (MethodChannel.Result result : moduleNameToResults.get(moduleName)) { @@ -92,6 +98,13 @@ public void completeInstallSuccess(String moduleName) { return; } + /** + * Finishes the `installDynamicFeature` method channel call for the specified moduleName + * with an error/failure. + * + * @param moduleName The name of the android dynamic feature module install request to complete. + * @param errorMessage The error message to display to complete the future with. + */ public void completeInstallError(String moduleName, String errorMessage) { if (moduleNameToResults.containsKey(moduleName)) { for (MethodChannel.Result result : moduleNameToResults.get(moduleName)) { @@ -101,5 +114,4 @@ public void completeInstallError(String moduleName, String errorMessage) { } return; } - } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannelTest.java new file mode 100644 index 0000000000000..5f694bf3b5502 --- /dev/null +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannelTest.java @@ -0,0 +1,45 @@ +package io.flutter.embedding.engine.systemchannels; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.res.AssetManager; +import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Matchers; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@Config(manifest = Config.NONE) +@RunWith(RobolectricTestRunner.class) +public class DynamicFeaturesChannelTest { + @Test + public void dynamicFeaturesChannel_hasStringsMessage() { + MethodChannel rawChannel = mock(MethodChannel.class); + FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); + DynamicFeaturesChannel fakeDynamicFeaturesChannel = new DynamicFeaturesChannel(dartExecutor); + // PlatformChannel.PlatformMessageHandler mockMessageHandler = + // mock(PlatformChannel.PlatformMessageHandler.class); + // fakePlatformChannel.setPlatformMessageHandler(mockMessageHandler); + // Boolean returnValue = true; + // when(mockMessageHandler.clipboardHasStrings()).thenReturn(returnValue); + // MethodCall methodCall = new MethodCall("Clipboard.hasStrings", null); + // MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + // fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult); + + // JSONObject expected = new JSONObject(); + // try { + // expected.put("value", returnValue); + // } catch (JSONException e) { + // } + // verify(mockResult).success(Matchers.refEq(expected)); + } +} From dd312ae85a0dc43b35f3b0d0e18072a14408581a Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 8 Dec 2020 20:27:36 -0800 Subject: [PATCH 08/24] Rename to DynamicFeatureChannel, use standard codec --- lib/ui/ui_dart_state.cc | 1 + shell/platform/android/BUILD.gn | 2 +- .../embedding/engine/FlutterEngine.java | 12 ++-- .../DynamicFeatureManager.java | 10 ++-- .../PlayStoreDynamicFeatureManager.java | 6 +- ...hannel.java => DynamicFeatureChannel.java} | 56 +++++++++---------- ...st.java => DynamicFeatureChannelTest.java} | 6 +- 7 files changed, 44 insertions(+), 49 deletions(-) rename shell/platform/android/io/flutter/embedding/engine/systemchannels/{DynamicFeaturesChannel.java => DynamicFeatureChannel.java} (65%) rename shell/platform/android/test/io/flutter/embedding/engine/systemchannels/{DynamicFeaturesChannelTest.java => DynamicFeatureChannelTest.java} (89%) diff --git a/lib/ui/ui_dart_state.cc b/lib/ui/ui_dart_state.cc index eac218548cfbf..c38a9a685f0b3 100644 --- a/lib/ui/ui_dart_state.cc +++ b/lib/ui/ui_dart_state.cc @@ -1,4 +1,5 @@ // Copyright 2013 The Flutter Authors. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 809c247c86b7e..9f2663e126f34 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -188,7 +188,7 @@ android_java_sources = [ "io/flutter/embedding/engine/renderer/RenderSurface.java", "io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java", "io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java", - "io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java", + "io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java", "io/flutter/embedding/engine/systemchannels/KeyEventChannel.java", "io/flutter/embedding/engine/systemchannels/LifecycleChannel.java", "io/flutter/embedding/engine/systemchannels/LocalizationChannel.java", diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index b89e359f0d9d0..2cf62123061e8 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -21,7 +21,7 @@ import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; -import io.flutter.embedding.engine.systemchannels.DynamicFeaturesChannel; +import io.flutter.embedding.engine.systemchannels.DynamicFeatureChannel; import io.flutter.embedding.engine.systemchannels.KeyEventChannel; import io.flutter.embedding.engine.systemchannels.LifecycleChannel; import io.flutter.embedding.engine.systemchannels.LocalizationChannel; @@ -82,7 +82,7 @@ public class FlutterEngine { // System channels. @NonNull private final AccessibilityChannel accessibilityChannel; - @NonNull private final DynamicFeaturesChannel dynamicFeaturesChannel; + @NonNull private final DynamicFeatureChannel dynamicFeatureChannel; @NonNull private final KeyEventChannel keyEventChannel; @NonNull private final LifecycleChannel lifecycleChannel; @NonNull private final LocalizationChannel localizationChannel; @@ -278,7 +278,7 @@ public FlutterEngine( this.dartExecutor.onAttachedToJNI(); accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI); - dynamicFeaturesChannel = new DynamicFeaturesChannel(dartExecutor); + dynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor); keyEventChannel = new KeyEventChannel(dartExecutor); lifecycleChannel = new LifecycleChannel(dartExecutor); localizationChannel = new LocalizationChannel(dartExecutor); @@ -290,7 +290,7 @@ public FlutterEngine( systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); - FlutterInjector.instance().dynamicFeatureManager().setDynamicFeaturesChannel(dynamicFeaturesChannel); + FlutterInjector.instance().dynamicFeatureManager().setDynamicFeatureChannel(dynamicFeatureChannel); this.localizationPlugin = new LocalizationPlugin(context, localizationChannel); @@ -491,8 +491,8 @@ public SettingsChannel getSettingsChannel() { /** System channel that allows manual installation and state querying of dynamic features. */ @NonNull - public DynamicFeaturesChannel getDynamicFeaturesChannel() { - return dynamicFeaturesChannel; + public DynamicFeatureChannel getDynamicFeatureChannel() { + return dynamicFeatureChannel; } /** System channel that sends memory pressure warnings from Android to Flutter. */ diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java index 498d895e6cb38..759e15421bc8d 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -5,7 +5,7 @@ package io.flutter.embedding.engine.dynamicfeatures; import io.flutter.embedding.engine.FlutterJNI; -import io.flutter.embedding.engine.systemchannels.DynamicFeaturesChannel; +import io.flutter.embedding.engine.systemchannels.DynamicFeatureChannel; // TODO: add links to external documentation on how to use split aot features. /** @@ -48,16 +48,16 @@ public interface DynamicFeatureManager { public abstract void setJNI(FlutterJNI flutterJNI); /** - * Sets the DynamicFeaturesChannel system channel to handle the framework API to directly call + * Sets the DynamicFeatureChannel system channel to handle the framework API to directly call * methods in DynamicFeatureManager. * - *

A DynamicFeaturesChannel is required to handle assets-only dynamic features and manually + *

A DynamicFeatureChannel is required to handle assets-only dynamic features and manually * installed dynamic features. * *

Since this class may be instantiated for injection before the FlutterEngine and System - * Channels are initialized, this method should be called to provide the DynamicFeaturesChannel. + * Channels are initialized, this method should be called to provide the DynamicFeatureChannel. */ - public abstract void setDynamicFeaturesChannel(DynamicFeaturesChannel channel); + public abstract void setDynamicFeatureChannel(DynamicFeatureChannel channel); /** * Request that the feature module be downloaded and installed. diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index c5a1499b9b985..55e8fd8d55d96 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -22,7 +22,7 @@ import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus; import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; -import io.flutter.embedding.engine.systemchannels.DynamicFeaturesChannel; +import io.flutter.embedding.engine.systemchannels.DynamicFeatureChannel; import java.io.File; import java.util.ArrayList; import java.util.HashMap; @@ -40,7 +40,7 @@ public class PlayStoreDynamicFeatureManager implements DynamicFeatureManager { private @NonNull SplitInstallManager splitInstallManager; private @Nullable FlutterJNI flutterJNI; - private @Nullable DynamicFeaturesChannel channel; + private @Nullable DynamicFeatureChannel channel; private @NonNull Context context; // Each request to install a feature module gets a session ID. These maps associate // the session ID with the loading unit and module name that was requested. @@ -213,7 +213,7 @@ private boolean verifyJNI() { return true; } - public void setDynamicFeaturesChannel(DynamicFeaturesChannel channel) { + public void setDynamicFeatureChannel(DynamicFeatureChannel channel) { this.channel = channel; } diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java similarity index 65% rename from shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java rename to shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java index 8ca3b1bd8b360..806a67b977693 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java @@ -11,18 +11,16 @@ import io.flutter.Log; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dynamicfeatures.DynamicFeatureManager; -import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.plugin.common.StandardMethodCodec; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; -import org.json.JSONException; -import org.json.JSONObject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -public class DynamicFeaturesChannel { - private static final String TAG = "DynamicFeaturesChannel"; +public class DynamicFeatureChannel { + private static final String TAG = "DynamicFeatureChannel"; @NonNull public final MethodChannel channel; @Nullable DynamicFeatureManager dynamicFeatureManager; @@ -38,45 +36,41 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result return; } String method = call.method; - Object args = call.arguments; + // Object args = call.arguments; + Map args = call.arguments(); Log.v(TAG, "Received '" + method + "' message."); - try { - final JSONObject arguments = (JSONObject) args; - final int loadingUnitId = arguments.getInt("loadingUnitId"); - final String moduleName = arguments.getString("moduleName"); - switch (method) { - case "DynamicFeatures.installDynamicFeature": - dynamicFeatureManager.installDynamicFeature(loadingUnitId, moduleName); - if (!moduleNameToResults.containsKey(moduleName)) { - moduleNameToResults.put(moduleName, new ArrayList<>()); - } else { - moduleNameToResults.get(moduleName).add(result); - } - break; - case "DynamicFeatures.getDynamicFeatureInstallState": - result.success(dynamicFeatureManager.getDynamicFeatureInstallState(loadingUnitId, moduleName)); - break; - default: - result.notImplemented(); - break; - } - } catch (JSONException exception) { - result.error("error", exception.getMessage(), null); + final int loadingUnitId = (int) args.get("loadingUnitId"); + final String moduleName = (String) args.get("moduleName"); + switch (method) { + case "DynamicFeature.installDynamicFeature": + dynamicFeatureManager.installDynamicFeature(loadingUnitId, moduleName); + if (!moduleNameToResults.containsKey(moduleName)) { + moduleNameToResults.put(moduleName, new ArrayList<>()); + } else { + moduleNameToResults.get(moduleName).add(result); + } + break; + case "DynamicFeature.getDynamicFeatureInstallState": + result.success(dynamicFeatureManager.getDynamicFeatureInstallState(loadingUnitId, moduleName)); + break; + default: + result.notImplemented(); + break; } } }; /** - * Constructs a {@code DynamicFeaturesChannel} that connects Android to the Dart code running in {@code + * Constructs a {@code DynamicFeatureChannel} that connects Android to the Dart code running in {@code * dartExecutor}. * *

The given {@code dartExecutor} is permitted to be idle or executing code. * *

See {@link DartExecutor}. */ - public DynamicFeaturesChannel(@NonNull DartExecutor dartExecutor) { + public DynamicFeatureChannel(@NonNull DartExecutor dartExecutor) { this.channel = - new MethodChannel(dartExecutor, "flutter/splitaot", JSONMethodCodec.INSTANCE); + new MethodChannel(dartExecutor, "flutter/splitaot", StandardMethodCodec.INSTANCE); channel.setMethodCallHandler(parsingMethodHandler); dynamicFeatureManager = FlutterInjector.instance().dynamicFeatureManager(); moduleNameToResults = new HashMap<>(); diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java similarity index 89% rename from shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannelTest.java rename to shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java index 5f694bf3b5502..40a1ebcad5ab6 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java @@ -19,13 +19,13 @@ @Config(manifest = Config.NONE) @RunWith(RobolectricTestRunner.class) -public class DynamicFeaturesChannelTest { +public class DynamicFeatureChannelTest { @Test - public void dynamicFeaturesChannel_hasStringsMessage() { + public void dynamicFeatureChannel_hasStringsMessage() { MethodChannel rawChannel = mock(MethodChannel.class); FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); - DynamicFeaturesChannel fakeDynamicFeaturesChannel = new DynamicFeaturesChannel(dartExecutor); + DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor); // PlatformChannel.PlatformMessageHandler mockMessageHandler = // mock(PlatformChannel.PlatformMessageHandler.class); // fakePlatformChannel.setPlatformMessageHandler(mockMessageHandler); From b9dd22daa96527a677461726a072ac69b09d6ad7 Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 8 Dec 2020 20:32:30 -0800 Subject: [PATCH 09/24] Remove duplicate line --- lib/ui/ui_dart_state.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ui/ui_dart_state.cc b/lib/ui/ui_dart_state.cc index c38a9a685f0b3..eac218548cfbf 100644 --- a/lib/ui/ui_dart_state.cc +++ b/lib/ui/ui_dart_state.cc @@ -1,5 +1,4 @@ // Copyright 2013 The Flutter Authors. All rights reserved. -// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. From 22b8476eda0baf4672a2571c91f9aa305d0091a6 Mon Sep 17 00:00:00 2001 From: garyqian Date: Wed, 9 Dec 2020 22:49:17 -0800 Subject: [PATCH 10/24] Functioning channel --- .../dynamicfeatures/PlayStoreDynamicFeatureManager.java | 4 +++- .../engine/systemchannels/DynamicFeatureChannel.java | 9 ++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index 55e8fd8d55d96..45508bc54198c 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -249,7 +249,9 @@ public void installDynamicFeature(int loadingUnitId, String moduleName) { sessionId -> { sessionIdToName.put(sessionId, resolvedModuleName); sessionIdToLoadingUnitId.put(sessionId, loadingUnitId); - sessionIdToState.remove(nameToSessionId.get(resolvedModuleName)); + if (nameToSessionId.containsKey(resolvedModuleName)) { + sessionIdToState.remove(nameToSessionId.get(resolvedModuleName)); + } nameToSessionId.put(resolvedModuleName, sessionId); sessionIdToState.put(sessionId, "Requested"); }) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java index 806a67b977693..b742b93809247 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java @@ -42,15 +42,14 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result final int loadingUnitId = (int) args.get("loadingUnitId"); final String moduleName = (String) args.get("moduleName"); switch (method) { - case "DynamicFeature.installDynamicFeature": + case "installDynamicFeature": dynamicFeatureManager.installDynamicFeature(loadingUnitId, moduleName); if (!moduleNameToResults.containsKey(moduleName)) { moduleNameToResults.put(moduleName, new ArrayList<>()); - } else { - moduleNameToResults.get(moduleName).add(result); } + moduleNameToResults.get(moduleName).add(result); break; - case "DynamicFeature.getDynamicFeatureInstallState": + case "getDynamicFeatureInstallState": result.success(dynamicFeatureManager.getDynamicFeatureInstallState(loadingUnitId, moduleName)); break; default: @@ -70,7 +69,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result */ public DynamicFeatureChannel(@NonNull DartExecutor dartExecutor) { this.channel = - new MethodChannel(dartExecutor, "flutter/splitaot", StandardMethodCodec.INSTANCE); + new MethodChannel(dartExecutor, "flutter/dynamicfeature", StandardMethodCodec.INSTANCE); channel.setMethodCallHandler(parsingMethodHandler); dynamicFeatureManager = FlutterInjector.instance().dynamicFeatureManager(); moduleNameToResults = new HashMap<>(); From 1f82ef6c2339554ca23640f47eea45106f2bed05 Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 10 Dec 2020 18:33:03 -0800 Subject: [PATCH 11/24] Tests --- .../embedding/engine/FlutterEngine.java | 2 +- .../DynamicFeatureManager.java | 6 +- .../PlayStoreDynamicFeatureManager.java | 32 ++++--- .../systemchannels/DynamicFeatureChannel.java | 10 +- .../DynamicFeatureChannelTest.java | 95 +++++++++++++++---- 5 files changed, 104 insertions(+), 41 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 2cf62123061e8..3a6b333961ff5 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -278,7 +278,7 @@ public FlutterEngine( this.dartExecutor.onAttachedToJNI(); accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI); - dynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor); + dynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor, FlutterInjector.instance().dynamicFeatureManager()); keyEventChannel = new KeyEventChannel(dartExecutor); lifecycleChannel = new LifecycleChannel(dartExecutor); localizationChannel = new LocalizationChannel(dartExecutor); diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java index 759e15421bc8d..49ba3ba15dc20 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -75,12 +75,14 @@ public interface DynamicFeatureManager { * one of loadingUnitId or moduleName must be valid or non-null. * *

Flutter will typically call this method in two ways. When invoked as part of a dart - * loadLibrary() call, a valid loadingUnitId is passed in while the moduleName is null. In this + * `loadLibrary()` call, a valid loadingUnitId is passed in while the moduleName is null. In this * case, this method is responsible for figuring out what module the loadingUnitId corresponds to. * *

When invoked manually as part of loading an assets-only module, loadingUnitId is -1 * (invalid) and moduleName is supplied. Without a loadingUnitId, this method just downloads the - * module by name and attempts to load assets via loadAssets. + * module by name and attempts to load assets via loadAssets while loadDartLibrary is skipped, + * even if the dynamic feature module includes valid dart libs. To load these libs, call + * `loadLibrary()` on the dart library. * * @param loadingUnitId The unique identifier associated with a Dart deferred library. This id is * assigned by the compiler and can be seen for reference in bundle_config.yaml. This ID is diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index 45508bc54198c..ce560bd09135a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -76,7 +76,7 @@ public void onStateUpdate(SplitInstallSessionState state) { } sessionIdToName.delete(sessionId); sessionIdToLoadingUnitId.delete(sessionId); - sessionIdToState.put(sessionId, "Failed"); + sessionIdToState.put(sessionId, "failed"); break; } case SplitInstallSessionStatus.INSTALLED: @@ -91,16 +91,19 @@ public void onStateUpdate(SplitInstallSessionState state) { sessionIdToName.get(sessionId)); // We only load Dart shared lib for the loading unit id requested. Other loading units // (if present) in the dynamic feature module are not loaded, but can be loaded by - // calling again with their loading unit id. - loadDartLibrary( - sessionIdToLoadingUnitId.get(sessionId), - sessionIdToName.get(sessionId)); + // calling again with their loading unit id. If no valid loadingUnitId was included in + // the installation request such as for an asset only feature, then we can skip this. + if (sessionIdToLoadingUnitId.get(sessionId) > 0) { + loadDartLibrary( + sessionIdToLoadingUnitId.get(sessionId), + sessionIdToName.get(sessionId)); + } if (channel != null) { channel.completeInstallSuccess(sessionIdToName.get(sessionId)); } sessionIdToName.delete(sessionId); sessionIdToLoadingUnitId.delete(sessionId); - sessionIdToState.put(sessionId, "Installed"); + sessionIdToState.put(sessionId, "installed"); break; } case SplitInstallSessionStatus.CANCELED: @@ -117,7 +120,7 @@ public void onStateUpdate(SplitInstallSessionState state) { } sessionIdToName.delete(sessionId); sessionIdToLoadingUnitId.delete(sessionId); - sessionIdToState.put(sessionId, "Cancelled"); + sessionIdToState.put(sessionId, "cancelled"); break; } case SplitInstallSessionStatus.CANCELING: @@ -127,7 +130,7 @@ public void onStateUpdate(SplitInstallSessionState state) { String.format( "Module \"%s\" (sessionId %d) install canceling.", sessionIdToName.get(sessionId), sessionId)); - sessionIdToState.put(sessionId, "Canceling"); + sessionIdToState.put(sessionId, "canceling"); break; } case SplitInstallSessionStatus.PENDING: @@ -137,7 +140,7 @@ public void onStateUpdate(SplitInstallSessionState state) { String.format( "Module \"%s\" (sessionId %d) install pending.", sessionIdToName.get(sessionId), sessionId)); - sessionIdToState.put(sessionId, "Pending"); + sessionIdToState.put(sessionId, "pending"); break; } case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION: @@ -147,7 +150,7 @@ public void onStateUpdate(SplitInstallSessionState state) { String.format( "Module \"%s\" (sessionId %d) install requires user confirmation.", sessionIdToName.get(sessionId), sessionId)); - sessionIdToState.put(sessionId, "Requires User Confirmation"); + sessionIdToState.put(sessionId, "requires_user_confirmation"); break; } case SplitInstallSessionStatus.DOWNLOADING: @@ -157,7 +160,7 @@ public void onStateUpdate(SplitInstallSessionState state) { String.format( "Module \"%s\" (sessionId %d) downloading.", sessionIdToName.get(sessionId), sessionId)); - sessionIdToState.put(sessionId, "Downloading"); + sessionIdToState.put(sessionId, "downloading"); break; } case SplitInstallSessionStatus.DOWNLOADED: @@ -167,7 +170,7 @@ public void onStateUpdate(SplitInstallSessionState state) { String.format( "Module \"%s\" (sessionId %d) downloaded.", sessionIdToName.get(sessionId), sessionId)); - sessionIdToState.put(sessionId, "Downloaded"); + sessionIdToState.put(sessionId, "downloaded"); break; } case SplitInstallSessionStatus.INSTALLING: @@ -177,7 +180,7 @@ public void onStateUpdate(SplitInstallSessionState state) { String.format( "Module \"%s\" (sessionId %d) installing.", sessionIdToName.get(sessionId), sessionId)); - sessionIdToState.put(sessionId, "Installing"); + sessionIdToState.put(sessionId, "installing"); break; } default: @@ -228,10 +231,11 @@ private String loadingUnitIdToModuleName(int loadingUnitId) { } public void installDynamicFeature(int loadingUnitId, String moduleName) { + Log.e("flutter", "Install: " + loadingUnitId + " " + moduleName); String resolvedModuleName = moduleName != null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); if (resolvedModuleName == null) { - Log.d(TAG, "Dynamic feature module name was null."); + Log.d(TAG, "Dynamic feature module name was null and could not be resolved from loading unit id."); return; } diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java index b742b93809247..bcad66636baa6 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java @@ -7,6 +7,7 @@ import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.dart.DartExecutor; @@ -26,8 +27,8 @@ public class DynamicFeatureChannel { @Nullable DynamicFeatureManager dynamicFeatureManager; @NonNull Map> moduleNameToResults; - - private final MethodChannel.MethodCallHandler parsingMethodHandler = + @NonNull @VisibleForTesting + final MethodChannel.MethodCallHandler parsingMethodHandler = new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { @@ -36,7 +37,6 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result return; } String method = call.method; - // Object args = call.arguments; Map args = call.arguments(); Log.v(TAG, "Received '" + method + "' message."); final int loadingUnitId = (int) args.get("loadingUnitId"); @@ -67,11 +67,11 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result * *

See {@link DartExecutor}. */ - public DynamicFeatureChannel(@NonNull DartExecutor dartExecutor) { + public DynamicFeatureChannel(@NonNull DartExecutor dartExecutor, @Nullable DynamicFeatureManager featureManager) { this.channel = new MethodChannel(dartExecutor, "flutter/dynamicfeature", StandardMethodCodec.INSTANCE); channel.setMethodCallHandler(parsingMethodHandler); - dynamicFeatureManager = FlutterInjector.instance().dynamicFeatureManager(); + dynamicFeatureManager = featureManager != null ? featureManager : FlutterInjector.instance().dynamicFeatureManager(); moduleNameToResults = new HashMap<>(); } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java index 40a1ebcad5ab6..e99487b321619 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java @@ -7,39 +7,96 @@ import android.content.res.AssetManager; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.dynamicfeatures.DynamicFeatureManager; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; -import org.json.JSONException; -import org.json.JSONObject; +import java.util.HashMap; +import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Matchers; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +class TestDynamicFeatureManager implements DynamicFeatureManager { + DynamicFeatureChannel channel; + String moduleName; + public void setJNI(FlutterJNI flutterJNI) {} + public void setDynamicFeatureChannel(DynamicFeatureChannel channel) { + this.channel = channel; + } + public void installDynamicFeature(int loadingUnitId, String moduleName) { + this.moduleName = moduleName; + } + public void completeInstall() { + channel.completeInstallSuccess(moduleName); + } + public String getDynamicFeatureInstallState(int loadingUnitId, String moduleName) { + return "installed"; + } + public void loadAssets(int loadingUnitId, String moduleName) {} + public void loadDartLibrary(int loadingUnitId, String moduleName) {} + public void uninstallFeature(int loadingUnitId, String moduleName) {} + public void destroy() {} +} + @Config(manifest = Config.NONE) @RunWith(RobolectricTestRunner.class) public class DynamicFeatureChannelTest { @Test - public void dynamicFeatureChannel_hasStringsMessage() { + public void dynamicFeatureChannel_installCompletesResults() { + MethodChannel rawChannel = mock(MethodChannel.class); + FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); + TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); + DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); + + Map args = new HashMap<>(); + args.put('loadingUnitId', -1); + args.put('moduleName', "hello"); + MethodCall methodCall = new MethodCall("installDynamicFeature", args); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult); + + testDynamicFeatureManager.completeInstall(); + verify(mockResult).success(null); + } + + @Test + public void dynamicFeatureChannel_installCompletesMultipleResults() { + MethodChannel rawChannel = mock(MethodChannel.class); + FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); + DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor); + + Map args = new HashMap<>(); + args.put('loadingUnitId', -1); + args.put('moduleName', "hello"); + MethodCall methodCall = new MethodCall("installDynamicFeature", args); + MethodChannel.Result mockResult1 = mock(MethodChannel.Result.class); + MethodChannel.Result mockResult2 = mock(MethodChannel.Result.class); + fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult1); + fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult2); + + testDynamicFeatureManager.completeInstall(); + verify(mockResult1).success(null); + verify(mockResult2).success(null); + } + + @Test + public void dynamicFeatureChannel_getInstallState() { MethodChannel rawChannel = mock(MethodChannel.class); FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor); - // PlatformChannel.PlatformMessageHandler mockMessageHandler = - // mock(PlatformChannel.PlatformMessageHandler.class); - // fakePlatformChannel.setPlatformMessageHandler(mockMessageHandler); - // Boolean returnValue = true; - // when(mockMessageHandler.clipboardHasStrings()).thenReturn(returnValue); - // MethodCall methodCall = new MethodCall("Clipboard.hasStrings", null); - // MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - // fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult); - - // JSONObject expected = new JSONObject(); - // try { - // expected.put("value", returnValue); - // } catch (JSONException e) { - // } - // verify(mockResult).success(Matchers.refEq(expected)); + + Map args = new HashMap<>(); + args.put('loadingUnitId', -1); + args.put('moduleName', "hello"); + MethodCall methodCall = new MethodCall("getDynamicFeatureInstallState", args); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult); + + testDynamicFeatureManager.completeInstall(); + verify(mockResult).success("installed"); } } From 8bebbafcf6903353e03fb3c5ae81d9f2e353e52b Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 10 Dec 2020 18:55:13 -0800 Subject: [PATCH 12/24] Tests passing --- .../io/flutter/embedding/engine/FlutterEngine.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 3a6b333961ff5..a22705cbd86f6 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -12,6 +12,7 @@ import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.dynamicfeatures.DynamicFeatureManager; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.PluginRegistry; import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; @@ -277,8 +278,10 @@ public FlutterEngine( this.dartExecutor = new DartExecutor(flutterJNI, assetManager); this.dartExecutor.onAttachedToJNI(); + DynamicFeatureManager dynamicFeatureManager = FlutterInjector.instance().dynamicFeatureManager(); + accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI); - dynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor, FlutterInjector.instance().dynamicFeatureManager()); + dynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor, dynamicFeatureManager); keyEventChannel = new KeyEventChannel(dartExecutor); lifecycleChannel = new LifecycleChannel(dartExecutor); localizationChannel = new LocalizationChannel(dartExecutor); @@ -290,7 +293,9 @@ public FlutterEngine( systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); - FlutterInjector.instance().dynamicFeatureManager().setDynamicFeatureChannel(dynamicFeatureChannel); + if(dynamicFeatureManager != null) { + dynamicFeatureManager.setDynamicFeatureChannel(dynamicFeatureChannel); + } this.localizationPlugin = new LocalizationPlugin(context, localizationChannel); From a620ce3e07195659866226224a7bcbd15d006985 Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 10 Dec 2020 18:56:31 -0800 Subject: [PATCH 13/24] Licenses --- ci/licenses_golden/licenses_flutter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index b2c46e394f8f1..827129219ee43 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -783,7 +783,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/render FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/RenderSurface.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java -FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeaturesChannel.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java From 1d837de6f4fd3d53ca4fa403341c3214b428e27f Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 10 Dec 2020 19:10:19 -0800 Subject: [PATCH 14/24] Tests pass for real this time --- shell/platform/android/BUILD.gn | 1 + .../DynamicFeatureChannelTest.java | 26 ++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 9f2663e126f34..9aa06b754774b 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -478,6 +478,7 @@ action("robolectric_tests") { "test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java", "test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java", "test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java", + "test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java", "test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java", "test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java", "test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java", diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java index e99487b321619..e3bd111a5f161 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java @@ -51,11 +51,11 @@ public void dynamicFeatureChannel_installCompletesResults() { DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); Map args = new HashMap<>(); - args.put('loadingUnitId', -1); - args.put('moduleName', "hello"); + args.put("loadingUnitId", -1); + args.put("moduleName", "hello"); MethodCall methodCall = new MethodCall("installDynamicFeature", args); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult); + fakeDynamicFeatureChannel.parsingMethodHandler.onMethodCall(methodCall, mockResult); testDynamicFeatureManager.completeInstall(); verify(mockResult).success(null); @@ -66,16 +66,17 @@ public void dynamicFeatureChannel_installCompletesMultipleResults() { MethodChannel rawChannel = mock(MethodChannel.class); FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); - DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor); + TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); + DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); Map args = new HashMap<>(); - args.put('loadingUnitId', -1); - args.put('moduleName', "hello"); + args.put("loadingUnitId", -1); + args.put("moduleName", "hello"); MethodCall methodCall = new MethodCall("installDynamicFeature", args); MethodChannel.Result mockResult1 = mock(MethodChannel.Result.class); MethodChannel.Result mockResult2 = mock(MethodChannel.Result.class); - fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult1); - fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult2); + fakeDynamicFeatureChannel.parsingMethodHandler.onMethodCall(methodCall, mockResult1); + fakeDynamicFeatureChannel.parsingMethodHandler.onMethodCall(methodCall, mockResult2); testDynamicFeatureManager.completeInstall(); verify(mockResult1).success(null); @@ -87,14 +88,15 @@ public void dynamicFeatureChannel_getInstallState() { MethodChannel rawChannel = mock(MethodChannel.class); FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); - DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor); + TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); + DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); Map args = new HashMap<>(); - args.put('loadingUnitId', -1); - args.put('moduleName', "hello"); + args.put("loadingUnitId", -1); + args.put("moduleName", "hello"); MethodCall methodCall = new MethodCall("getDynamicFeatureInstallState", args); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult); + fakeDynamicFeatureChannel.parsingMethodHandler.onMethodCall(methodCall, mockResult); testDynamicFeatureManager.completeInstall(); verify(mockResult).success("installed"); From 88cafe4c5e7a71bb50739ad405aa49276aa1d0bb Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 10 Dec 2020 21:03:19 -0800 Subject: [PATCH 15/24] Formatting and some docs --- .../embedding/engine/FlutterEngine.java | 5 ++- .../DynamicFeatureManager.java | 10 ++--- .../PlayStoreDynamicFeatureManager.java | 19 ++++----- .../systemchannels/DynamicFeatureChannel.java | 40 ++++++++++++------- .../DynamicFeatureChannelTest.java | 18 +++++++-- 5 files changed, 56 insertions(+), 36 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index a22705cbd86f6..77591a922e885 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -278,7 +278,8 @@ public FlutterEngine( this.dartExecutor = new DartExecutor(flutterJNI, assetManager); this.dartExecutor.onAttachedToJNI(); - DynamicFeatureManager dynamicFeatureManager = FlutterInjector.instance().dynamicFeatureManager(); + DynamicFeatureManager dynamicFeatureManager = + FlutterInjector.instance().dynamicFeatureManager(); accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI); dynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor, dynamicFeatureManager); @@ -293,7 +294,7 @@ public FlutterEngine( systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); - if(dynamicFeatureManager != null) { + if (dynamicFeatureManager != null) { dynamicFeatureManager.setDynamicFeatureChannel(dynamicFeatureChannel); } diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java index 49ba3ba15dc20..d2bb3dc9a691d 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -103,8 +103,8 @@ public interface DynamicFeatureManager { public abstract void installDynamicFeature(int loadingUnitId, String moduleName); /** - * Gets the current state of the installation session corresponding to the specified - * loadingUnitId and/or moduleName. + * Gets the current state of the installation session corresponding to the specified loadingUnitId + * and/or moduleName. * *

Invocations of {@link installDynamicFeature} typically result in asynchronous downloading * and other tasks. This method enables querying of the state of the installation. If no dynamic @@ -122,9 +122,9 @@ public interface DynamicFeatureManager { * *

Both parameters are not always necessary to identify which module to install. Asset-only * modules do not have an associated loadingUnitId. Instead, an invalid ID like -1 may be passed - * to query only with moduleName. On the other hand, it can be possible to resolve the - * moduleName based on the loadingUnitId. This resolution is done if moduleName is null. At least - * one of loadingUnitId or moduleName must be valid or non-null. + * to query only with moduleName. On the other hand, it can be possible to resolve the moduleName + * based on the loadingUnitId. This resolution is done if moduleName is null. At least one of + * loadingUnitId or moduleName must be valid or non-null. * * @param loadingUnitId The unique identifier associated with a Dart deferred library. * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index ce560bd09135a..7ec3459fa693a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -62,17 +62,14 @@ public void onStateUpdate(SplitInstallSessionState state) { TAG, String.format( "Module \"%s\" (sessionId %d) install failed with: %s", - sessionIdToName.get(sessionId), - sessionId, - state.errorCode())); + sessionIdToName.get(sessionId), sessionId, state.errorCode())); flutterJNI.dynamicFeatureInstallFailure( sessionIdToLoadingUnitId.get(sessionId), "Module install failed with " + state.errorCode(), true); if (channel != null) { channel.completeInstallError( - sessionIdToName.get(sessionId), - "Android Dynamic Feature failed to install."); + sessionIdToName.get(sessionId), "Android Dynamic Feature failed to install."); } sessionIdToName.delete(sessionId); sessionIdToLoadingUnitId.delete(sessionId); @@ -86,17 +83,14 @@ public void onStateUpdate(SplitInstallSessionState state) { String.format( "Module \"%s\" (sessionId %d) install successfully.", sessionIdToName.get(sessionId), sessionId)); - loadAssets( - sessionIdToLoadingUnitId.get(sessionId), - sessionIdToName.get(sessionId)); + loadAssets(sessionIdToLoadingUnitId.get(sessionId), sessionIdToName.get(sessionId)); // We only load Dart shared lib for the loading unit id requested. Other loading units // (if present) in the dynamic feature module are not loaded, but can be loaded by // calling again with their loading unit id. If no valid loadingUnitId was included in // the installation request such as for an asset only feature, then we can skip this. if (sessionIdToLoadingUnitId.get(sessionId) > 0) { loadDartLibrary( - sessionIdToLoadingUnitId.get(sessionId), - sessionIdToName.get(sessionId)); + sessionIdToLoadingUnitId.get(sessionId), sessionIdToName.get(sessionId)); } if (channel != null) { channel.completeInstallSuccess(sessionIdToName.get(sessionId)); @@ -231,11 +225,12 @@ private String loadingUnitIdToModuleName(int loadingUnitId) { } public void installDynamicFeature(int loadingUnitId, String moduleName) { - Log.e("flutter", "Install: " + loadingUnitId + " " + moduleName); String resolvedModuleName = moduleName != null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); if (resolvedModuleName == null) { - Log.d(TAG, "Dynamic feature module name was null and could not be resolved from loading unit id."); + Log.d( + TAG, + "Dynamic feature module name was null and could not be resolved from loading unit id."); return; } diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java index bcad66636baa6..1a485cf9b43ec 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java @@ -4,7 +4,6 @@ package io.flutter.embedding.engine.systemchannels; -import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -12,20 +11,27 @@ import io.flutter.Log; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dynamicfeatures.DynamicFeatureManager; -import io.flutter.plugin.common.StandardMethodCodec; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.StandardMethodCodec; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * Method channel that handles manual installation requests and queries for installation + * state for dynamic feature modules. + */ public class DynamicFeatureChannel { private static final String TAG = "DynamicFeatureChannel"; - @NonNull public final MethodChannel channel; - @Nullable DynamicFeatureManager dynamicFeatureManager; - @NonNull Map> moduleNameToResults; + @NonNull private final MethodChannel channel; + @Nullable private DynamicFeatureManager dynamicFeatureManager; + // Track the Result objects to be able to handle multiple install requests of + // the same module at a time. When installation enters a terminal state, either + // completeInstallSuccess or completeInstallError can be called. + @NonNull private Map> moduleNameToResults; @NonNull @VisibleForTesting final MethodChannel.MethodCallHandler parsingMethodHandler = @@ -50,7 +56,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result moduleNameToResults.get(moduleName).add(result); break; case "getDynamicFeatureInstallState": - result.success(dynamicFeatureManager.getDynamicFeatureInstallState(loadingUnitId, moduleName)); + result.success( + dynamicFeatureManager.getDynamicFeatureInstallState(loadingUnitId, moduleName)); break; default: result.notImplemented(); @@ -60,24 +67,29 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result }; /** - * Constructs a {@code DynamicFeatureChannel} that connects Android to the Dart code running in {@code - * dartExecutor}. + * Constructs a {@code DynamicFeatureChannel} that connects Android to the Dart code running in + * {@code dartExecutor}. * *

The given {@code dartExecutor} is permitted to be idle or executing code. * *

See {@link DartExecutor}. */ - public DynamicFeatureChannel(@NonNull DartExecutor dartExecutor, @Nullable DynamicFeatureManager featureManager) { + public DynamicFeatureChannel( + @NonNull DartExecutor dartExecutor, @Nullable DynamicFeatureManager featureManager) { this.channel = new MethodChannel(dartExecutor, "flutter/dynamicfeature", StandardMethodCodec.INSTANCE); channel.setMethodCallHandler(parsingMethodHandler); - dynamicFeatureManager = featureManager != null ? featureManager : FlutterInjector.instance().dynamicFeatureManager(); + ddynamicFeatureManager = + featureManager != null + ? featureManager + : FlutterInjector.instance().dynamicFeatureManager(); + moduleNameToResults = new HashMap<>(); moduleNameToResults = new HashMap<>(); } /** - * Finishes the `installDynamicFeature` method channel call for the specified moduleName - * with a success. + * Finishes the `installDynamicFeature` method channel call for the specified moduleName with a + * success. * * @param moduleName The name of the android dynamic feature module install request to complete. */ @@ -92,8 +104,8 @@ public void completeInstallSuccess(String moduleName) { } /** - * Finishes the `installDynamicFeature` method channel call for the specified moduleName - * with an error/failure. + * Finishes the `installDynamicFeature` method channel call for the specified moduleName with an + * error/failure. * * @param moduleName The name of the android dynamic feature module install request to complete. * @param errorMessage The error message to display to complete the future with. diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java index e3bd111a5f161..f7d9de8e5cf73 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java @@ -20,22 +20,31 @@ class TestDynamicFeatureManager implements DynamicFeatureManager { DynamicFeatureChannel channel; String moduleName; + public void setJNI(FlutterJNI flutterJNI) {} + public void setDynamicFeatureChannel(DynamicFeatureChannel channel) { this.channel = channel; } + public void installDynamicFeature(int loadingUnitId, String moduleName) { this.moduleName = moduleName; } + public void completeInstall() { channel.completeInstallSuccess(moduleName); } + public String getDynamicFeatureInstallState(int loadingUnitId, String moduleName) { return "installed"; } + public void loadAssets(int loadingUnitId, String moduleName) {} + public void loadDartLibrary(int loadingUnitId, String moduleName) {} + public void uninstallFeature(int loadingUnitId, String moduleName) {} + public void destroy() {} } @@ -48,7 +57,8 @@ public void dynamicFeatureChannel_installCompletesResults() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); - DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); + DynamicFeatureChannel fakeDynamicFeatureChannel = + new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); Map args = new HashMap<>(); args.put("loadingUnitId", -1); @@ -67,7 +77,8 @@ public void dynamicFeatureChannel_installCompletesMultipleResults() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); - DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); + DynamicFeatureChannel fakeDynamicFeatureChannel = new + DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); Map args = new HashMap<>(); args.put("loadingUnitId", -1); @@ -89,7 +100,8 @@ public void dynamicFeatureChannel_getInstallState() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); - DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); + DynamicFeatureChannel fakeDynamicFeatureChannel = + new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); Map args = new HashMap<>(); args.put("loadingUnitId", -1); From 2b96f178e1947220216184d5169c2fedb0bd2210 Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 10 Dec 2020 22:33:35 -0800 Subject: [PATCH 16/24] Address comments --- .../embedding/engine/FlutterEngine.java | 1 + .../DynamicFeatureManager.java | 13 ++++++---- .../PlayStoreDynamicFeatureManager.java | 1 + .../systemchannels/DynamicFeatureChannel.java | 24 ++++++++++++++----- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 77591a922e885..0060fa7ef542c 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -389,6 +389,7 @@ public void destroy() { flutterJNI.removeEngineLifecycleListener(engineLifecycleListener); flutterJNI.setDynamicFeatureManager(null); flutterJNI.detachFromNativeAndReleaseResources(); + dynamicFeatureChannel.destroy(); if (FlutterInjector.instance().dynamicFeatureManager() != null) { FlutterInjector.instance().dynamicFeatureManager().destroy(); } diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java index d2bb3dc9a691d..707e854f850db 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -107,13 +107,16 @@ public interface DynamicFeatureManager { * and/or moduleName. * *

Invocations of {@link installDynamicFeature} typically result in asynchronous downloading - * and other tasks. This method enables querying of the state of the installation. If no dynamic - * feature has been installed or requested to be installed by the provided loadingUnitId or - * moduleName, then this method will return null. + * and other tasks. This method enables querying of the state of the installation. Querying the + * installation state is purely informational and does not impact the installation process. Upon + * completion of installation, the Future returned by the installation request will complete. + * + *

If no dynamic feature has been installed or requested to be installed by the provided + * loadingUnitId or moduleName, then this method will return null. * *

Depending on the implementation, the returned String may vary. The Play store default - * implementation begins in the "Requested" state before transitioning to the "Downloading" and - * "Installed" states. + * implementation begins in the "requested" state before transitioning to the "downloading" and + * "installed" states. * *

Only sucessfully requested modules have state. Modules that are invalid or have not been * requested with {@link installDynamicFeature} will not have a state. Due to the asynchronous diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index 7ec3459fa693a..ca09ab2d6089a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -388,6 +388,7 @@ public void uninstallFeature(int loadingUnitId, String moduleName) { public void destroy() { splitInstallManager.unregisterListener(listener); + channel = null; flutterJNI = null; } } diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java index 1a485cf9b43ec..de26c8b4a6cb3 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java @@ -22,6 +22,8 @@ /** * Method channel that handles manual installation requests and queries for installation * state for dynamic feature modules. + * + * This channel is able to handle multiple simultaneous installation requests */ public class DynamicFeatureChannel { private static final String TAG = "DynamicFeatureChannel"; @@ -75,18 +77,24 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result *

See {@link DartExecutor}. */ public DynamicFeatureChannel( - @NonNull DartExecutor dartExecutor, @Nullable DynamicFeatureManager featureManager) { + @NonNull DartExecutor dartExecutor) { this.channel = new MethodChannel(dartExecutor, "flutter/dynamicfeature", StandardMethodCodec.INSTANCE); channel.setMethodCallHandler(parsingMethodHandler); - ddynamicFeatureManager = - featureManager != null - ? featureManager - : FlutterInjector.instance().dynamicFeatureManager(); - moduleNameToResults = new HashMap<>(); + dynamicFeatureManager = FlutterInjector.instance().dynamicFeatureManager(); moduleNameToResults = new HashMap<>(); } + /** + * Sets the DynamicFeatureManager to exectue method channel calls with. + * + * @param dynamicFeatureManager the DynamicFeatureManager to use. + */ + @VisibleForTesting + public void setDynamicFeatureManager(@Nullable DynamicFeatureManager dynamicFeatureManager) { + this.dynamicFeatureManager = dynamicFeatureManager; + } + /** * Finishes the `installDynamicFeature` method channel call for the specified moduleName with a * success. @@ -119,4 +127,8 @@ public void completeInstallError(String moduleName, String errorMessage) { } return; } + + public void destroy() { + dynamicFeatureManager = null; + } } From e0a417bd937f7c5eb7179ea2ff684f15217da1d7 Mon Sep 17 00:00:00 2001 From: garyqian Date: Fri, 11 Dec 2020 13:29:10 -0800 Subject: [PATCH 17/24] Formatting --- .../engine/systemchannels/DynamicFeatureChannel.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java index de26c8b4a6cb3..f73476810a3e4 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java @@ -20,10 +20,10 @@ import java.util.Map; /** - * Method channel that handles manual installation requests and queries for installation - * state for dynamic feature modules. + * Method channel that handles manual installation requests and queries for installation state for + * dynamic feature modules. * - * This channel is able to handle multiple simultaneous installation requests + *

This channel is able to handle multiple simultaneous installation requests */ public class DynamicFeatureChannel { private static final String TAG = "DynamicFeatureChannel"; @@ -76,8 +76,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result * *

See {@link DartExecutor}. */ - public DynamicFeatureChannel( - @NonNull DartExecutor dartExecutor) { + public DynamicFeatureChannel(@NonNull DartExecutor dartExecutor) { this.channel = new MethodChannel(dartExecutor, "flutter/dynamicfeature", StandardMethodCodec.INSTANCE); channel.setMethodCallHandler(parsingMethodHandler); From 8a2f11c560e928420bdc380fdfc7bd3a311dda27 Mon Sep 17 00:00:00 2001 From: garyqian Date: Fri, 11 Dec 2020 13:34:50 -0800 Subject: [PATCH 18/24] Formatting again --- .../engine/systemchannels/DynamicFeatureChannelTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java index f7d9de8e5cf73..2d7c64b2c1aad 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java @@ -77,8 +77,8 @@ public void dynamicFeatureChannel_installCompletesMultipleResults() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); - DynamicFeatureChannel fakeDynamicFeatureChannel = new - DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); + DynamicFeatureChannel fakeDynamicFeatureChannel = + new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); Map args = new HashMap<>(); args.put("loadingUnitId", -1); From cf6fd7311345d2bd9833f564e9ea402bea96ebf2 Mon Sep 17 00:00:00 2001 From: garyqian Date: Fri, 11 Dec 2020 13:40:50 -0800 Subject: [PATCH 19/24] Clean/remove extra imports --- .../engine/systemchannels/DynamicFeatureChannelTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java index 2d7c64b2c1aad..8e0e3718c3af6 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java @@ -2,7 +2,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.content.res.AssetManager; import io.flutter.embedding.engine.FlutterJNI; From a10ee50fb6271c3c285a887044a432ad0431b77e Mon Sep 17 00:00:00 2001 From: garyqian Date: Fri, 11 Dec 2020 23:17:19 -0800 Subject: [PATCH 20/24] Docs, lints, address comments --- .../embedding/engine/FlutterEngine.java | 4 +-- .../DynamicFeatureManager.java | 31 ++++++++++++++++--- .../PlayStoreDynamicFeatureManager.java | 4 ++- .../systemchannels/DynamicFeatureChannel.java | 4 --- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 0060fa7ef542c..ec489f25ae667 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -282,7 +282,7 @@ public FlutterEngine( FlutterInjector.instance().dynamicFeatureManager(); accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI); - dynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor, dynamicFeatureManager); + dynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor); keyEventChannel = new KeyEventChannel(dartExecutor); lifecycleChannel = new LifecycleChannel(dartExecutor); localizationChannel = new LocalizationChannel(dartExecutor); @@ -389,9 +389,9 @@ public void destroy() { flutterJNI.removeEngineLifecycleListener(engineLifecycleListener); flutterJNI.setDynamicFeatureManager(null); flutterJNI.detachFromNativeAndReleaseResources(); - dynamicFeatureChannel.destroy(); if (FlutterInjector.instance().dynamicFeatureManager() != null) { FlutterInjector.instance().dynamicFeatureManager().destroy(); + dynamicFeatureChannel.setDynamicFeatureManager(null); } } diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java index 707e854f850db..012b8260fb88f 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -56,6 +56,15 @@ public interface DynamicFeatureManager { * *

Since this class may be instantiated for injection before the FlutterEngine and System * Channels are initialized, this method should be called to provide the DynamicFeatureChannel. + * Similarly, the {@link DynamicFeatureChannel.setDynamicFeatureManager} method should also be + * called with this DynamicFeatureManager instance to properly forward method invocations. + * + *

The {@link DynamicFeatureChannel} passes manual invocations of {@link installDynamicFeature} + * and {@link getDynamicFeatureInstallState} from the method channel to this + * DynamicFeatureManager. Upon completion of the install process, sucessful installations should + * notify the DynamicFeatureChannel by calling {@link + * DynamicFeatureChannel.completeInstallSuccess} while errors and failures should call + * {@link DynamicFeatureChannel.completeInstallError}. */ public abstract void setDynamicFeatureChannel(DynamicFeatureChannel channel); @@ -66,7 +75,10 @@ public interface DynamicFeatureManager { * example, the Play Store dynamic delivery implementation uses SplitInstallManager to request the * download of the module. Download is not complete when this method returns. The download process * should be listened for and upon completion of download, listeners should invoke loadAssets - * first and then loadDartLibrary to complete the dynamic feature load process. + * first and then loadDartLibrary to complete the dynamic feature load process. Assets-only + * dynamic features should also call {@link DynamicFeatureChannel.completeInstallSuccess} or + * {@link DynamicFeatureChannel.completeInstallError} to complete the method channel invocation's + * dart Future. * *

Both parameters are not always necessary to identify which module to install. Asset-only * modules do not have an associated loadingUnitId. Instead, an invalid ID like -1 may be passed @@ -81,8 +93,15 @@ public interface DynamicFeatureManager { *

When invoked manually as part of loading an assets-only module, loadingUnitId is -1 * (invalid) and moduleName is supplied. Without a loadingUnitId, this method just downloads the * module by name and attempts to load assets via loadAssets while loadDartLibrary is skipped, - * even if the dynamic feature module includes valid dart libs. To load these libs, call - * `loadLibrary()` on the dart library. + * even if the dynamic feature module includes valid dart libs. To load dart libs, call + * `loadLibrary()` using the first way described in the previous paragraph as the method channel + * invocation will not load dart shared libraries. + * + *

While the Future retuned by either `loadLibary` or the method channel invocation will + * indicate when the code and assets are ready to be used, informational querying of the + * install process' state can be done with {@link getDynamicFeatureInstallState}, though + * the results of this query should not be used to decide if the dynamic feature is ready + * to use. Only the Future completion should be used to do this. * * @param loadingUnitId The unique identifier associated with a Dart deferred library. This id is * assigned by the compiler and can be seen for reference in bundle_config.yaml. This ID is @@ -108,8 +127,10 @@ public interface DynamicFeatureManager { * *

Invocations of {@link installDynamicFeature} typically result in asynchronous downloading * and other tasks. This method enables querying of the state of the installation. Querying the - * installation state is purely informational and does not impact the installation process. Upon - * completion of installation, the Future returned by the installation request will complete. + * installation state is purely informational and does not impact the installation process. The + * results of this query should not be used to decide if the dynamic feature is ready to use. + * Upon completion of installation, the Future returned by the installation request will complete. + * Only after dart Future completion is it safe to use code and assets from the dynamic feature. * *

If no dynamic feature has been installed or requested to be installed by the provided * loadingUnitId or moduleName, then this method will return null. diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index ca09ab2d6089a..bd66e1e21119a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -4,6 +4,7 @@ package io.flutter.embedding.engine.dynamicfeatures; +import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetManager; @@ -52,6 +53,7 @@ public class PlayStoreDynamicFeatureManager implements DynamicFeatureManager { private FeatureInstallStateUpdatedListener listener; private class FeatureInstallStateUpdatedListener implements SplitInstallStateUpdatedListener { + @SuppressLint("DefaultLocale") public void onStateUpdate(SplitInstallSessionState state) { int sessionId = state.sessionId(); if (sessionIdToName.get(sessionId) != null) { @@ -228,7 +230,7 @@ public void installDynamicFeature(int loadingUnitId, String moduleName) { String resolvedModuleName = moduleName != null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); if (resolvedModuleName == null) { - Log.d( + Log.e( TAG, "Dynamic feature module name was null and could not be resolved from loading unit id."); return; diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java index f73476810a3e4..4535fff8685ef 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannel.java @@ -126,8 +126,4 @@ public void completeInstallError(String moduleName, String errorMessage) { } return; } - - public void destroy() { - dynamicFeatureManager = null; - } } From dc9dd2f8b47dda3d55058f5a43625a177d9dafe9 Mon Sep 17 00:00:00 2001 From: garyqian Date: Fri, 11 Dec 2020 23:22:07 -0800 Subject: [PATCH 21/24] Fix nit --- .../dynamicfeatures/PlayStoreDynamicFeatureManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index bd66e1e21119a..2e1b8592a2dd4 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -293,7 +293,9 @@ public String getDynamicFeatureInstallState(int loadingUnitId, String moduleName String resolvedModuleName = moduleName != null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); if (resolvedModuleName == null) { - Log.d(TAG, "Dynamic feature module name was null."); + Log.e( + TAG, + "Dynamic feature module name was null and could not be resolved from loading unit id."); return null; } if (!nameToSessionId.containsKey(resolvedModuleName)) { From c826e2722f156c5fdf0a53c699fc0d0eaadd541f Mon Sep 17 00:00:00 2001 From: garyqian Date: Fri, 11 Dec 2020 23:33:37 -0800 Subject: [PATCH 22/24] Formatting --- .../dynamicfeatures/DynamicFeatureManager.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java index 012b8260fb88f..5acdf59c06602 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -63,8 +63,8 @@ public interface DynamicFeatureManager { * and {@link getDynamicFeatureInstallState} from the method channel to this * DynamicFeatureManager. Upon completion of the install process, sucessful installations should * notify the DynamicFeatureChannel by calling {@link - * DynamicFeatureChannel.completeInstallSuccess} while errors and failures should call - * {@link DynamicFeatureChannel.completeInstallError}. + * DynamicFeatureChannel.completeInstallSuccess} while errors and failures should call {@link + * DynamicFeatureChannel.completeInstallError}. */ public abstract void setDynamicFeatureChannel(DynamicFeatureChannel channel); @@ -98,10 +98,10 @@ public interface DynamicFeatureManager { * invocation will not load dart shared libraries. * *

While the Future retuned by either `loadLibary` or the method channel invocation will - * indicate when the code and assets are ready to be used, informational querying of the - * install process' state can be done with {@link getDynamicFeatureInstallState}, though - * the results of this query should not be used to decide if the dynamic feature is ready - * to use. Only the Future completion should be used to do this. + * indicate when the code and assets are ready to be used, informational querying of the install + * process' state can be done with {@link getDynamicFeatureInstallState}, though the results of + * this query should not be used to decide if the dynamic feature is ready to use. Only the Future + * completion should be used to do this. * * @param loadingUnitId The unique identifier associated with a Dart deferred library. This id is * assigned by the compiler and can be seen for reference in bundle_config.yaml. This ID is @@ -128,9 +128,9 @@ public interface DynamicFeatureManager { *

Invocations of {@link installDynamicFeature} typically result in asynchronous downloading * and other tasks. This method enables querying of the state of the installation. Querying the * installation state is purely informational and does not impact the installation process. The - * results of this query should not be used to decide if the dynamic feature is ready to use. - * Upon completion of installation, the Future returned by the installation request will complete. - * Only after dart Future completion is it safe to use code and assets from the dynamic feature. + * results of this query should not be used to decide if the dynamic feature is ready to use. Upon + * completion of installation, the Future returned by the installation request will complete. Only + * after dart Future completion is it safe to use code and assets from the dynamic feature. * *

If no dynamic feature has been installed or requested to be installed by the provided * loadingUnitId or moduleName, then this method will return null. From 7841a716c9f8142740da5f7ef9e4285bd46ad436 Mon Sep 17 00:00:00 2001 From: garyqian Date: Sat, 12 Dec 2020 16:23:25 -0800 Subject: [PATCH 23/24] Fix test --- .../engine/systemchannels/DynamicFeatureChannelTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java index 8e0e3718c3af6..f45040227a4c2 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java @@ -57,7 +57,8 @@ public void dynamicFeatureChannel_installCompletesResults() { DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); DynamicFeatureChannel fakeDynamicFeatureChannel = - new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); + new DynamicFeatureChannel(dartExecutor); + fakeDynamicFeatureChannel.setDynamicFeatureManager(testDynamicFeatureManager); Map args = new HashMap<>(); args.put("loadingUnitId", -1); @@ -77,7 +78,8 @@ public void dynamicFeatureChannel_installCompletesMultipleResults() { DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); DynamicFeatureChannel fakeDynamicFeatureChannel = - new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); + new DynamicFeatureChannel(dartExecutor); + fakeDynamicFeatureChannel.setDynamicFeatureManager(testDynamicFeatureManager); Map args = new HashMap<>(); args.put("loadingUnitId", -1); @@ -100,7 +102,8 @@ public void dynamicFeatureChannel_getInstallState() { DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); DynamicFeatureChannel fakeDynamicFeatureChannel = - new DynamicFeatureChannel(dartExecutor, testDynamicFeatureManager); + new DynamicFeatureChannel(dartExecutor); + fakeDynamicFeatureChannel.setDynamicFeatureManager(testDynamicFeatureManager); Map args = new HashMap<>(); args.put("loadingUnitId", -1); From 587a896b087d518cc293436957e11c2a6e3a78a8 Mon Sep 17 00:00:00 2001 From: garyqian Date: Sat, 12 Dec 2020 19:42:27 -0800 Subject: [PATCH 24/24] Formatting againg: --- .../engine/systemchannels/DynamicFeatureChannelTest.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java index f45040227a4c2..591d063b2a8e3 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/DynamicFeatureChannelTest.java @@ -56,8 +56,7 @@ public void dynamicFeatureChannel_installCompletesResults() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); - DynamicFeatureChannel fakeDynamicFeatureChannel = - new DynamicFeatureChannel(dartExecutor); + DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor); fakeDynamicFeatureChannel.setDynamicFeatureManager(testDynamicFeatureManager); Map args = new HashMap<>(); @@ -77,8 +76,7 @@ public void dynamicFeatureChannel_installCompletesMultipleResults() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); - DynamicFeatureChannel fakeDynamicFeatureChannel = - new DynamicFeatureChannel(dartExecutor); + DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor); fakeDynamicFeatureChannel.setDynamicFeatureManager(testDynamicFeatureManager); Map args = new HashMap<>(); @@ -101,8 +99,7 @@ public void dynamicFeatureChannel_getInstallState() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); TestDynamicFeatureManager testDynamicFeatureManager = new TestDynamicFeatureManager(); - DynamicFeatureChannel fakeDynamicFeatureChannel = - new DynamicFeatureChannel(dartExecutor); + DynamicFeatureChannel fakeDynamicFeatureChannel = new DynamicFeatureChannel(dartExecutor); fakeDynamicFeatureChannel.setDynamicFeatureManager(testDynamicFeatureManager); Map args = new HashMap<>();