diff --git a/packages/android_intent_plus/android/src/main/java/dev/fluttercommunity/plus/androidintent/IntentSender.java b/packages/android_intent_plus/android/src/main/java/dev/fluttercommunity/plus/androidintent/IntentSender.java index 2158b42024..ef2bd7a549 100644 --- a/packages/android_intent_plus/android/src/main/java/dev/fluttercommunity/plus/androidintent/IntentSender.java +++ b/packages/android_intent_plus/android/src/main/java/dev/fluttercommunity/plus/androidintent/IntentSender.java @@ -5,12 +5,15 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import androidx.annotation.Nullable; import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; /** Forms and launches intents. */ public final class IntentSender { @@ -102,6 +105,41 @@ boolean canResolveActivity(Intent intent) { return packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null; } + /** + * Get the default activity that will resolve the intent + * + *

This will fail to create and send the intent if {@code applicationContext} hasn't been set * + * at the time of calling. + * + *

This currently only supports resolving activities. + * + * @param intent Fully built intent. + * @return Whether the package manager found {@link android.content.pm.ResolveInfo} using its + * {@link PackageManager#resolveActivity(Intent, int)} method. + * @see #buildIntent(String, Integer, String, Uri, Bundle, String, ComponentName, String) + */ + @Nullable + Map getResolvedActivity(Intent intent) { + if (applicationContext == null) { + Log.wtf(TAG, "Trying to resolve an activity before the applicationContext was initialized."); + return null; + } + + final PackageManager packageManager = applicationContext.getPackageManager(); + ResolveInfo resolveInfo = + packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + + if (resolveInfo != null) { + Map resultMap = new HashMap<>(); + resultMap.put("activityName", resolveInfo.activityInfo.name); + resultMap.put("packageName", resolveInfo.activityInfo.packageName); + resultMap.put("appName", resolveInfo.loadLabel(packageManager)); + return resultMap; + } + + return null; + } + /** Caches the given {@code activity} to use for {@link #send}. */ void setActivity(@Nullable Activity activity) { this.activity = activity; diff --git a/packages/android_intent_plus/android/src/main/java/dev/fluttercommunity/plus/androidintent/MethodCallHandlerImpl.java b/packages/android_intent_plus/android/src/main/java/dev/fluttercommunity/plus/androidintent/MethodCallHandlerImpl.java index b56ff44845..0cd0a0765f 100644 --- a/packages/android_intent_plus/android/src/main/java/dev/fluttercommunity/plus/androidintent/MethodCallHandlerImpl.java +++ b/packages/android_intent_plus/android/src/main/java/dev/fluttercommunity/plus/androidintent/MethodCallHandlerImpl.java @@ -119,6 +119,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { result.success(null); } else if ("canResolveActivity".equalsIgnoreCase(call.method)) { result.success(sender.canResolveActivity(intent)); + } else if ("getResolvedActivity".equalsIgnoreCase(call.method)) { + result.success(sender.getResolvedActivity(intent)); } else { result.notImplemented(); } diff --git a/packages/android_intent_plus/example/android/app/src/main/AndroidManifest.xml b/packages/android_intent_plus/example/android/app/src/main/AndroidManifest.xml index e033f9db7b..d7a00781d5 100644 --- a/packages/android_intent_plus/example/android/app/src/main/AndroidManifest.xml +++ b/packages/android_intent_plus/example/android/app/src/main/AndroidManifest.xml @@ -10,6 +10,17 @@ --> + + + + + + + + + + + _getResolvedActivity(context), + child: const Text( + 'Tap here to get default resolved activity', + ), + ), + const SizedBox(height: 16), ElevatedButton( onPressed: _openGmail, child: const Text( diff --git a/packages/android_intent_plus/lib/android_intent.dart b/packages/android_intent_plus/lib/android_intent.dart index b680b962af..4764a55b10 100644 --- a/packages/android_intent_plus/lib/android_intent.dart +++ b/packages/android_intent_plus/lib/android_intent.dart @@ -207,6 +207,33 @@ class AndroidIntent { ); } + /// Get the default activity that will resolve the intent + /// + /// This works only on Android platforms. + /// + /// Note: ensure the calling app's AndroidManifest contains queries that match the intent. + /// See: https://developer.android.com/guide/topics/manifest/queries-element + Future getResolvedActivity() async { + if (!_platform.isAndroid) { + return null; + } + + final result = await _channel.invokeMethod>( + 'getResolvedActivity', + _buildArguments(), + ); + + if (result != null) { + return ResolvedActivity( + appName: result["appName"] as String, + activityName: result["activityName"] as String, + packageName: result["packageName"] as String, + ); + } + + return null; + } + /// Constructs the map of arguments which is passed to the plugin. Map _buildArguments() { return { @@ -224,3 +251,28 @@ class AndroidIntent { }; } } + +class ResolvedActivity { + final String appName; + final String activityName; + final String packageName; + + ResolvedActivity({ + required this.appName, + required this.activityName, + required this.packageName, + }); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ResolvedActivity && + runtimeType == other.runtimeType && + appName == other.appName && + activityName == other.activityName && + packageName == other.packageName; + + @override + int get hashCode => + appName.hashCode ^ activityName.hashCode ^ packageName.hashCode; +} diff --git a/packages/android_intent_plus/test/android_intent_test.dart b/packages/android_intent_plus/test/android_intent_test.dart index 2ff4fd8e00..1d39129f2e 100644 --- a/packages/android_intent_plus/test/android_intent_test.dart +++ b/packages/android_intent_plus/test/android_intent_test.dart @@ -141,6 +141,86 @@ void main() { }); }); + group('getResolvedActivity', () { + test('pass right params', () async { + androidIntent = AndroidIntent.private( + action: 'action_view', + data: Uri.encodeFull('https://flutter.dev'), + flags: [Flag.FLAG_ACTIVITY_NEW_TASK], + channel: mockChannel, + platform: FakePlatform(operatingSystem: 'android'), + type: 'video/*'); + await androidIntent.getResolvedActivity(); + verify(mockChannel + .invokeMethod('getResolvedActivity', { + 'action': 'action_view', + 'data': Uri.encodeFull('https://flutter.dev'), + 'flags': + androidIntent.convertFlags([Flag.FLAG_ACTIVITY_NEW_TASK]), + 'type': 'video/*', + })); + }); + + test('returns a ResolvedActivity', () async { + androidIntent = AndroidIntent.private( + action: 'action_view', + data: Uri.encodeFull('https://flutter.dev'), + channel: mockChannel, + platform: FakePlatform(operatingSystem: 'android'), + ); + + when(mockChannel.invokeMethod("getResolvedActivity", any)) + .thenAnswer((_) async => { + "activityName": "activity name", + "appName": "App Name", + "packageName": "com.packagename", + }); + + final result = await androidIntent.getResolvedActivity(); + + expect(result?.activityName, equals("activity name")); + expect(result?.appName, equals("App Name")); + expect(result?.packageName, equals("com.packagename")); + }); + + test('can send Intent with an action and no component', () async { + androidIntent = AndroidIntent.private( + action: 'action_view', + channel: mockChannel, + platform: FakePlatform(operatingSystem: 'android'), + ); + await androidIntent.getResolvedActivity(); + verify(mockChannel + .invokeMethod('getResolvedActivity', { + 'action': 'action_view', + })); + }); + + test('can send Intent with a component and no action', () async { + androidIntent = AndroidIntent.private( + package: 'packageName', + componentName: 'componentName', + channel: mockChannel, + platform: FakePlatform(operatingSystem: 'android'), + ); + await androidIntent.getResolvedActivity(); + verify(mockChannel + .invokeMethod('getResolvedActivity', { + 'package': 'packageName', + 'componentName': 'componentName', + })); + }); + + test('call in ios platform', () async { + androidIntent = AndroidIntent.private( + action: 'action_view', + channel: mockChannel, + platform: FakePlatform(operatingSystem: 'ios')); + await androidIntent.getResolvedActivity(); + verifyZeroInteractions(mockChannel); + }); + }); + group('launchChooser', () { test('pass title', () async { androidIntent = AndroidIntent.private(