From cda2c3b8143225f3aebf529e69b1cd1f73161913 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Fri, 6 Nov 2020 11:42:52 -0800 Subject: [PATCH 1/8] Revert "Revert "support uri intent launcher in android (#21275)" (#22298)" This reverts commit f61cbc05f01ffb5dbda6a9c20eb0feda524774ab. --- .../embedding/android/FlutterActivity.java | 11 +++-- .../FlutterActivityAndFragmentDelegate.java | 26 +++++++++-- .../android/FlutterFragmentActivity.java | 11 +++-- ...lutterActivityAndFragmentDelegateTest.java | 46 +++++++++++++++++++ 4 files changed, 83 insertions(+), 11 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 66c2acd8e886b..1a6d48f160dde 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -774,13 +774,18 @@ public String getDartEntrypointFunctionName() { * * If both preferences are set, the {@code Intent} preference takes priority. * + *

If none is set, the {@link FlutterActivityAndFragmentDelegate} retrieves the initial route + * from the {@code Intent} through the Intent.getData() instead. + * *

The reason that a {@code } preference is supported is because this {@code * Activity} might be the very first {@code Activity} launched, which means the developer won't * have control over the incoming {@code Intent}. * *

Subclasses may override this method to directly control the initial route. + * + *

If this method returns null, the {@link FlutterActivityAndFragmentDelegate} retrieves the + * initial route from the {@code Intent} through the Intent.getData() instead. */ - @NonNull public String getInitialRoute() { if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) { return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE); @@ -792,9 +797,9 @@ public String getInitialRoute() { Bundle metadata = activityInfo.metaData; String desiredInitialRoute = metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null; - return desiredInitialRoute != null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE; + return desiredInitialRoute; } catch (PackageManager.NameNotFoundException e) { - return DEFAULT_INITIAL_ROUTE; + return null; } } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 78585172dd683..1ecbd0fafc908 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -9,6 +9,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; @@ -362,18 +363,21 @@ private void doInitialFlutterViewRun() { // So this is expected behavior in many cases. return; } - + String initialRoute = host.getInitialRoute(); + if (initialRoute == null) { + initialRoute = getInitialRouteFromIntent(host.getActivity().getIntent()); + } Log.v( TAG, "Executing Dart entrypoint: " + host.getDartEntrypointFunctionName() + ", and sending initial route: " - + host.getInitialRoute()); + + initialRoute); // The engine needs to receive the Flutter app's initial route before executing any // Dart code to ensure that the initial route arrives in time to be applied. - if (host.getInitialRoute() != null) { - flutterEngine.getNavigationChannel().setInitialRoute(host.getInitialRoute()); + if (initialRoute != null) { + flutterEngine.getNavigationChannel().setInitialRoute(initialRoute); } String appBundlePathOverride = host.getAppBundlePath(); @@ -388,6 +392,14 @@ private void doInitialFlutterViewRun() { flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); } + private String getInitialRouteFromIntent(Intent intent) { + Uri data = intent.getData(); + if (data != null && !data.toString().isEmpty()) { + return data.toString(); + } + return null; + } + /** * Invoke this from {@code Activity#onResume()} or {@code Fragment#onResume()}. * @@ -622,8 +634,12 @@ void onRequestPermissionsResult( void onNewIntent(@NonNull Intent intent) { ensureAlive(); if (flutterEngine != null) { - Log.v(TAG, "Forwarding onNewIntent() to FlutterEngine."); + Log.v(TAG, "Forwarding onNewIntent() to FlutterEngine and sending pushRoute message."); flutterEngine.getActivityControlSurface().onNewIntent(intent); + String initialRoute = getInitialRouteFromIntent(intent); + if (initialRoute != null && !initialRoute.isEmpty()) { + flutterEngine.getNavigationChannel().pushRoute(initialRoute); + } } else { Log.w(TAG, "onNewIntent() invoked before FlutterFragment was attached to an Activity."); } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index ff15581a57b22..486d36925d8b1 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -646,13 +646,18 @@ public String getDartEntrypointFunctionName() { * * If both preferences are set, the {@code Intent} preference takes priority. * + *

If none is set, the {@link FlutterActivityAndFragmentDelegate} retrieves the initial route + * from the {@code Intent} through the Intent.getData() instead. + * *

The reason that a {@code } preference is supported is because this {@code * Activity} might be the very first {@code Activity} launched, which means the developer won't * have control over the incoming {@code Intent}. * *

Subclasses may override this method to directly control the initial route. + * + *

If this method returns null, the {@link FlutterActivityAndFragmentDelegate} retrieves the + * initial route from the {@code Intent} through the Intent.getData() instead. */ - @NonNull protected String getInitialRoute() { if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) { return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE); @@ -664,9 +669,9 @@ protected String getInitialRoute() { Bundle metadata = activityInfo.metaData; String desiredInitialRoute = metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null; - return desiredInitialRoute != null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE; + return desiredInitialRoute; } catch (PackageManager.NameNotFoundException e) { - return DEFAULT_INITIAL_ROUTE; + return null; } } diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index 4a621b3344107..329f730175627 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -15,6 +15,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.net.Uri; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import io.flutter.FlutterInjector; @@ -43,6 +44,7 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; @Config(manifest = Config.NONE) @@ -426,6 +428,50 @@ public void itForwardsOnRequestPermissionsResultToFlutterEngine() { .onRequestPermissionsResult(any(Integer.class), any(String[].class), any(int[].class)); } + @Test + public void itSendsInitialRouteFromIntentOnStartIfnoInitialRouteFromActivity() { + Intent intent = FlutterActivity.createDefaultIntent(RuntimeEnvironment.application); + intent.setData(Uri.parse("http://myApp/custom/route")); + + ActivityController activityController = + Robolectric.buildActivity(FlutterActivity.class, intent); + FlutterActivity flutterActivity = activityController.get(); + + when(mockHost.getActivity()).thenReturn(flutterActivity); + when(mockHost.getInitialRoute()).thenReturn(null); + // Create the real object that we're testing. + FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); + + // --- Execute the behavior under test --- + // The FlutterEngine is setup in onAttach(). + delegate.onAttach(RuntimeEnvironment.application); + // Emulate app start. + delegate.onStart(); + + // Verify that the navigation channel was given the initial route message. + verify(mockFlutterEngine.getNavigationChannel(), times(1)) + .setInitialRoute("http://myApp/custom/route"); + } + + @Test + public void itSendsPushRouteMessageWhenOnNewIntent() { + // Create the real object that we're testing. + FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); + + // --- Execute the behavior under test --- + // The FlutterEngine is setup in onAttach(). + delegate.onAttach(RuntimeEnvironment.application); + + Intent mockIntent = mock(Intent.class); + when(mockIntent.getData()).thenReturn(Uri.parse("http://myApp/custom/route")); + // Emulate the host and call the method that we expect to be forwarded. + delegate.onNewIntent(mockIntent); + + // Verify that the navigation channel was given the push route message. + verify(mockFlutterEngine.getNavigationChannel(), times(1)) + .pushRoute("http://myApp/custom/route"); + } + @Test public void itForwardsOnNewIntentToFlutterEngine() { // Create the real object that we're testing. From 5fe12e9ef04fa02d6412eca5e37fff6af5f978ac Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Fri, 6 Nov 2020 13:15:44 -0800 Subject: [PATCH 2/8] reland support uri launch for android --- .../FlutterActivityAndFragmentDelegate.java | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 1ecbd0fafc908..e6d40368e7691 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -9,6 +9,9 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -67,6 +70,7 @@ private static final String TAG = "FlutterActivityAndFragmentDelegate"; private static final String FRAMEWORK_RESTORATION_BUNDLE_KEY = "framework"; private static final String PLUGINS_RESTORATION_BUNDLE_KEY = "plugins"; + private static final String HANDLE_DEEPLINKING_BUNDLE_KEY = "flutter_handle_deeplinking"; // The FlutterActivity or FlutterFragment that is delegating most of its calls // to this FlutterActivityAndFragmentDelegate. @@ -365,7 +369,7 @@ private void doInitialFlutterViewRun() { } String initialRoute = host.getInitialRoute(); if (initialRoute == null) { - initialRoute = getInitialRouteFromIntent(host.getActivity().getIntent()); + initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent()); } Log.v( TAG, @@ -392,12 +396,26 @@ private void doInitialFlutterViewRun() { flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); } - private String getInitialRouteFromIntent(Intent intent) { - Uri data = intent.getData(); - if (data != null && !data.toString().isEmpty()) { - return data.toString(); + private String maybeGetInitialRouteFromIntent(Intent intent) { + try { + Activity activity = host.getActivity(); + ActivityInfo activityInfo = + activity + .getPackageManager() + .getActivityInfo( + activity.getComponentName(), + PackageManager.GET_META_DATA | PackageManager.GET_ACTIVITIES); + Bundle metadata = activityInfo.metaData; + if (metadata != null && metadata.getBoolean(HANDLE_DEEPLINKING_BUNDLE_KEY)) { + Uri data = intent.getData(); + if (data != null && !data.toString().isEmpty()) { + return data.toString(); + } + } + return null; + } catch (NameNotFoundException e) { + return null; } - return null; } /** @@ -636,7 +654,7 @@ void onNewIntent(@NonNull Intent intent) { if (flutterEngine != null) { Log.v(TAG, "Forwarding onNewIntent() to FlutterEngine and sending pushRoute message."); flutterEngine.getActivityControlSurface().onNewIntent(intent); - String initialRoute = getInitialRouteFromIntent(intent); + String initialRoute = maybeGetInitialRouteFromIntent(intent); if (initialRoute != null && !initialRoute.isEmpty()) { flutterEngine.getNavigationChannel().pushRoute(initialRoute); } From bce3134795668f889fd27039011f7930fd92d72f Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Fri, 6 Nov 2020 15:34:18 -0800 Subject: [PATCH 3/8] refactor --- .../embedding/android/FlutterActivity.java | 24 ++++++++++--- .../FlutterActivityAndFragmentDelegate.java | 34 +++++++------------ .../android/FlutterActivityLaunchConfigs.java | 2 +- .../embedding/android/FlutterFragment.java | 31 +++++++++++++++++ .../android/FlutterFragmentActivity.java | 25 +++++++++++--- ...lutterActivityAndFragmentDelegateTest.java | 5 ++- .../android/FlutterAndroidComponentTest.java | 5 +++ .../android/FlutterFragmentTest.java | 3 ++ 8 files changed, 96 insertions(+), 33 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 1a6d48f160dde..40cf4d7a2b75a 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -13,6 +13,7 @@ import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_ENABLE_STATE_RESTORATION; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_INITIAL_ROUTE; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.SPLASH_SCREEN_META_DATA_KEY; @@ -774,17 +775,16 @@ public String getDartEntrypointFunctionName() { * * If both preferences are set, the {@code Intent} preference takes priority. * - *

If none is set, the {@link FlutterActivityAndFragmentDelegate} retrieves the initial route - * from the {@code Intent} through the Intent.getData() instead. - * *

The reason that a {@code } preference is supported is because this {@code * Activity} might be the very first {@code Activity} launched, which means the developer won't * have control over the incoming {@code Intent}. * *

Subclasses may override this method to directly control the initial route. * - *

If this method returns null, the {@link FlutterActivityAndFragmentDelegate} retrieves the - * initial route from the {@code Intent} through the Intent.getData() instead. + *

If this method returns null and the {@code } has {@link + * FlutterActivityAndFragmentDelegate#HANDLE_DEEPLINKING_BUNDLE_KEY} set to true, the {@link + * FlutterActivityAndFragmentDelegate} retrieves the initial route from the {@code Intent} through + * the Intent.getData() instead. */ public String getInitialRoute() { if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) { @@ -975,6 +975,20 @@ public boolean shouldAttachEngineToActivity() { return true; } + @Override + public boolean shouldHandleDeeplinking() { + try { + ActivityInfo activityInfo = + getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + Bundle metadata = activityInfo.metaData; + boolean shouldHandleDeeplinking = + metadata != null ? metadata.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false; + return shouldHandleDeeplinking; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + @Override public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) { // Hook for subclasses. diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index e6d40368e7691..59b8048467d6c 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -9,9 +9,6 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -70,7 +67,6 @@ private static final String TAG = "FlutterActivityAndFragmentDelegate"; private static final String FRAMEWORK_RESTORATION_BUNDLE_KEY = "framework"; private static final String PLUGINS_RESTORATION_BUNDLE_KEY = "plugins"; - private static final String HANDLE_DEEPLINKING_BUNDLE_KEY = "flutter_handle_deeplinking"; // The FlutterActivity or FlutterFragment that is delegating most of its calls // to this FlutterActivityAndFragmentDelegate. @@ -397,25 +393,13 @@ private void doInitialFlutterViewRun() { } private String maybeGetInitialRouteFromIntent(Intent intent) { - try { - Activity activity = host.getActivity(); - ActivityInfo activityInfo = - activity - .getPackageManager() - .getActivityInfo( - activity.getComponentName(), - PackageManager.GET_META_DATA | PackageManager.GET_ACTIVITIES); - Bundle metadata = activityInfo.metaData; - if (metadata != null && metadata.getBoolean(HANDLE_DEEPLINKING_BUNDLE_KEY)) { - Uri data = intent.getData(); - if (data != null && !data.toString().isEmpty()) { - return data.toString(); - } + if (host.shouldHandleDeeplinking()) { + Uri data = intent.getData(); + if (data != null && !data.toString().isEmpty()) { + return data.toString(); } - return null; - } catch (NameNotFoundException e) { - return null; } + return null; } /** @@ -769,6 +753,14 @@ private void ensureAlive() { @NonNull Context getContext(); + /** + * Returns true if the {@link FlutterActivityAndFragmentDelegate} should send the deeplinking + * URL to the framework through the {@code NavigationChannel.setInitialRoute} or {@code + * NavigationChannel.pushRoute}. + */ + @Nullable + boolean shouldHandleDeeplinking(); + /** * Returns the host {@link Activity} or the {@code Activity} that is currently attached to the * host {@code Fragment}. diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java index ae7a4a48e67a8..617ef056d233d 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java @@ -16,7 +16,7 @@ public class FlutterActivityLaunchConfigs { "io.flutter.embedding.android.SplashScreenDrawable"; /* package */ static final String NORMAL_THEME_META_DATA_KEY = "io.flutter.embedding.android.NormalTheme"; - + /* package */ static final String HANDLE_DEEPLINKING_META_DATA_KEY = "flutter_handle_deeplinking"; // Intent extra arguments. /* package */ static final String EXTRA_INITIAL_ROUTE = "route"; /* package */ static final String EXTRA_BACKGROUND_MODE = "background_mode"; diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index 4d2438bd95935..1824129098d20 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -88,6 +88,8 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm protected static final String ARG_DART_ENTRYPOINT = "dart_entrypoint"; /** Initial Flutter route that is rendered in a Navigator widget. */ protected static final String ARG_INITIAL_ROUTE = "initial_route"; + /** Whether the activity delegate should handle the deeplinking request. */ + protected static final String ARG_HANDLE_DEEPLINKING = "handle_deeplinking"; /** Path to Flutter's Dart code. */ protected static final String ARG_APP_BUNDLE_PATH = "app_bundle_path"; /** Flutter shell arguments. */ @@ -185,6 +187,7 @@ public static class NewEngineFragmentBuilder { private final Class fragmentClass; private String dartEntrypoint = "main"; private String initialRoute = "/"; + private boolean handleDeeplinking = false; private String appBundlePath = null; private FlutterShellArgs shellArgs = null; private RenderMode renderMode = RenderMode.surface; @@ -224,6 +227,13 @@ public NewEngineFragmentBuilder initialRoute(@NonNull String initialRoute) { return this; } + /** Whether the activity delegate should handle the deeplinking request. */ + @NonNull + public NewEngineFragmentBuilder handleDeeplinking(@NonNull Boolean handleDeeplinking) { + this.handleDeeplinking = handleDeeplinking; + return this; + } + /** * The path to the app bundle which contains the Dart app to execute. Null when unspecified, * which defaults to {@link FlutterLoader#findAppBundlePath()} @@ -316,6 +326,7 @@ public NewEngineFragmentBuilder shouldAttachEngineToActivity( protected Bundle createArgs() { Bundle args = new Bundle(); args.putString(ARG_INITIAL_ROUTE, initialRoute); + args.putBoolean(ARG_HANDLE_DEEPLINKING, handleDeeplinking); args.putString(ARG_APP_BUNDLE_PATH, appBundlePath); args.putString(ARG_DART_ENTRYPOINT, dartEntrypoint); // TODO(mattcarroll): determine if we should have an explicit FlutterTestFragment instead of @@ -409,6 +420,7 @@ public static class CachedEngineFragmentBuilder { private final Class fragmentClass; private final String engineId; private boolean destroyEngineWithFragment = false; + private boolean handleDeeplinking = false; private RenderMode renderMode = RenderMode.surface; private TransparencyMode transparencyMode = TransparencyMode.transparent; private boolean shouldAttachEngineToActivity = true; @@ -460,6 +472,13 @@ public CachedEngineFragmentBuilder transparencyMode( return this; } + /** Whether the activity delegate should handle the deeplinking request. */ + @NonNull + public CachedEngineFragmentBuilder handleDeeplinking(@NonNull Boolean handleDeeplinking) { + this.handleDeeplinking = handleDeeplinking; + return this; + } + /** * Whether or not this {@code FlutterFragment} should automatically attach its {@code Activity} * as a control surface for its {@link FlutterEngine}. @@ -512,6 +531,7 @@ protected Bundle createArgs() { Bundle args = new Bundle(); args.putString(ARG_CACHED_ENGINE_ID, engineId); args.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, destroyEngineWithFragment); + args.putBoolean(ARG_HANDLE_DEEPLINKING, handleDeeplinking); args.putString( ARG_FLUTTERVIEW_RENDER_MODE, renderMode != null ? renderMode.name() : RenderMode.surface.name()); @@ -1016,6 +1036,17 @@ public boolean shouldAttachEngineToActivity() { return getArguments().getBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY); } + /** + * See {@link NewEngineFragmentBuilder#shouldHandleDeeplinking()} and {@link + * CachedEngineFragmentBuilder#shouldHandleDeeplinking()}. + * + *

Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate} + */ + @Override + public boolean shouldHandleDeeplinking() { + return getArguments().getBoolean(ARG_HANDLE_DEEPLINKING); + } + @Override public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) { // Hook for subclasses. diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index 486d36925d8b1..5c80af2cdcb8a 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -12,6 +12,7 @@ import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_INITIAL_ROUTE; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.SPLASH_SCREEN_META_DATA_KEY; @@ -423,6 +424,7 @@ protected FlutterFragment createFlutterFragment() { return FlutterFragment.withCachedEngine(getCachedEngineId()) .renderMode(renderMode) .transparencyMode(transparencyMode) + .handleDeeplinking(shouldHandleDeeplinking()) .shouldAttachEngineToActivity(shouldAttachEngineToActivity()) .destroyEngineWithFragment(shouldDestroyEngineWithHost()) .build(); @@ -450,6 +452,7 @@ protected FlutterFragment createFlutterFragment() { .initialRoute(getInitialRoute()) .appBundlePath(getAppBundlePath()) .flutterShellArgs(FlutterShellArgs.fromIntent(getIntent())) + .handleDeeplinking(shouldHandleDeeplinking()) .renderMode(renderMode) .transparencyMode(transparencyMode) .shouldAttachEngineToActivity(shouldAttachEngineToActivity()) @@ -545,6 +548,19 @@ protected boolean shouldAttachEngineToActivity() { return true; } + public boolean shouldHandleDeeplinking() { + try { + ActivityInfo activityInfo = + getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + Bundle metadata = activityInfo.metaData; + boolean shouldHandleDeeplinking = + metadata != null ? metadata.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false; + return shouldHandleDeeplinking; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + /** Hook for subclasses to easily provide a custom {@code FlutterEngine}. */ @Nullable @Override @@ -646,17 +662,16 @@ public String getDartEntrypointFunctionName() { * * If both preferences are set, the {@code Intent} preference takes priority. * - *

If none is set, the {@link FlutterActivityAndFragmentDelegate} retrieves the initial route - * from the {@code Intent} through the Intent.getData() instead. - * *

The reason that a {@code } preference is supported is because this {@code * Activity} might be the very first {@code Activity} launched, which means the developer won't * have control over the incoming {@code Intent}. * *

Subclasses may override this method to directly control the initial route. * - *

If this method returns null, the {@link FlutterActivityAndFragmentDelegate} retrieves the - * initial route from the {@code Intent} through the Intent.getData() instead. + *

If this method returns null and the {@code } has {@link + * FlutterActivityAndFragmentDelegate#HANDLE_DEEPLINKING_BUNDLE_KEY} set to true, the {@link + * FlutterActivityAndFragmentDelegate} retrieves the initial route from the {@code Intent} through + * the Intent.getData() instead. */ protected String getInitialRoute() { if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) { diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index 329f730175627..a44d196e4102f 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -73,6 +73,7 @@ public void setup() { when(mockHost.getTransparencyMode()).thenReturn(TransparencyMode.transparent); when(mockHost.provideFlutterEngine(any(Context.class))).thenReturn(mockFlutterEngine); when(mockHost.shouldAttachEngineToActivity()).thenReturn(true); + when(mockHost.shouldHandleDeeplinking()).thenReturn(false); when(mockHost.shouldDestroyEngineWithHost()).thenReturn(true); } @@ -429,7 +430,7 @@ public void itForwardsOnRequestPermissionsResultToFlutterEngine() { } @Test - public void itSendsInitialRouteFromIntentOnStartIfnoInitialRouteFromActivity() { + public void itSendsInitialRouteFromIntentOnStartIfNoInitialRouteFromActivity() { Intent intent = FlutterActivity.createDefaultIntent(RuntimeEnvironment.application); intent.setData(Uri.parse("http://myApp/custom/route")); @@ -439,6 +440,7 @@ public void itSendsInitialRouteFromIntentOnStartIfnoInitialRouteFromActivity() { when(mockHost.getActivity()).thenReturn(flutterActivity); when(mockHost.getInitialRoute()).thenReturn(null); + when(mockHost.shouldHandleDeeplinking()).thenReturn(true); // Create the real object that we're testing. FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); @@ -455,6 +457,7 @@ public void itSendsInitialRouteFromIntentOnStartIfnoInitialRouteFromActivity() { @Test public void itSendsPushRouteMessageWhenOnNewIntent() { + when(mockHost.shouldHandleDeeplinking()).thenReturn(true); // Create the real object that we're testing. FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java index 5cede33a1b833..51532b9c96143 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java @@ -358,6 +358,11 @@ public boolean shouldAttachEngineToActivity() { return true; } + @Override + public boolean shouldHandleDeeplinking() { + return false; + } + @Override public boolean shouldRestoreAndSaveState() { return true; diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java index 9327994e8d9e6..332c48770c9f4 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java @@ -27,6 +27,7 @@ public void itCreatesDefaultFragmentWithExpectedDefaults() { assertEquals("/", fragment.getInitialRoute()); assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray()); assertTrue(fragment.shouldAttachEngineToActivity()); + assertFalse(fragment.shouldHandleDeeplinking()); assertNull(fragment.getCachedEngineId()); assertTrue(fragment.shouldDestroyEngineWithHost()); assertEquals(RenderMode.surface, fragment.getRenderMode()); @@ -40,6 +41,7 @@ public void itCreatesNewEngineFragmentWithRequestedSettings() { .dartEntrypoint("custom_entrypoint") .initialRoute("/custom/route") .shouldAttachEngineToActivity(false) + .handleDeeplinking(true) .renderMode(RenderMode.texture) .transparencyMode(TransparencyMode.opaque) .build(); @@ -49,6 +51,7 @@ public void itCreatesNewEngineFragmentWithRequestedSettings() { assertEquals("/custom/route", fragment.getInitialRoute()); assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray()); assertFalse(fragment.shouldAttachEngineToActivity()); + assertTrue(fragment.shouldAttachEngineToActivity()); assertNull(fragment.getCachedEngineId()); assertTrue(fragment.shouldDestroyEngineWithHost()); assertEquals(RenderMode.texture, fragment.getRenderMode()); From 6b09ef544f58baa468caa81f89e5ec318af67d4f Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Fri, 6 Nov 2020 15:46:52 -0800 Subject: [PATCH 4/8] update --- .../io/flutter/embedding/android/FlutterActivity.java | 7 +++---- .../flutter/embedding/android/FlutterFragmentActivity.java | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 40cf4d7a2b75a..b95bf6e96031d 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -781,10 +781,9 @@ public String getDartEntrypointFunctionName() { * *

Subclasses may override this method to directly control the initial route. * - *

If this method returns null and the {@code } has {@link - * FlutterActivityAndFragmentDelegate#HANDLE_DEEPLINKING_BUNDLE_KEY} set to true, the {@link - * FlutterActivityAndFragmentDelegate} retrieves the initial route from the {@code Intent} through - * the Intent.getData() instead. + *

If this method returns null and the {@code shouldHandleDeeplinking()} returns true, the + * {@link FlutterActivityAndFragmentDelegate} retrieves the initial route from the {@code Intent} + * through the Intent.getData() instead. */ public String getInitialRoute() { if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) { diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index 5c80af2cdcb8a..c76be5e267797 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -668,10 +668,9 @@ public String getDartEntrypointFunctionName() { * *

Subclasses may override this method to directly control the initial route. * - *

If this method returns null and the {@code } has {@link - * FlutterActivityAndFragmentDelegate#HANDLE_DEEPLINKING_BUNDLE_KEY} set to true, the {@link - * FlutterActivityAndFragmentDelegate} retrieves the initial route from the {@code Intent} through - * the Intent.getData() instead. + *

If this method returns null and the {@code shouldHandleDeeplinking()} returns true, the + * {@link FlutterActivityAndFragmentDelegate} retrieves the initial route from the {@code Intent} + * through the Intent.getData() instead. */ protected String getInitialRoute() { if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) { From fe25a89817a0dabb56869e401c6bcab00b1c1f2b Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Fri, 6 Nov 2020 16:17:58 -0800 Subject: [PATCH 5/8] fix test --- .../flutter/embedding/android/FlutterFragmentActivity.java | 2 +- .../embedding/android/FlutterFragmentActivityTest.java | 5 +++++ .../io/flutter/embedding/android/FlutterFragmentTest.java | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index c76be5e267797..9cb1b32337282 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -548,7 +548,7 @@ protected boolean shouldAttachEngineToActivity() { return true; } - public boolean shouldHandleDeeplinking() { + protected boolean shouldHandleDeeplinking() { try { ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java index cd311a835588e..a1f044c014784 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java @@ -119,6 +119,11 @@ protected String getInitialRoute() { protected String getAppBundlePath() { return ""; } + + @Override + protected boolean shouldHandleDeeplinking() { + return false; + } } // This is just a compile time check to ensure that it's possible for FlutterFragmentActivity diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java index 332c48770c9f4..ecaafe02f7e9a 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java @@ -51,7 +51,7 @@ public void itCreatesNewEngineFragmentWithRequestedSettings() { assertEquals("/custom/route", fragment.getInitialRoute()); assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray()); assertFalse(fragment.shouldAttachEngineToActivity()); - assertTrue(fragment.shouldAttachEngineToActivity()); + assertTrue(fragment.shouldHandleDeeplinking()); assertNull(fragment.getCachedEngineId()); assertTrue(fragment.shouldDestroyEngineWithHost()); assertEquals(RenderMode.texture, fragment.getRenderMode()); From bc1059c6c6ea6ff904e5025f91a0ebc1fbd4b376 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Tue, 10 Nov 2020 10:02:25 -0800 Subject: [PATCH 6/8] addressing comments --- .../embedding/android/FlutterActivity.java | 54 +++++++++++------- .../FlutterActivityAndFragmentDelegate.java | 14 ++--- .../android/FlutterActivityLaunchConfigs.java | 3 +- .../embedding/android/FlutterFragment.java | 16 ++++-- .../android/FlutterFragmentActivity.java | 56 ++++++++++++------- ...lutterActivityAndFragmentDelegateTest.java | 28 +++++++++- .../android/FlutterActivityTest.java | 47 ++++++++++++++++ .../android/FlutterFragmentActivityTest.java | 37 ++++++++++++ 8 files changed, 201 insertions(+), 54 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index b95bf6e96031d..2664b61212105 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -447,10 +447,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { */ private void switchLaunchThemeForNormalTheme() { try { - ActivityInfo activityInfo = - getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - if (activityInfo.metaData != null) { - int normalThemeRID = activityInfo.metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1); + Bundle metaData = getMetaData(); + if (metaData != null) { + int normalThemeRID = metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1); if (normalThemeRID != -1) { setTheme(normalThemeRID); } @@ -486,9 +485,7 @@ public SplashScreen provideSplashScreen() { @SuppressWarnings("deprecation") private Drawable getSplashScreenFromManifest() { try { - ActivityInfo activityInfo = - getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - Bundle metadata = activityInfo.metaData; + Bundle metadata = getMetaData(); int splashScreenId = metadata != null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : 0; return splashScreenId != 0 ? Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP @@ -749,9 +746,7 @@ public boolean shouldDestroyEngineWithHost() { @NonNull public String getDartEntrypointFunctionName() { try { - ActivityInfo activityInfo = - getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - Bundle metadata = activityInfo.metaData; + Bundle metadata = getMetaData(); String desiredDartEntrypoint = metadata != null ? metadata.getString(DART_ENTRYPOINT_META_DATA_KEY) : null; return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT; @@ -781,9 +776,8 @@ public String getDartEntrypointFunctionName() { * *

Subclasses may override this method to directly control the initial route. * - *

If this method returns null and the {@code shouldHandleDeeplinking()} returns true, the - * {@link FlutterActivityAndFragmentDelegate} retrieves the initial route from the {@code Intent} - * through the Intent.getData() instead. + *

If this method returns null and the {@code shouldHandleDeeplinking} returns true, the + * initial route is derived from the {@code Intent} through the Intent.getData() instead. */ public String getInitialRoute() { if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) { @@ -791,9 +785,7 @@ public String getInitialRoute() { } try { - ActivityInfo activityInfo = - getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - Bundle metadata = activityInfo.metaData; + Bundle metadata = getMetaData(); String desiredInitialRoute = metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null; return desiredInitialRoute; @@ -898,6 +890,25 @@ protected FlutterEngine getFlutterEngine() { return delegate.getFlutterEngine(); } + private Bundle cachedMetaData; + + /** Mocks the meta data for testing purposes. */ + @VisibleForTesting + public void setMetaData(Bundle metaData) { + cachedMetaData = metaData; + }; + + /** Retrieves the meta data specified in the AndroidManifest.xml. */ + @Nullable + protected Bundle getMetaData() throws PackageManager.NameNotFoundException { + if (cachedMetaData == null) { + ActivityInfo activityInfo = + getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + cachedMetaData = activityInfo.metaData; + } + return cachedMetaData; + } + @Nullable @Override public PlatformPlugin providePlatformPlugin( @@ -974,12 +985,17 @@ public boolean shouldAttachEngineToActivity() { return true; } + /** + * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code + * getInitialRoute} returns null. + * + *

The default implementation looks for the value of the key `flutter_handle_deeplinking` in + * the AndroidManifest.xml. + */ @Override public boolean shouldHandleDeeplinking() { try { - ActivityInfo activityInfo = - getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - Bundle metadata = activityInfo.metaData; + Bundle metadata = getMetaData(); boolean shouldHandleDeeplinking = metadata != null ? metadata.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false; return shouldHandleDeeplinking; diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 59b8048467d6c..f48b7e539f4b0 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -5,6 +5,7 @@ package io.flutter.embedding.android; import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE; import android.app.Activity; import android.content.Context; @@ -366,6 +367,9 @@ private void doInitialFlutterViewRun() { String initialRoute = host.getInitialRoute(); if (initialRoute == null) { initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent()); + if (initialRoute == null) { + initialRoute = DEFAULT_INITIAL_ROUTE; + } } Log.v( TAG, @@ -376,9 +380,7 @@ private void doInitialFlutterViewRun() { // The engine needs to receive the Flutter app's initial route before executing any // Dart code to ensure that the initial route arrives in time to be applied. - if (initialRoute != null) { - flutterEngine.getNavigationChannel().setInitialRoute(initialRoute); - } + flutterEngine.getNavigationChannel().setInitialRoute(initialRoute); String appBundlePathOverride = host.getAppBundlePath(); if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) { @@ -753,11 +755,7 @@ private void ensureAlive() { @NonNull Context getContext(); - /** - * Returns true if the {@link FlutterActivityAndFragmentDelegate} should send the deeplinking - * URL to the framework through the {@code NavigationChannel.setInitialRoute} or {@code - * NavigationChannel.pushRoute}. - */ + /** Returns true if the delegate should retrieve the initial route from the {@link Intent}. */ @Nullable boolean shouldHandleDeeplinking(); diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java index 617ef056d233d..a92e342333765 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java @@ -16,7 +16,8 @@ public class FlutterActivityLaunchConfigs { "io.flutter.embedding.android.SplashScreenDrawable"; /* package */ static final String NORMAL_THEME_META_DATA_KEY = "io.flutter.embedding.android.NormalTheme"; - /* package */ static final String HANDLE_DEEPLINKING_META_DATA_KEY = "flutter_handle_deeplinking"; + /* package */ static final String HANDLE_DEEPLINKING_META_DATA_KEY = + "flutter_deeplinking_enabled"; // Intent extra arguments. /* package */ static final String EXTRA_INITIAL_ROUTE = "route"; /* package */ static final String EXTRA_BACKGROUND_MODE = "background_mode"; diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index 1824129098d20..f47dd8f0f280f 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -227,7 +227,10 @@ public NewEngineFragmentBuilder initialRoute(@NonNull String initialRoute) { return this; } - /** Whether the activity delegate should handle the deeplinking request. */ + /** + * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code + * getInitialRoute} returns null. + */ @NonNull public NewEngineFragmentBuilder handleDeeplinking(@NonNull Boolean handleDeeplinking) { this.handleDeeplinking = handleDeeplinking; @@ -472,7 +475,10 @@ public CachedEngineFragmentBuilder transparencyMode( return this; } - /** Whether the activity delegate should handle the deeplinking request. */ + /** + * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code + * getInitialRoute} returns null. + */ @NonNull public CachedEngineFragmentBuilder handleDeeplinking(@NonNull Boolean handleDeeplinking) { this.handleDeeplinking = handleDeeplinking; @@ -1037,10 +1043,8 @@ public boolean shouldAttachEngineToActivity() { } /** - * See {@link NewEngineFragmentBuilder#shouldHandleDeeplinking()} and {@link - * CachedEngineFragmentBuilder#shouldHandleDeeplinking()}. - * - *

Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate} + * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code + * getInitialRoute} returns null. */ @Override public boolean shouldHandleDeeplinking() { diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index 9cb1b32337282..00bca2bc3757d 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -34,6 +34,7 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import io.flutter.Log; @@ -280,10 +281,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { */ private void switchLaunchThemeForNormalTheme() { try { - ActivityInfo activityInfo = - getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - if (activityInfo.metaData != null) { - int normalThemeRID = activityInfo.metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1); + Bundle metaData = getMetaData(); + if (metaData != null) { + int normalThemeRID = metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1); if (normalThemeRID != -1) { setTheme(normalThemeRID); } @@ -319,9 +319,7 @@ public SplashScreen provideSplashScreen() { @SuppressWarnings("deprecation") private Drawable getSplashScreenFromManifest() { try { - ActivityInfo activityInfo = - getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - Bundle metadata = activityInfo.metaData; + Bundle metadata = getMetaData(); Integer splashScreenId = metadata != null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : null; return splashScreenId != null @@ -548,11 +546,17 @@ protected boolean shouldAttachEngineToActivity() { return true; } + /** + * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code + * getInitialRoute} returns null. + * + *

The default implementation looks for the value of the key `flutter_handle_deeplinking` in + * the AndroidManifest.xml. + */ + @VisibleForTesting protected boolean shouldHandleDeeplinking() { try { - ActivityInfo activityInfo = - getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - Bundle metadata = activityInfo.metaData; + Bundle metadata = getMetaData(); boolean shouldHandleDeeplinking = metadata != null ? metadata.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false; return shouldHandleDeeplinking; @@ -624,6 +628,25 @@ protected String getAppBundlePath() { return null; } + private Bundle cachedMetaData; + + /** Mocks the meta data for testing purposes. */ + @VisibleForTesting + public void setMetaData(Bundle metaData) { + cachedMetaData = metaData; + }; + + /** Retrieves the meta data specified in the AndroidManifest.xml. */ + @Nullable + protected Bundle getMetaData() throws PackageManager.NameNotFoundException { + if (cachedMetaData == null) { + ActivityInfo activityInfo = + getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + cachedMetaData = activityInfo.metaData; + } + return cachedMetaData; + } + /** * The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded. * @@ -636,9 +659,7 @@ protected String getAppBundlePath() { @NonNull public String getDartEntrypointFunctionName() { try { - ActivityInfo activityInfo = - getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - Bundle metadata = activityInfo.metaData; + Bundle metadata = getMetaData(); String desiredDartEntrypoint = metadata != null ? metadata.getString(DART_ENTRYPOINT_META_DATA_KEY) : null; return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT; @@ -668,9 +689,8 @@ public String getDartEntrypointFunctionName() { * *

Subclasses may override this method to directly control the initial route. * - *

If this method returns null and the {@code shouldHandleDeeplinking()} returns true, the - * {@link FlutterActivityAndFragmentDelegate} retrieves the initial route from the {@code Intent} - * through the Intent.getData() instead. + *

If this method returns null and the {@code shouldHandleDeeplinking} returns true, the + * initial route is derived from the {@code Intent} through the Intent.getData() instead. */ protected String getInitialRoute() { if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) { @@ -678,9 +698,7 @@ protected String getInitialRoute() { } try { - ActivityInfo activityInfo = - getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - Bundle metadata = activityInfo.metaData; + Bundle metadata = getMetaData(); String desiredInitialRoute = metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null; return desiredInitialRoute; diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index a44d196e4102f..bf49fb6871c94 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -430,7 +430,8 @@ public void itForwardsOnRequestPermissionsResultToFlutterEngine() { } @Test - public void itSendsInitialRouteFromIntentOnStartIfNoInitialRouteFromActivity() { + public void + itSendsInitialRouteFromIntentOnStartIfNoInitialRouteFromActivityAndShouldHandleDeeplinking() { Intent intent = FlutterActivity.createDefaultIntent(RuntimeEnvironment.application); intent.setData(Uri.parse("http://myApp/custom/route")); @@ -455,6 +456,31 @@ public void itSendsInitialRouteFromIntentOnStartIfNoInitialRouteFromActivity() { .setInitialRoute("http://myApp/custom/route"); } + @Test + public void itSendsdefaultInitialRouteOnStartIfNotDeepLinkingFromIntent() { + // Creates an empty intent without launch uri. + Intent intent = FlutterActivity.createDefaultIntent(RuntimeEnvironment.application); + + ActivityController activityController = + Robolectric.buildActivity(FlutterActivity.class, intent); + FlutterActivity flutterActivity = activityController.get(); + + when(mockHost.getActivity()).thenReturn(flutterActivity); + when(mockHost.getInitialRoute()).thenReturn(null); + when(mockHost.shouldHandleDeeplinking()).thenReturn(true); + // Create the real object that we're testing. + FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); + + // --- Execute the behavior under test --- + // The FlutterEngine is setup in onAttach(). + delegate.onAttach(RuntimeEnvironment.application); + // Emulate app start. + delegate.onStart(); + + // Verify that the navigation channel was given the default initial route message. + verify(mockFlutterEngine.getNavigationChannel(), times(1)).setInitialRoute("/"); + } + @Test public void itSendsPushRouteMessageWhenOnNewIntent() { when(mockHost.shouldHandleDeeplinking()).thenReturn(true); diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java index 2fe23de49b303..b32052cdb0175 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java @@ -1,5 +1,6 @@ package io.flutter.embedding.android; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -116,6 +117,52 @@ public void itCreatesNewEngineIntentWithRequestedSettings() { assertEquals(TransparencyMode.transparent, flutterActivity.getTransparencyMode()); } + @Test + public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1() { + Intent intent = + FlutterActivity.withNewEngine() + .backgroundMode(BackgroundMode.transparent) + .build(RuntimeEnvironment.application); + ActivityController activityController = + Robolectric.buildActivity(FlutterActivity.class, intent); + FlutterActivity flutterActivity = activityController.get(); + Bundle bundle = new Bundle(); + bundle.putBoolean(HANDLE_DEEPLINKING_META_DATA_KEY, true); + flutterActivity.setMetaData(bundle); + assertTrue(flutterActivity.shouldHandleDeeplinking()); + } + + @Test + public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase2() { + Intent intent = + FlutterActivity.withNewEngine() + .backgroundMode(BackgroundMode.transparent) + .build(RuntimeEnvironment.application); + ActivityController activityController = + Robolectric.buildActivity(FlutterActivity.class, intent); + FlutterActivity flutterActivity = activityController.get(); + Bundle bundle = new Bundle(); + bundle.putBoolean(HANDLE_DEEPLINKING_META_DATA_KEY, false); + flutterActivity.setMetaData(bundle); + assertFalse(flutterActivity.shouldHandleDeeplinking()); + } + + @Test + public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase3() { + Intent intent = + FlutterActivity.withNewEngine() + .backgroundMode(BackgroundMode.transparent) + .build(RuntimeEnvironment.application); + ActivityController activityController = + Robolectric.buildActivity(FlutterActivity.class, intent); + FlutterActivity flutterActivity = activityController.get(); + // Creates an empty bundle. + Bundle bundle = new Bundle(); + flutterActivity.setMetaData(bundle); + // Empty bundle should return false. + assertFalse(flutterActivity.shouldHandleDeeplinking()); + } + @Test public void itCreatesCachedEngineIntentThatDoesNotDestroyTheEngine() { Intent intent = diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java index a1f044c014784..1127d7cca1c03 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java @@ -1,12 +1,15 @@ package io.flutter.embedding.android; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; +import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode; @@ -82,6 +85,40 @@ public void itRegistersPluginsAtConfigurationTime() { assertEquals(activity.getFlutterEngine(), registeredEngines.get(0)); } + @Test + public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1() { + FlutterFragmentActivity activity = + Robolectric.buildActivity(FlutterFragmentActivityWithProvidedEngine.class).get(); + assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty()); + Bundle bundle = new Bundle(); + bundle.putBoolean(HANDLE_DEEPLINKING_META_DATA_KEY, true); + activity.setMetaData(bundle); + assertTrue(activity.shouldHandleDeeplinking()); + } + + @Test + public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase2() { + FlutterFragmentActivity activity = + Robolectric.buildActivity(FlutterFragmentActivityWithProvidedEngine.class).get(); + assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty()); + Bundle bundle = new Bundle(); + bundle.putBoolean(HANDLE_DEEPLINKING_META_DATA_KEY, false); + activity.setMetaData(bundle); + assertFalse(activity.shouldHandleDeeplinking()); + } + + @Test + public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase3() { + FlutterFragmentActivity activity = + Robolectric.buildActivity(FlutterFragmentActivityWithProvidedEngine.class).get(); + assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty()); + // Creates an empty bundle. + Bundle bundle = new Bundle(); + activity.setMetaData(bundle); + // Empty bundle should return false. + assertFalse(activity.shouldHandleDeeplinking()); + } + static class FlutterFragmentActivityWithProvidedEngine extends FlutterFragmentActivity { @Override protected FlutterFragment createFlutterFragment() { From 33428d1c2dd8b615800d4e4e5bbef59e2a0f5361 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 11 Nov 2020 10:26:54 -0800 Subject: [PATCH 7/8] addressing comments --- .../embedding/android/FlutterActivity.java | 48 ++++++++----------- .../android/FlutterFragmentActivity.java | 48 ++++++++----------- .../android/FlutterActivityTest.java | 25 ++++++---- .../android/FlutterFragmentActivityTest.java | 25 ++++++---- 4 files changed, 74 insertions(+), 72 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 2664b61212105..60dfe27aa748c 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -456,7 +456,7 @@ private void switchLaunchThemeForNormalTheme() { } else { Log.v(TAG, "Using the launch theme as normal theme."); } - } catch (PackageManager.NameNotFoundException exception) { + } catch (RuntimeException exception) { Log.e( TAG, "Could not read meta-data for FlutterActivity. Using the launch theme as normal theme."); @@ -485,14 +485,14 @@ public SplashScreen provideSplashScreen() { @SuppressWarnings("deprecation") private Drawable getSplashScreenFromManifest() { try { - Bundle metadata = getMetaData(); - int splashScreenId = metadata != null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : 0; + Bundle metaData = getMetaData(); + int splashScreenId = metaData != null ? metaData.getInt(SPLASH_SCREEN_META_DATA_KEY) : 0; return splashScreenId != 0 ? Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP ? getResources().getDrawable(splashScreenId, getTheme()) : getResources().getDrawable(splashScreenId) : null; - } catch (PackageManager.NameNotFoundException e) { + } catch (RuntimeException e) { // This is never expected to happen. return null; } @@ -746,11 +746,11 @@ public boolean shouldDestroyEngineWithHost() { @NonNull public String getDartEntrypointFunctionName() { try { - Bundle metadata = getMetaData(); + Bundle metaData = getMetaData(); String desiredDartEntrypoint = - metadata != null ? metadata.getString(DART_ENTRYPOINT_META_DATA_KEY) : null; + metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null; return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT; - } catch (PackageManager.NameNotFoundException e) { + } catch (RuntimeException e) { return DEFAULT_DART_ENTRYPOINT; } } @@ -785,11 +785,11 @@ public String getInitialRoute() { } try { - Bundle metadata = getMetaData(); + Bundle metaData = getMetaData(); String desiredInitialRoute = - metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null; + metaData != null ? metaData.getString(INITIAL_ROUTE_META_DATA_KEY) : null; return desiredInitialRoute; - } catch (PackageManager.NameNotFoundException e) { + } catch (RuntimeException e) { return null; } } @@ -890,23 +890,16 @@ protected FlutterEngine getFlutterEngine() { return delegate.getFlutterEngine(); } - private Bundle cachedMetaData; - - /** Mocks the meta data for testing purposes. */ - @VisibleForTesting - public void setMetaData(Bundle metaData) { - cachedMetaData = metaData; - }; - /** Retrieves the meta data specified in the AndroidManifest.xml. */ @Nullable - protected Bundle getMetaData() throws PackageManager.NameNotFoundException { - if (cachedMetaData == null) { + protected Bundle getMetaData() throws RuntimeException { + try { ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - cachedMetaData = activityInfo.metaData; + return activityInfo.metaData; + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e.getMessage()); } - return cachedMetaData; } @Nullable @@ -989,17 +982,18 @@ public boolean shouldAttachEngineToActivity() { * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code * getInitialRoute} returns null. * - *

The default implementation looks for the value of the key `flutter_handle_deeplinking` in - * the AndroidManifest.xml. + *

The default implementation looks {@code } called {@link + * FlutterActivityLaunchConfigs#HANDLE_DEEPLINKING_META_DATA_KEY} within the Android manifest + * definition for this {@code FlutterActivity}. */ @Override public boolean shouldHandleDeeplinking() { try { - Bundle metadata = getMetaData(); + Bundle metaData = getMetaData(); boolean shouldHandleDeeplinking = - metadata != null ? metadata.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false; + metaData != null ? metaData.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false; return shouldHandleDeeplinking; - } catch (PackageManager.NameNotFoundException e) { + } catch (RuntimeException e) { return false; } } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index 00bca2bc3757d..3da7c7ec3ce7c 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -290,7 +290,7 @@ private void switchLaunchThemeForNormalTheme() { } else { Log.v(TAG, "Using the launch theme as normal theme."); } - } catch (PackageManager.NameNotFoundException exception) { + } catch (RuntimeException exception) { Log.e( TAG, "Could not read meta-data for FlutterFragmentActivity. Using the launch theme as normal theme."); @@ -319,15 +319,15 @@ public SplashScreen provideSplashScreen() { @SuppressWarnings("deprecation") private Drawable getSplashScreenFromManifest() { try { - Bundle metadata = getMetaData(); + Bundle metaData = getMetaData(); Integer splashScreenId = - metadata != null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : null; + metaData != null ? metaData.getInt(SPLASH_SCREEN_META_DATA_KEY) : null; return splashScreenId != null ? Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP ? getResources().getDrawable(splashScreenId, getTheme()) : getResources().getDrawable(splashScreenId) : null; - } catch (PackageManager.NameNotFoundException e) { + } catch (RuntimeException e) { // This is never expected to happen. return null; } @@ -550,17 +550,18 @@ protected boolean shouldAttachEngineToActivity() { * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code * getInitialRoute} returns null. * - *

The default implementation looks for the value of the key `flutter_handle_deeplinking` in - * the AndroidManifest.xml. + *

The default implementation looks {@code } called {@link + * FlutterActivityLaunchConfigs#HANDLE_DEEPLINKING_META_DATA_KEY} within the Android manifest + * definition for this {@code FlutterFragmentActivity}. */ @VisibleForTesting protected boolean shouldHandleDeeplinking() { try { - Bundle metadata = getMetaData(); + Bundle metaData = getMetaData(); boolean shouldHandleDeeplinking = - metadata != null ? metadata.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false; + metaData != null ? metaData.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false; return shouldHandleDeeplinking; - } catch (PackageManager.NameNotFoundException e) { + } catch (RuntimeException e) { return false; } } @@ -628,23 +629,16 @@ protected String getAppBundlePath() { return null; } - private Bundle cachedMetaData; - - /** Mocks the meta data for testing purposes. */ - @VisibleForTesting - public void setMetaData(Bundle metaData) { - cachedMetaData = metaData; - }; - /** Retrieves the meta data specified in the AndroidManifest.xml. */ @Nullable - protected Bundle getMetaData() throws PackageManager.NameNotFoundException { - if (cachedMetaData == null) { + protected Bundle getMetaData() throws RuntimeException { + try { ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - cachedMetaData = activityInfo.metaData; + return activityInfo.metaData; + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e.getMessage()); } - return cachedMetaData; } /** @@ -659,11 +653,11 @@ protected Bundle getMetaData() throws PackageManager.NameNotFoundException { @NonNull public String getDartEntrypointFunctionName() { try { - Bundle metadata = getMetaData(); + Bundle metaData = getMetaData(); String desiredDartEntrypoint = - metadata != null ? metadata.getString(DART_ENTRYPOINT_META_DATA_KEY) : null; + metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null; return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT; - } catch (PackageManager.NameNotFoundException e) { + } catch (RuntimeException e) { return DEFAULT_DART_ENTRYPOINT; } } @@ -698,11 +692,11 @@ protected String getInitialRoute() { } try { - Bundle metadata = getMetaData(); + Bundle metaData = getMetaData(); String desiredInitialRoute = - metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null; + metaData != null ? metaData.getString(INITIAL_ROUTE_META_DATA_KEY) : null; return desiredInitialRoute; - } catch (PackageManager.NameNotFoundException e) { + } catch (RuntimeException e) { return null; } } diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java index b32052cdb0175..da0ad6a115d8b 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java @@ -8,6 +8,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -118,7 +119,8 @@ public void itCreatesNewEngineIntentWithRequestedSettings() { } @Test - public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1() { + public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1() + throws RuntimeException { Intent intent = FlutterActivity.withNewEngine() .backgroundMode(BackgroundMode.transparent) @@ -128,12 +130,14 @@ public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1() { FlutterActivity flutterActivity = activityController.get(); Bundle bundle = new Bundle(); bundle.putBoolean(HANDLE_DEEPLINKING_META_DATA_KEY, true); - flutterActivity.setMetaData(bundle); - assertTrue(flutterActivity.shouldHandleDeeplinking()); + FlutterActivity spyFlutterActivity = spy(flutterActivity); + when(spyFlutterActivity.getMetaData()).thenReturn(bundle); + assertTrue(spyFlutterActivity.shouldHandleDeeplinking()); } @Test - public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase2() { + public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase2() + throws RuntimeException { Intent intent = FlutterActivity.withNewEngine() .backgroundMode(BackgroundMode.transparent) @@ -143,12 +147,14 @@ public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase2() { FlutterActivity flutterActivity = activityController.get(); Bundle bundle = new Bundle(); bundle.putBoolean(HANDLE_DEEPLINKING_META_DATA_KEY, false); - flutterActivity.setMetaData(bundle); - assertFalse(flutterActivity.shouldHandleDeeplinking()); + FlutterActivity spyFlutterActivity = spy(flutterActivity); + when(spyFlutterActivity.getMetaData()).thenReturn(bundle); + assertFalse(spyFlutterActivity.shouldHandleDeeplinking()); } @Test - public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase3() { + public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase3() + throws RuntimeException { Intent intent = FlutterActivity.withNewEngine() .backgroundMode(BackgroundMode.transparent) @@ -158,9 +164,10 @@ public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase3() { FlutterActivity flutterActivity = activityController.get(); // Creates an empty bundle. Bundle bundle = new Bundle(); - flutterActivity.setMetaData(bundle); + FlutterActivity spyFlutterActivity = spy(flutterActivity); + when(spyFlutterActivity.getMetaData()).thenReturn(bundle); // Empty bundle should return false. - assertFalse(flutterActivity.shouldHandleDeeplinking()); + assertFalse(spyFlutterActivity.shouldHandleDeeplinking()); } @Test diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java index 1127d7cca1c03..842059952ab63 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; @@ -86,37 +87,43 @@ public void itRegistersPluginsAtConfigurationTime() { } @Test - public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1() { + public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1() + throws RuntimeException { FlutterFragmentActivity activity = Robolectric.buildActivity(FlutterFragmentActivityWithProvidedEngine.class).get(); assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty()); Bundle bundle = new Bundle(); bundle.putBoolean(HANDLE_DEEPLINKING_META_DATA_KEY, true); - activity.setMetaData(bundle); - assertTrue(activity.shouldHandleDeeplinking()); + FlutterFragmentActivity spyFlutterActivity = spy(activity); + when(spyFlutterActivity.getMetaData()).thenReturn(bundle); + assertTrue(spyFlutterActivity.shouldHandleDeeplinking()); } @Test - public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase2() { + public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase2() + throws RuntimeException { FlutterFragmentActivity activity = Robolectric.buildActivity(FlutterFragmentActivityWithProvidedEngine.class).get(); assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty()); Bundle bundle = new Bundle(); bundle.putBoolean(HANDLE_DEEPLINKING_META_DATA_KEY, false); - activity.setMetaData(bundle); - assertFalse(activity.shouldHandleDeeplinking()); + FlutterFragmentActivity spyFlutterActivity = spy(activity); + when(spyFlutterActivity.getMetaData()).thenReturn(bundle); + assertFalse(spyFlutterActivity.shouldHandleDeeplinking()); } @Test - public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase3() { + public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase3() + throws RuntimeException { FlutterFragmentActivity activity = Robolectric.buildActivity(FlutterFragmentActivityWithProvidedEngine.class).get(); assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty()); // Creates an empty bundle. Bundle bundle = new Bundle(); - activity.setMetaData(bundle); + FlutterFragmentActivity spyFlutterActivity = spy(activity); + when(spyFlutterActivity.getMetaData()).thenReturn(bundle); // Empty bundle should return false. - assertFalse(activity.shouldHandleDeeplinking()); + assertFalse(spyFlutterActivity.shouldHandleDeeplinking()); } static class FlutterFragmentActivityWithProvidedEngine extends FlutterFragmentActivity { From 9822d25cdfc7f7f8fa1c499873f5d8c12f9907d0 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 11 Nov 2020 11:09:34 -0800 Subject: [PATCH 8/8] revert throw error --- .../embedding/android/FlutterActivity.java | 22 ++++++++----------- .../android/FlutterFragmentActivity.java | 22 ++++++++----------- .../android/FlutterActivityTest.java | 7 +++--- .../android/FlutterFragmentActivityTest.java | 7 +++--- 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 60dfe27aa748c..95e9b978892a5 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -456,7 +456,7 @@ private void switchLaunchThemeForNormalTheme() { } else { Log.v(TAG, "Using the launch theme as normal theme."); } - } catch (RuntimeException exception) { + } catch (PackageManager.NameNotFoundException exception) { Log.e( TAG, "Could not read meta-data for FlutterActivity. Using the launch theme as normal theme."); @@ -492,7 +492,7 @@ private Drawable getSplashScreenFromManifest() { ? getResources().getDrawable(splashScreenId, getTheme()) : getResources().getDrawable(splashScreenId) : null; - } catch (RuntimeException e) { + } catch (PackageManager.NameNotFoundException e) { // This is never expected to happen. return null; } @@ -750,7 +750,7 @@ public String getDartEntrypointFunctionName() { String desiredDartEntrypoint = metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null; return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT; - } catch (RuntimeException e) { + } catch (PackageManager.NameNotFoundException e) { return DEFAULT_DART_ENTRYPOINT; } } @@ -789,7 +789,7 @@ public String getInitialRoute() { String desiredInitialRoute = metaData != null ? metaData.getString(INITIAL_ROUTE_META_DATA_KEY) : null; return desiredInitialRoute; - } catch (RuntimeException e) { + } catch (PackageManager.NameNotFoundException e) { return null; } } @@ -892,14 +892,10 @@ protected FlutterEngine getFlutterEngine() { /** Retrieves the meta data specified in the AndroidManifest.xml. */ @Nullable - protected Bundle getMetaData() throws RuntimeException { - try { - ActivityInfo activityInfo = - getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - return activityInfo.metaData; - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException(e.getMessage()); - } + protected Bundle getMetaData() throws PackageManager.NameNotFoundException { + ActivityInfo activityInfo = + getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + return activityInfo.metaData; } @Nullable @@ -993,7 +989,7 @@ public boolean shouldHandleDeeplinking() { boolean shouldHandleDeeplinking = metaData != null ? metaData.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false; return shouldHandleDeeplinking; - } catch (RuntimeException e) { + } catch (PackageManager.NameNotFoundException e) { return false; } } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index 3da7c7ec3ce7c..f5ef1711e2592 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -290,7 +290,7 @@ private void switchLaunchThemeForNormalTheme() { } else { Log.v(TAG, "Using the launch theme as normal theme."); } - } catch (RuntimeException exception) { + } catch (PackageManager.NameNotFoundException exception) { Log.e( TAG, "Could not read meta-data for FlutterFragmentActivity. Using the launch theme as normal theme."); @@ -327,7 +327,7 @@ private Drawable getSplashScreenFromManifest() { ? getResources().getDrawable(splashScreenId, getTheme()) : getResources().getDrawable(splashScreenId) : null; - } catch (RuntimeException e) { + } catch (PackageManager.NameNotFoundException e) { // This is never expected to happen. return null; } @@ -561,7 +561,7 @@ protected boolean shouldHandleDeeplinking() { boolean shouldHandleDeeplinking = metaData != null ? metaData.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false; return shouldHandleDeeplinking; - } catch (RuntimeException e) { + } catch (PackageManager.NameNotFoundException e) { return false; } } @@ -631,14 +631,10 @@ protected String getAppBundlePath() { /** Retrieves the meta data specified in the AndroidManifest.xml. */ @Nullable - protected Bundle getMetaData() throws RuntimeException { - try { - ActivityInfo activityInfo = - getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - return activityInfo.metaData; - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException(e.getMessage()); - } + protected Bundle getMetaData() throws PackageManager.NameNotFoundException { + ActivityInfo activityInfo = + getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + return activityInfo.metaData; } /** @@ -657,7 +653,7 @@ public String getDartEntrypointFunctionName() { String desiredDartEntrypoint = metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null; return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT; - } catch (RuntimeException e) { + } catch (PackageManager.NameNotFoundException e) { return DEFAULT_DART_ENTRYPOINT; } } @@ -696,7 +692,7 @@ protected String getInitialRoute() { String desiredInitialRoute = metaData != null ? metaData.getString(INITIAL_ROUTE_META_DATA_KEY) : null; return desiredInitialRoute; - } catch (RuntimeException e) { + } catch (PackageManager.NameNotFoundException e) { return null; } } diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java index da0ad6a115d8b..19720fa7e6e02 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java @@ -15,6 +15,7 @@ import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -120,7 +121,7 @@ public void itCreatesNewEngineIntentWithRequestedSettings() { @Test public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1() - throws RuntimeException { + throws PackageManager.NameNotFoundException { Intent intent = FlutterActivity.withNewEngine() .backgroundMode(BackgroundMode.transparent) @@ -137,7 +138,7 @@ public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1() @Test public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase2() - throws RuntimeException { + throws PackageManager.NameNotFoundException { Intent intent = FlutterActivity.withNewEngine() .backgroundMode(BackgroundMode.transparent) @@ -154,7 +155,7 @@ public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase2() @Test public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase3() - throws RuntimeException { + throws PackageManager.NameNotFoundException { Intent intent = FlutterActivity.withNewEngine() .backgroundMode(BackgroundMode.transparent) diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java index 842059952ab63..448646db9c793 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java @@ -10,6 +10,7 @@ import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -88,7 +89,7 @@ public void itRegistersPluginsAtConfigurationTime() { @Test public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1() - throws RuntimeException { + throws PackageManager.NameNotFoundException { FlutterFragmentActivity activity = Robolectric.buildActivity(FlutterFragmentActivityWithProvidedEngine.class).get(); assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty()); @@ -101,7 +102,7 @@ public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1() @Test public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase2() - throws RuntimeException { + throws PackageManager.NameNotFoundException { FlutterFragmentActivity activity = Robolectric.buildActivity(FlutterFragmentActivityWithProvidedEngine.class).get(); assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty()); @@ -114,7 +115,7 @@ public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase2() @Test public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase3() - throws RuntimeException { + throws PackageManager.NameNotFoundException { FlutterFragmentActivity activity = Robolectric.buildActivity(FlutterFragmentActivityWithProvidedEngine.class).get(); assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());