From f6c51fd5eae3fc00537a2b1a82e82d6a5027aff5 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Fri, 5 May 2023 10:25:59 -0700 Subject: [PATCH] Makes android embedding to send full uri --- .../flutter/app/FlutterActivityDelegate.java | 3 +- .../embedding/android/FlutterActivity.java | 7 ++-- .../FlutterActivityAndFragmentDelegate.java | 30 ++++++----------- .../embedding/android/FlutterFragment.java | 5 +-- .../embedding/engine/FlutterEngine.java | 3 +- .../embedding/engine/FlutterEngineGroup.java | 11 ++++--- .../flutter/embedding/engine/FlutterJNI.java | 7 ++-- .../systemchannels/NavigationChannel.java | 14 +++----- .../android/io/flutter/view/FlutterView.java | 7 ++-- ...lutterActivityAndFragmentDelegateTest.java | 32 +++++++++---------- 10 files changed, 52 insertions(+), 67 deletions(-) diff --git a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java index bcc54b1e9786f..c71bc8818dfa6 100644 --- a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java +++ b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java @@ -17,6 +17,7 @@ import android.content.res.Configuration; import android.content.res.Resources.NotFoundException; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.TypedValue; @@ -356,7 +357,7 @@ private static String[] getArgsFromIntent(Intent intent) { private boolean loadIntent(Intent intent) { String action = intent.getAction(); if (Intent.ACTION_RUN.equals(action)) { - String route = intent.getStringExtra("route"); + Uri route = Uri.parse(intent.getStringExtra("route")); String appBundlePath = intent.getDataString(); if (appBundlePath == null) { // Fall back to the installation path if no bundle path was specified. diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 53344a1a3ad79..6904400b523c5 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -32,6 +32,7 @@ import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.view.View; @@ -1149,16 +1150,16 @@ public String getDartEntrypointLibraryUri() { *

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() { + public Uri getInitialRoute() { if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) { - return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE); + return Uri.parse(getIntent().getStringExtra(EXTRA_INITIAL_ROUTE)); } try { Bundle metaData = getMetaData(); String desiredInitialRoute = metaData != null ? metaData.getString(INITIAL_ROUTE_META_DATA_KEY) : null; - return desiredInitialRoute; + return desiredInitialRoute == null ? null : Uri.parse(desiredInitialRoute); } catch (PackageManager.NameNotFoundException e) { 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 359eca9514125..f184f8a9a1800 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -235,11 +235,11 @@ private FlutterEngineGroup.Options addEntrypointOptions(FlutterEngineGroup.Optio DartExecutor.DartEntrypoint dartEntrypoint = new DartExecutor.DartEntrypoint( appBundlePathOverride, host.getDartEntrypointFunctionName()); - String initialRoute = host.getInitialRoute(); + Uri initialRoute = host.getInitialRoute(); if (initialRoute == null) { initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent()); if (initialRoute == null) { - initialRoute = DEFAULT_INITIAL_ROUTE; + initialRoute = Uri.parse(DEFAULT_INITIAL_ROUTE); } } return options @@ -484,11 +484,11 @@ private void doInitialFlutterViewRun() { // So this is expected behavior in many cases. return; } - String initialRoute = host.getInitialRoute(); + Uri initialRoute = host.getInitialRoute(); if (initialRoute == null) { initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent()); if (initialRoute == null) { - initialRoute = DEFAULT_INITIAL_ROUTE; + initialRoute = Uri.parse(DEFAULT_INITIAL_ROUTE); } } @Nullable String libraryUri = host.getDartEntrypointLibraryUri(); @@ -521,21 +521,9 @@ private void doInitialFlutterViewRun() { flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint, host.getDartEntrypointArgs()); } - private String maybeGetInitialRouteFromIntent(Intent intent) { + private Uri maybeGetInitialRouteFromIntent(Intent intent) { if (host.shouldHandleDeeplinking()) { - Uri data = intent.getData(); - if (data != null) { - String fullRoute = data.getPath(); - if (fullRoute != null && !fullRoute.isEmpty()) { - if (data.getQuery() != null && !data.getQuery().isEmpty()) { - fullRoute += "?" + data.getQuery(); - } - if (data.getFragment() != null && !data.getFragment().isEmpty()) { - fullRoute += "#" + data.getFragment(); - } - return fullRoute; - } - } + return intent.getData(); } return null; } @@ -845,8 +833,8 @@ void onNewIntent(@NonNull Intent intent) { TAG, "Forwarding onNewIntent() to FlutterEngine and sending pushRouteInformation message."); flutterEngine.getActivityControlSurface().onNewIntent(intent); - String initialRoute = maybeGetInitialRouteFromIntent(intent); - if (initialRoute != null && !initialRoute.isEmpty()) { + Uri initialRoute = maybeGetInitialRouteFromIntent(intent); + if (initialRoute != null) { flutterEngine.getNavigationChannel().pushRouteInformation(initialRoute); } } else { @@ -1049,7 +1037,7 @@ private void ensureAlive() { /** Returns the initial route that Flutter renders. */ @Nullable - String getInitialRoute(); + Uri getInitialRoute(); /** * Returns the {@link RenderMode} used by the {@link FlutterView} that displays the {@link diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index 85ce3ade38fb0..f1595e09ef159 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -8,6 +8,7 @@ import android.content.ComponentCallbacks2; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; @@ -1410,8 +1411,8 @@ public String getAppBundlePath() { */ @Override @Nullable - public String getInitialRoute() { - return getArguments().getString(ARG_INITIAL_ROUTE); + public Uri getInitialRoute() { + return Uri.parse(getArguments().getString(ARG_INITIAL_ROUTE)); } /** diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index bc2902c0281a9..ee5a6e54aa3cf 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetManager; +import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -415,7 +416,7 @@ private boolean isAttachedToJni() { /*package*/ FlutterEngine spawn( @NonNull Context context, @NonNull DartEntrypoint dartEntrypoint, - @Nullable String initialRoute, + @Nullable Uri initialRoute, @Nullable List dartEntrypointArgs, @Nullable PlatformViewsController platformViewsController, boolean automaticallyRegisterPlugins, diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java index c45210f4f1063..4753b8b3844c4 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java @@ -5,6 +5,7 @@ package io.flutter.embedding.engine; import android.content.Context; +import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -117,7 +118,7 @@ public FlutterEngine createAndRunEngine( public FlutterEngine createAndRunEngine( @NonNull Context context, @Nullable DartEntrypoint dartEntrypoint, - @Nullable String initialRoute) { + @Nullable Uri initialRoute) { return createAndRunEngine( new Options(context).setDartEntrypoint(dartEntrypoint).setInitialRoute(initialRoute)); } @@ -141,7 +142,7 @@ public FlutterEngine createAndRunEngine(@NonNull Options options) { Context context = options.getContext(); DartEntrypoint dartEntrypoint = options.getDartEntrypoint(); - String initialRoute = options.getInitialRoute(); + Uri initialRoute = options.getInitialRoute(); List dartEntrypointArgs = options.getDartEntrypointArgs(); PlatformViewsController platformViewsController = options.getPlatformViewsController(); platformViewsController = @@ -218,7 +219,7 @@ public void onEngineWillDestroy() { public static class Options { @NonNull private Context context; @Nullable private DartEntrypoint dartEntrypoint; - @Nullable private String initialRoute; + @Nullable private Uri initialRoute; @Nullable private List dartEntrypointArgs; @NonNull private PlatformViewsController platformViewsController; private boolean automaticallyRegisterPlugins = true; @@ -245,7 +246,7 @@ public DartEntrypoint getDartEntrypoint() { * The name of the initial Flutter `Navigator` `Route` to load. If this is null, it will default * to the "/" route. */ - public String getInitialRoute() { + public Uri getInitialRoute() { return initialRoute; } @@ -294,7 +295,7 @@ public Options setDartEntrypoint(DartEntrypoint dartEntrypoint) { * @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is * null, it will default to the "/" route. */ - public Options setInitialRoute(String initialRoute) { + public Options setInitialRoute(Uri initialRoute) { this.initialRoute = initialRoute; return this; } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 3a20807bdf1f0..e18ffbe373595 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -10,6 +10,7 @@ import android.graphics.ColorSpace; import android.graphics.ImageDecoder; import android.graphics.SurfaceTexture; +import android.net.Uri; import android.os.Build; import android.os.Looper; import android.util.Size; @@ -429,7 +430,7 @@ public long performNativeAttach(@NonNull FlutterJNI flutterJNI) { public FlutterJNI spawn( @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, - @Nullable String initialRoute, + @Nullable Uri initialRoute, @Nullable List entrypointArgs) { ensureRunningOnMainThread(); ensureAttachedToNative(); @@ -438,7 +439,7 @@ public FlutterJNI spawn( nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction, - initialRoute, + initialRoute.toString(), entrypointArgs); Preconditions.checkState( spawnedJNI.nativeShellHolderId != null && spawnedJNI.nativeShellHolderId != 0, @@ -461,7 +462,7 @@ private native FlutterJNI nativeSpawn( *

This method must not be invoked if {@code FlutterJNI} is not already attached to native. * *

Invoking this method will result in the release of all native-side resources that were set - * up during {@link #attachToNative()} or {@link #spawn(String, String, String, List)}, or + * up during {@link #attachToNative()} or {@link #spawn(String, String, Uri, List)}, or * accumulated thereafter. * *

It is permissible to re-attach this instance to native after detaching it from native. diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/NavigationChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/NavigationChannel.java index 36cac70125bf5..f53f5ab29c5dc 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/NavigationChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/NavigationChannel.java @@ -4,6 +4,7 @@ package io.flutter.embedding.engine.systemchannels; +import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.Log; @@ -35,20 +36,15 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result } }; - public void setInitialRoute(@NonNull String initialRoute) { + public void setInitialRoute(@NonNull Uri initialRoute) { Log.v(TAG, "Sending message to set initial route to '" + initialRoute + "'"); - channel.invokeMethod("setInitialRoute", initialRoute); + channel.invokeMethod("setInitialRoute", initialRoute.toString()); } - public void pushRoute(@NonNull String route) { - Log.v(TAG, "Sending message to push route '" + route + "'"); - channel.invokeMethod("pushRoute", route); - } - - public void pushRouteInformation(@NonNull String route) { + public void pushRouteInformation(@NonNull Uri route) { Log.v(TAG, "Sending message to push route information '" + route + "'"); Map message = new HashMap<>(); - message.put("location", route); + message.put("location", route.toString()); channel.invokeMethod("pushRouteInformation", message); } diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index eb498a2335e96..5dd23b3b27bd5 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -14,6 +14,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import android.net.Uri; import android.os.Build; import android.os.Handler; import android.text.format.DateFormat; @@ -356,14 +357,10 @@ public void disableTransparentBackground() { getHolder().setFormat(PixelFormat.OPAQUE); } - public void setInitialRoute(String route) { + public void setInitialRoute(Uri route) { navigationChannel.setInitialRoute(route); } - public void pushRoute(String route) { - navigationChannel.pushRoute(route); - } - public void popRoute() { navigationChannel.popRoute(); } 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 d5daa43f269a2..7f630d752eb72 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -810,13 +810,13 @@ public void itSendsPushRouteInformationMessageWhenOnNewIntent() { delegate.onAttach(ctx); Intent mockIntent = mock(Intent.class); - when(mockIntent.getData()).thenReturn(Uri.parse("http://myApp/custom/route?query=test")); + Uri expected = Uri.parse("http://myApp/custom/route?query=test"); + when(mockIntent.getData()).thenReturn(expected); // 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)) - .pushRouteInformation("/custom/route?query=test"); + verify(mockFlutterEngine.getNavigationChannel(), times(1)).pushRouteInformation(expected); } @Test @@ -830,16 +830,15 @@ public void itDoesNotSendPushRouteInformationMessageWhenOnNewIntentIsNonHierarch delegate.onAttach(ctx); Intent mockIntent = mock(Intent.class); - + Uri expected = Uri.parse("mailto:test@test.com"); // mailto: URIs are non-hierarchical - when(mockIntent.getData()).thenReturn(Uri.parse("mailto:test@test.com")); + when(mockIntent.getData()).thenReturn(expected); // Emulate the host and call the method delegate.onNewIntent(mockIntent); // Verify that the navigation channel was not given a push route message. - verify(mockFlutterEngine.getNavigationChannel(), times(0)) - .pushRouteInformation("mailto:test@test.com"); + verify(mockFlutterEngine.getNavigationChannel(), times(0)).pushRouteInformation(expected); } @Test @@ -852,15 +851,14 @@ public void itSendsPushRouteInformationMessageWhenOnNewIntentWithQueryParameterA // The FlutterEngine is set up in onAttach(). delegate.onAttach(ctx); + Uri expected = Uri.parse("http://myApp/custom/route?query=test#fragment"); Intent mockIntent = mock(Intent.class); - when(mockIntent.getData()) - .thenReturn(Uri.parse("http://myApp/custom/route?query=test#fragment")); + when(mockIntent.getData()).thenReturn(expected); // 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)) - .pushRouteInformation("/custom/route?query=test#fragment"); + verify(mockFlutterEngine.getNavigationChannel(), times(1)).pushRouteInformation(expected); } @Test @@ -873,14 +871,14 @@ public void itSendsPushRouteInformationMessageWhenOnNewIntentWithFragmentNoQuery // The FlutterEngine is set up in onAttach(). delegate.onAttach(ctx); + Uri expected = Uri.parse("http://myApp/custom/route#fragment"); Intent mockIntent = mock(Intent.class); - when(mockIntent.getData()).thenReturn(Uri.parse("http://myApp/custom/route#fragment")); + when(mockIntent.getData()).thenReturn(expected); // 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)) - .pushRouteInformation("/custom/route#fragment"); + verify(mockFlutterEngine.getNavigationChannel(), times(1)).pushRouteInformation(expected); } @Test @@ -893,14 +891,14 @@ public void itSendsPushRouteInformationMessageWhenOnNewIntentNoQueryParameter() // The FlutterEngine is set up in onAttach(). delegate.onAttach(ctx); + Uri expected = Uri.parse("http://myApp/custom/route"); Intent mockIntent = mock(Intent.class); - when(mockIntent.getData()).thenReturn(Uri.parse("http://myApp/custom/route")); + when(mockIntent.getData()).thenReturn(Uri.parse(expected)); // 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)) - .pushRouteInformation("/custom/route"); + verify(mockFlutterEngine.getNavigationChannel(), times(1)).pushRouteInformation(expected); } @Test