Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit d97a81c

Browse files
authored
reland support uri launch in android (#22363)
* Revert "Revert "support uri intent launcher in android (#21275)" (#22298)" This reverts commit f61cbc0. * reland support uri launch for android * refactor * update * fix test * addressing comments * addressing comments * revert throw error
1 parent 86bd77c commit d97a81c

File tree

10 files changed

+344
-45
lines changed

10 files changed

+344
-45
lines changed

shell/platform/android/io/flutter/embedding/android/FlutterActivity.java

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY;
1414
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_ENABLE_STATE_RESTORATION;
1515
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_INITIAL_ROUTE;
16+
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY;
1617
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY;
1718
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY;
1819
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.SPLASH_SCREEN_META_DATA_KEY;
@@ -446,10 +447,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
446447
*/
447448
private void switchLaunchThemeForNormalTheme() {
448449
try {
449-
ActivityInfo activityInfo =
450-
getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
451-
if (activityInfo.metaData != null) {
452-
int normalThemeRID = activityInfo.metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1);
450+
Bundle metaData = getMetaData();
451+
if (metaData != null) {
452+
int normalThemeRID = metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1);
453453
if (normalThemeRID != -1) {
454454
setTheme(normalThemeRID);
455455
}
@@ -485,10 +485,8 @@ public SplashScreen provideSplashScreen() {
485485
@SuppressWarnings("deprecation")
486486
private Drawable getSplashScreenFromManifest() {
487487
try {
488-
ActivityInfo activityInfo =
489-
getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
490-
Bundle metadata = activityInfo.metaData;
491-
int splashScreenId = metadata != null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : 0;
488+
Bundle metaData = getMetaData();
489+
int splashScreenId = metaData != null ? metaData.getInt(SPLASH_SCREEN_META_DATA_KEY) : 0;
492490
return splashScreenId != 0
493491
? Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP
494492
? getResources().getDrawable(splashScreenId, getTheme())
@@ -748,11 +746,9 @@ public boolean shouldDestroyEngineWithHost() {
748746
@NonNull
749747
public String getDartEntrypointFunctionName() {
750748
try {
751-
ActivityInfo activityInfo =
752-
getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
753-
Bundle metadata = activityInfo.metaData;
749+
Bundle metaData = getMetaData();
754750
String desiredDartEntrypoint =
755-
metadata != null ? metadata.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
751+
metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
756752
return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT;
757753
} catch (PackageManager.NameNotFoundException e) {
758754
return DEFAULT_DART_ENTRYPOINT;
@@ -779,22 +775,22 @@ public String getDartEntrypointFunctionName() {
779775
* have control over the incoming {@code Intent}.
780776
*
781777
* <p>Subclasses may override this method to directly control the initial route.
778+
*
779+
* <p>If this method returns null and the {@code shouldHandleDeeplinking} returns true, the
780+
* initial route is derived from the {@code Intent} through the Intent.getData() instead.
782781
*/
783-
@NonNull
784782
public String getInitialRoute() {
785783
if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) {
786784
return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE);
787785
}
788786

789787
try {
790-
ActivityInfo activityInfo =
791-
getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
792-
Bundle metadata = activityInfo.metaData;
788+
Bundle metaData = getMetaData();
793789
String desiredInitialRoute =
794-
metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
795-
return desiredInitialRoute != null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE;
790+
metaData != null ? metaData.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
791+
return desiredInitialRoute;
796792
} catch (PackageManager.NameNotFoundException e) {
797-
return DEFAULT_INITIAL_ROUTE;
793+
return null;
798794
}
799795
}
800796

@@ -894,6 +890,14 @@ protected FlutterEngine getFlutterEngine() {
894890
return delegate.getFlutterEngine();
895891
}
896892

893+
/** Retrieves the meta data specified in the AndroidManifest.xml. */
894+
@Nullable
895+
protected Bundle getMetaData() throws PackageManager.NameNotFoundException {
896+
ActivityInfo activityInfo =
897+
getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
898+
return activityInfo.metaData;
899+
}
900+
897901
@Nullable
898902
@Override
899903
public PlatformPlugin providePlatformPlugin(
@@ -970,6 +974,26 @@ public boolean shouldAttachEngineToActivity() {
970974
return true;
971975
}
972976

977+
/**
978+
* Whether to handle the deeplinking from the {@code Intent} automatically if the {@code
979+
* getInitialRoute} returns null.
980+
*
981+
* <p>The default implementation looks {@code <meta-data>} called {@link
982+
* FlutterActivityLaunchConfigs#HANDLE_DEEPLINKING_META_DATA_KEY} within the Android manifest
983+
* definition for this {@code FlutterActivity}.
984+
*/
985+
@Override
986+
public boolean shouldHandleDeeplinking() {
987+
try {
988+
Bundle metaData = getMetaData();
989+
boolean shouldHandleDeeplinking =
990+
metaData != null ? metaData.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false;
991+
return shouldHandleDeeplinking;
992+
} catch (PackageManager.NameNotFoundException e) {
993+
return false;
994+
}
995+
}
996+
973997
@Override
974998
public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) {
975999
// Hook for subclasses.

shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
package io.flutter.embedding.android;
66

77
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
8+
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE;
89

910
import android.app.Activity;
1011
import android.content.Context;
1112
import android.content.Intent;
13+
import android.net.Uri;
1214
import android.os.Build;
1315
import android.os.Bundle;
1416
import android.view.LayoutInflater;
@@ -362,19 +364,23 @@ private void doInitialFlutterViewRun() {
362364
// So this is expected behavior in many cases.
363365
return;
364366
}
365-
367+
String initialRoute = host.getInitialRoute();
368+
if (initialRoute == null) {
369+
initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent());
370+
if (initialRoute == null) {
371+
initialRoute = DEFAULT_INITIAL_ROUTE;
372+
}
373+
}
366374
Log.v(
367375
TAG,
368376
"Executing Dart entrypoint: "
369377
+ host.getDartEntrypointFunctionName()
370378
+ ", and sending initial route: "
371-
+ host.getInitialRoute());
379+
+ initialRoute);
372380

373381
// The engine needs to receive the Flutter app's initial route before executing any
374382
// Dart code to ensure that the initial route arrives in time to be applied.
375-
if (host.getInitialRoute() != null) {
376-
flutterEngine.getNavigationChannel().setInitialRoute(host.getInitialRoute());
377-
}
383+
flutterEngine.getNavigationChannel().setInitialRoute(initialRoute);
378384

379385
String appBundlePathOverride = host.getAppBundlePath();
380386
if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
@@ -388,6 +394,16 @@ private void doInitialFlutterViewRun() {
388394
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
389395
}
390396

397+
private String maybeGetInitialRouteFromIntent(Intent intent) {
398+
if (host.shouldHandleDeeplinking()) {
399+
Uri data = intent.getData();
400+
if (data != null && !data.toString().isEmpty()) {
401+
return data.toString();
402+
}
403+
}
404+
return null;
405+
}
406+
391407
/**
392408
* Invoke this from {@code Activity#onResume()} or {@code Fragment#onResume()}.
393409
*
@@ -622,8 +638,12 @@ void onRequestPermissionsResult(
622638
void onNewIntent(@NonNull Intent intent) {
623639
ensureAlive();
624640
if (flutterEngine != null) {
625-
Log.v(TAG, "Forwarding onNewIntent() to FlutterEngine.");
641+
Log.v(TAG, "Forwarding onNewIntent() to FlutterEngine and sending pushRoute message.");
626642
flutterEngine.getActivityControlSurface().onNewIntent(intent);
643+
String initialRoute = maybeGetInitialRouteFromIntent(intent);
644+
if (initialRoute != null && !initialRoute.isEmpty()) {
645+
flutterEngine.getNavigationChannel().pushRoute(initialRoute);
646+
}
627647
} else {
628648
Log.w(TAG, "onNewIntent() invoked before FlutterFragment was attached to an Activity.");
629649
}
@@ -735,6 +755,10 @@ private void ensureAlive() {
735755
@NonNull
736756
Context getContext();
737757

758+
/** Returns true if the delegate should retrieve the initial route from the {@link Intent}. */
759+
@Nullable
760+
boolean shouldHandleDeeplinking();
761+
738762
/**
739763
* Returns the host {@link Activity} or the {@code Activity} that is currently attached to the
740764
* host {@code Fragment}.

shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ public class FlutterActivityLaunchConfigs {
1616
"io.flutter.embedding.android.SplashScreenDrawable";
1717
/* package */ static final String NORMAL_THEME_META_DATA_KEY =
1818
"io.flutter.embedding.android.NormalTheme";
19-
19+
/* package */ static final String HANDLE_DEEPLINKING_META_DATA_KEY =
20+
"flutter_deeplinking_enabled";
2021
// Intent extra arguments.
2122
/* package */ static final String EXTRA_INITIAL_ROUTE = "route";
2223
/* package */ static final String EXTRA_BACKGROUND_MODE = "background_mode";

shell/platform/android/io/flutter/embedding/android/FlutterFragment.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
8888
protected static final String ARG_DART_ENTRYPOINT = "dart_entrypoint";
8989
/** Initial Flutter route that is rendered in a Navigator widget. */
9090
protected static final String ARG_INITIAL_ROUTE = "initial_route";
91+
/** Whether the activity delegate should handle the deeplinking request. */
92+
protected static final String ARG_HANDLE_DEEPLINKING = "handle_deeplinking";
9193
/** Path to Flutter's Dart code. */
9294
protected static final String ARG_APP_BUNDLE_PATH = "app_bundle_path";
9395
/** Flutter shell arguments. */
@@ -185,6 +187,7 @@ public static class NewEngineFragmentBuilder {
185187
private final Class<? extends FlutterFragment> fragmentClass;
186188
private String dartEntrypoint = "main";
187189
private String initialRoute = "/";
190+
private boolean handleDeeplinking = false;
188191
private String appBundlePath = null;
189192
private FlutterShellArgs shellArgs = null;
190193
private RenderMode renderMode = RenderMode.surface;
@@ -224,6 +227,16 @@ public NewEngineFragmentBuilder initialRoute(@NonNull String initialRoute) {
224227
return this;
225228
}
226229

230+
/**
231+
* Whether to handle the deeplinking from the {@code Intent} automatically if the {@code
232+
* getInitialRoute} returns null.
233+
*/
234+
@NonNull
235+
public NewEngineFragmentBuilder handleDeeplinking(@NonNull Boolean handleDeeplinking) {
236+
this.handleDeeplinking = handleDeeplinking;
237+
return this;
238+
}
239+
227240
/**
228241
* The path to the app bundle which contains the Dart app to execute. Null when unspecified,
229242
* which defaults to {@link FlutterLoader#findAppBundlePath()}
@@ -316,6 +329,7 @@ public NewEngineFragmentBuilder shouldAttachEngineToActivity(
316329
protected Bundle createArgs() {
317330
Bundle args = new Bundle();
318331
args.putString(ARG_INITIAL_ROUTE, initialRoute);
332+
args.putBoolean(ARG_HANDLE_DEEPLINKING, handleDeeplinking);
319333
args.putString(ARG_APP_BUNDLE_PATH, appBundlePath);
320334
args.putString(ARG_DART_ENTRYPOINT, dartEntrypoint);
321335
// TODO(mattcarroll): determine if we should have an explicit FlutterTestFragment instead of
@@ -409,6 +423,7 @@ public static class CachedEngineFragmentBuilder {
409423
private final Class<? extends FlutterFragment> fragmentClass;
410424
private final String engineId;
411425
private boolean destroyEngineWithFragment = false;
426+
private boolean handleDeeplinking = false;
412427
private RenderMode renderMode = RenderMode.surface;
413428
private TransparencyMode transparencyMode = TransparencyMode.transparent;
414429
private boolean shouldAttachEngineToActivity = true;
@@ -460,6 +475,16 @@ public CachedEngineFragmentBuilder transparencyMode(
460475
return this;
461476
}
462477

478+
/**
479+
* Whether to handle the deeplinking from the {@code Intent} automatically if the {@code
480+
* getInitialRoute} returns null.
481+
*/
482+
@NonNull
483+
public CachedEngineFragmentBuilder handleDeeplinking(@NonNull Boolean handleDeeplinking) {
484+
this.handleDeeplinking = handleDeeplinking;
485+
return this;
486+
}
487+
463488
/**
464489
* Whether or not this {@code FlutterFragment} should automatically attach its {@code Activity}
465490
* as a control surface for its {@link FlutterEngine}.
@@ -512,6 +537,7 @@ protected Bundle createArgs() {
512537
Bundle args = new Bundle();
513538
args.putString(ARG_CACHED_ENGINE_ID, engineId);
514539
args.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, destroyEngineWithFragment);
540+
args.putBoolean(ARG_HANDLE_DEEPLINKING, handleDeeplinking);
515541
args.putString(
516542
ARG_FLUTTERVIEW_RENDER_MODE,
517543
renderMode != null ? renderMode.name() : RenderMode.surface.name());
@@ -1016,6 +1042,15 @@ public boolean shouldAttachEngineToActivity() {
10161042
return getArguments().getBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY);
10171043
}
10181044

1045+
/**
1046+
* Whether to handle the deeplinking from the {@code Intent} automatically if the {@code
1047+
* getInitialRoute} returns null.
1048+
*/
1049+
@Override
1050+
public boolean shouldHandleDeeplinking() {
1051+
return getArguments().getBoolean(ARG_HANDLE_DEEPLINKING);
1052+
}
1053+
10191054
@Override
10201055
public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) {
10211056
// Hook for subclasses.

0 commit comments

Comments
 (0)