From b9fbeca5b3dda55ea8c2f52f164bb41776e192dd Mon Sep 17 00:00:00 2001 From: Lau Ching Jun Date: Mon, 25 Jan 2021 16:34:27 -0800 Subject: [PATCH] Allow naming shared libraries in deferred component via AndroidManifest (flutter/engine#23925) --- .../PlayStoreDeferredComponentManager.java | 28 +++++-- ...PlayStoreDeferredComponentManagerTest.java | 84 +++++++++++++------ 2 files changed, 81 insertions(+), 31 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java index 829108651a0..a19d6fc3613 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java @@ -44,6 +44,9 @@ import java.util.Queue; public class PlayStoreDeferredComponentManager implements DeferredComponentManager { private static final String TAG = "PlayStoreDeferredComponentManager"; + public static final String MAPPING_KEY = + DeferredComponentManager.class.getName() + ".loadingUnitMapping"; + private @NonNull SplitInstallManager splitInstallManager; private @Nullable FlutterJNI flutterJNI; private @Nullable DeferredComponentChannel channel; @@ -57,6 +60,7 @@ public class PlayStoreDeferredComponentManager implements DeferredComponentManag private @NonNull Map nameToSessionId; protected @NonNull SparseArray loadingUnitIdToModuleNames; + protected @NonNull SparseArray loadingUnitIdToSharedLibraryNames; private FeatureInstallStateUpdatedListener listener; @@ -209,6 +213,7 @@ public class PlayStoreDeferredComponentManager implements DeferredComponentManag nameToSessionId = new HashMap<>(); loadingUnitIdToModuleNames = new SparseArray<>(); + loadingUnitIdToSharedLibraryNames = new SparseArray<>(); initLoadingUnitMappingToModuleNames(); } @@ -243,27 +248,33 @@ public class PlayStoreDeferredComponentManager implements DeferredComponentManag // Obtain and parses the metadata string. An example encoded string is: // - // "2:module2,3:module3,4:module1" + // "2:module2,3:module3,4:module1:libmodule4.so" // // Where loading unit 2 is included in module2, loading unit 3 is // included in module3, and loading unit 4 is included in module1. + // An optional third parameter can be added to indicate the name of + // the shared library of the loading unit. private void initLoadingUnitMappingToModuleNames() { String mappingKey = DeferredComponentManager.class.getName() + ".loadingUnitMapping"; ApplicationInfo applicationInfo = getApplicationInfo(); if (applicationInfo != null) { Bundle metaData = applicationInfo.metaData; if (metaData != null) { - String rawMappingString = metaData.getString(mappingKey, null); + String rawMappingString = metaData.getString(MAPPING_KEY, null); if (rawMappingString == null) { Log.e( TAG, "No loading unit to dynamic feature module name found. Ensure '" - + mappingKey + + MAPPING_KEY + "' is defined in the base module's AndroidManifest."); } else { for (String entry : rawMappingString.split(",")) { String[] splitEntry = entry.split(":"); - loadingUnitIdToModuleNames.put(Integer.parseInt(splitEntry[0]), splitEntry[1]); + int loadingUnitId = Integer.parseInt(splitEntry[0]); + loadingUnitIdToModuleNames.put(loadingUnitId, splitEntry[1]); + if (splitEntry.length > 2) { + loadingUnitIdToSharedLibraryNames.put(loadingUnitId, splitEntry[2]); + } } } } @@ -379,9 +390,12 @@ public class PlayStoreDeferredComponentManager implements DeferredComponentManag return; } - // This matches/depends on dart's loading unit naming convention, which we use unchanged. - String aotSharedLibraryName = - flutterApplicationInfo.aotSharedLibraryName + "-" + loadingUnitId + ".part.so"; + String aotSharedLibraryName = loadingUnitIdToSharedLibraryNames.get(loadingUnitId); + if (aotSharedLibraryName == null) { + // If the filename is not specified, we use dart's loading unit naming convention. + aotSharedLibraryName = + flutterApplicationInfo.aotSharedLibraryName + "-" + loadingUnitId + ".part.so"; + } // Possible values: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips, mips64 String abi; diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java index 3157e9861f2..68180f4333a 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java @@ -20,7 +20,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetManager; import android.os.Bundle; -import android.util.SparseArray; import androidx.annotation.NonNull; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.loader.ApplicationInfoLoader; @@ -72,9 +71,6 @@ public class PlayStoreDeferredComponentManagerTest { private class TestPlayStoreDeferredComponentManager extends PlayStoreDeferredComponentManager { public TestPlayStoreDeferredComponentManager(Context context, FlutterJNI jni) { super(context, jni); - loadingUnitIdToModuleNames = new SparseArray<>(); - loadingUnitIdToModuleNames.put(5, "FakeModuleName5"); - loadingUnitIdToModuleNames.put(2, "FakeModuleName2"); } @Override @@ -85,11 +81,25 @@ public class PlayStoreDeferredComponentManagerTest { } } + private Context createSpyContext(Bundle metadata) throws NameNotFoundException { + Context spyContext = spy(RuntimeEnvironment.application); + doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt()); + if (metadata == null) { + metadata = new Bundle(); + } + PackageManager packageManager = mock(PackageManager.class); + ApplicationInfo applicationInfo = mock(ApplicationInfo.class); + applicationInfo.metaData = metadata; + when(packageManager.getApplicationInfo(any(String.class), any(int.class))) + .thenReturn(applicationInfo); + doReturn(packageManager).when(spyContext).getPackageManager(); + return spyContext; + } + @Test public void downloadCallsJNIFunctions() throws NameNotFoundException { TestFlutterJNI jni = new TestFlutterJNI(); - Context spyContext = spy(RuntimeEnvironment.application); - doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt()); + Context spyContext = createSpyContext(null); doReturn(null).when(spyContext).getAssets(); String soTestFilename = "libapp.so-123.part.so"; String soTestPath = "test/path/" + soTestFilename; @@ -114,19 +124,13 @@ public class PlayStoreDeferredComponentManagerTest { @Test public void downloadCallsJNIFunctionsWithFilenameFromManifest() throws NameNotFoundException { TestFlutterJNI jni = new TestFlutterJNI(); - Context spyContext = spy(RuntimeEnvironment.application); - doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt()); - doReturn(null).when(spyContext).getAssets(); Bundle bundle = new Bundle(); bundle.putString(ApplicationInfoLoader.PUBLIC_AOT_SHARED_LIBRARY_NAME, "custom_name.so"); bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets"); - PackageManager packageManager = mock(PackageManager.class); - ApplicationInfo applicationInfo = mock(ApplicationInfo.class); - applicationInfo.metaData = bundle; - when(packageManager.getApplicationInfo(any(String.class), any(int.class))) - .thenReturn(applicationInfo); - doReturn(packageManager).when(spyContext).getPackageManager(); + + Context spyContext = createSpyContext(bundle); + doReturn(null).when(spyContext).getAssets(); String soTestFilename = "custom_name.so-123.part.so"; String soTestPath = "test/path/" + soTestFilename; @@ -148,11 +152,42 @@ public class PlayStoreDeferredComponentManagerTest { assertEquals(jni.assetBundlePath, "custom_assets"); } + @Test + public void downloadCallsJNIFunctionsWithSharedLibraryNameFromManifest() + throws NameNotFoundException { + TestFlutterJNI jni = new TestFlutterJNI(); + + Bundle bundle = new Bundle(); + bundle.putString(PlayStoreDeferredComponentManager.MAPPING_KEY, "123:module:custom_name.so"); + bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets"); + + Context spyContext = createSpyContext(bundle); + doReturn(null).when(spyContext).getAssets(); + + String soTestFilename = "custom_name.so"; + String soTestPath = "test/path/" + soTestFilename; + doReturn(new File(soTestPath)).when(spyContext).getFilesDir(); + TestPlayStoreDeferredComponentManager playStoreManager = + new TestPlayStoreDeferredComponentManager(spyContext, jni); + jni.setDeferredComponentManager(playStoreManager); + assertEquals(jni.loadingUnitId, 0); + + playStoreManager.installDeferredComponent(123, "TestModuleName"); + assertEquals(jni.loadDartDeferredLibraryCalled, 1); + assertEquals(jni.updateAssetManagerCalled, 1); + assertEquals(jni.deferredComponentInstallFailureCalled, 0); + + assertEquals(jni.searchPaths[0], soTestFilename); + assertTrue(jni.searchPaths[1].endsWith(soTestPath)); + assertEquals(jni.searchPaths.length, 2); + assertEquals(jni.loadingUnitId, 123); + assertEquals(jni.assetBundlePath, "custom_assets"); + } + @Test public void searchPathsAddsApks() throws NameNotFoundException { TestFlutterJNI jni = new TestFlutterJNI(); - Context spyContext = spy(RuntimeEnvironment.application); - doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt()); + Context spyContext = createSpyContext(null); doReturn(null).when(spyContext).getAssets(); String apkTestPath = "test/path/TestModuleName_armeabi_v7a.apk"; doReturn(new File(apkTestPath)).when(spyContext).getFilesDir(); @@ -176,8 +211,7 @@ public class PlayStoreDeferredComponentManagerTest { @Test public void invalidSearchPathsAreIgnored() throws NameNotFoundException { TestFlutterJNI jni = new TestFlutterJNI(); - Context spyContext = spy(RuntimeEnvironment.application); - doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt()); + Context spyContext = createSpyContext(null); doReturn(null).when(spyContext).getAssets(); String apkTestPath = "test/path/invalidpath.apk"; doReturn(new File(apkTestPath)).when(spyContext).getFilesDir(); @@ -200,8 +234,8 @@ public class PlayStoreDeferredComponentManagerTest { @Test public void assetManagerUpdateInvoked() throws NameNotFoundException { TestFlutterJNI jni = new TestFlutterJNI(); - Context spyContext = spy(RuntimeEnvironment.application); - doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt()); + Context spyContext = createSpyContext(null); + doReturn(null).when(spyContext).getAssets(); AssetManager assetManager = spyContext.getAssets(); String apkTestPath = "blah doesn't matter here"; doReturn(new File(apkTestPath)).when(spyContext).getFilesDir(); @@ -222,8 +256,8 @@ public class PlayStoreDeferredComponentManagerTest { @Test public void stateGetterReturnsUnknowByDefault() throws NameNotFoundException { TestFlutterJNI jni = new TestFlutterJNI(); - Context spyContext = spy(RuntimeEnvironment.application); - doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt()); + Context spyContext = createSpyContext(null); + doReturn(null).when(spyContext).getAssets(); TestPlayStoreDeferredComponentManager playStoreManager = new TestPlayStoreDeferredComponentManager(spyContext, jni); assertEquals(playStoreManager.getDeferredComponentInstallState(-1, "invalidName"), "unknown"); @@ -232,7 +266,9 @@ public class PlayStoreDeferredComponentManagerTest { @Test public void loadingUnitMappingFindsMatch() throws NameNotFoundException { TestFlutterJNI jni = new TestFlutterJNI(); - Context spyContext = spy(RuntimeEnvironment.application); + Bundle bundle = new Bundle(); + bundle.putString(PlayStoreDeferredComponentManager.MAPPING_KEY, "2:module1,5:module2"); + Context spyContext = createSpyContext(bundle); TestPlayStoreDeferredComponentManager playStoreManager = new TestPlayStoreDeferredComponentManager(spyContext, jni);