This repository was archived by the owner on Feb 25, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6k
reland support uri launch in android #22363
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
cda2c3b
Revert "Revert "support uri intent launcher in android (#21275)" (#22…
chunhtai 5fe12e9
reland support uri launch for android
chunhtai bce3134
refactor
chunhtai 6b09ef5
update
chunhtai fe25a89
fix test
chunhtai bc1059c
addressing comments
chunhtai 33428d1
addressing comments
chunhtai 9822d25
revert throw error
chunhtai File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -446,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); | ||
| } | ||
|
|
@@ -485,10 +485,8 @@ public SplashScreen provideSplashScreen() { | |
| @SuppressWarnings("deprecation") | ||
| private Drawable getSplashScreenFromManifest() { | ||
| try { | ||
| ActivityInfo activityInfo = | ||
| getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); | ||
| Bundle metadata = activityInfo.metaData; | ||
| 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()) | ||
|
|
@@ -748,11 +746,9 @@ 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; | ||
| metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null; | ||
| return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT; | ||
| } catch (PackageManager.NameNotFoundException e) { | ||
| return DEFAULT_DART_ENTRYPOINT; | ||
|
|
@@ -779,22 +775,22 @@ public String getDartEntrypointFunctionName() { | |
| * have control over the incoming {@code Intent}. | ||
| * | ||
| * <p>Subclasses may override this method to directly control the initial route. | ||
| * | ||
| * <p>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. | ||
| */ | ||
| @NonNull | ||
| public String getInitialRoute() { | ||
| if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) { | ||
| return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE); | ||
| } | ||
|
|
||
| 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 != null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE; | ||
| metaData != null ? metaData.getString(INITIAL_ROUTE_META_DATA_KEY) : null; | ||
| return desiredInitialRoute; | ||
| } catch (PackageManager.NameNotFoundException e) { | ||
| return DEFAULT_INITIAL_ROUTE; | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -894,6 +890,14 @@ protected FlutterEngine getFlutterEngine() { | |
| return delegate.getFlutterEngine(); | ||
| } | ||
|
|
||
| /** Retrieves the meta data specified in the AndroidManifest.xml. */ | ||
| @Nullable | ||
| protected Bundle getMetaData() throws PackageManager.NameNotFoundException { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The throw here is a bit burdensome for the subclasser. I would just re-throw it in a RuntimeException since it should be very unusual. |
||
| ActivityInfo activityInfo = | ||
| getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); | ||
| return activityInfo.metaData; | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public PlatformPlugin providePlatformPlugin( | ||
|
|
@@ -970,6 +974,26 @@ public boolean shouldAttachEngineToActivity() { | |
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code | ||
| * getInitialRoute} returns null. | ||
| * | ||
| * <p>The default implementation looks {@code <meta-data>} called {@link | ||
| * FlutterActivityLaunchConfigs#HANDLE_DEEPLINKING_META_DATA_KEY} within the Android manifest | ||
| * definition for this {@code FlutterActivity}. | ||
| */ | ||
| @Override | ||
| public boolean shouldHandleDeeplinking() { | ||
chunhtai marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| try { | ||
| Bundle metaData = getMetaData(); | ||
| 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. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,10 +5,12 @@ | |
| 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; | ||
| import android.content.Intent; | ||
| import android.net.Uri; | ||
| import android.os.Build; | ||
| import android.os.Bundle; | ||
| import android.view.LayoutInflater; | ||
|
|
@@ -362,19 +364,23 @@ private void doInitialFlutterViewRun() { | |
| // So this is expected behavior in many cases. | ||
| return; | ||
| } | ||
|
|
||
| String initialRoute = host.getInitialRoute(); | ||
| if (initialRoute == null) { | ||
| initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent()); | ||
| if (initialRoute == null) { | ||
| initialRoute = DEFAULT_INITIAL_ROUTE; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made it default to |
||
| } | ||
| } | ||
| 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()); | ||
| } | ||
| flutterEngine.getNavigationChannel().setInitialRoute(initialRoute); | ||
|
|
||
| String appBundlePathOverride = host.getAppBundlePath(); | ||
| if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) { | ||
|
|
@@ -388,6 +394,16 @@ private void doInitialFlutterViewRun() { | |
| flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); | ||
| } | ||
|
|
||
| private String maybeGetInitialRouteFromIntent(Intent intent) { | ||
| if (host.shouldHandleDeeplinking()) { | ||
| 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 +638,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 = maybeGetInitialRouteFromIntent(intent); | ||
| if (initialRoute != null && !initialRoute.isEmpty()) { | ||
| flutterEngine.getNavigationChannel().pushRoute(initialRoute); | ||
| } | ||
| } else { | ||
| Log.w(TAG, "onNewIntent() invoked before FlutterFragment was attached to an Activity."); | ||
| } | ||
|
|
@@ -735,6 +755,10 @@ private void ensureAlive() { | |
| @NonNull | ||
| Context getContext(); | ||
|
|
||
| /** Returns true if the delegate should retrieve the initial route from the {@link Intent}. */ | ||
| @Nullable | ||
| boolean shouldHandleDeeplinking(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note the classes on flutteractivity/fragment are the user facing docs. This interface is package private. |
||
|
|
||
| /** | ||
| * Returns the host {@link Activity} or the {@code Activity} that is currently attached to the | ||
| * host {@code Fragment}. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I refactored this bundle getter out so i can mock metadata during the test.