From efbc9d61d87adbeb6a35a054cda35acdbfe48f1a Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Mon, 28 Dec 2020 09:45:04 +0100 Subject: [PATCH 1/6] implement launchChooser --- .../plus/androidintent/IntentSender.java | 72 ++++++++++++------- .../androidintent/MethodCallHandlerImpl.java | 4 ++ .../android_intent_plus/example/lib/main.dart | 22 +++++- .../lib/android_intent.dart | 16 +++++ 4 files changed, 84 insertions(+), 30 deletions(-) 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 1b335c6007..1555e1feaf 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 @@ -9,14 +9,19 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.Log; + import androidx.annotation.Nullable; -/** Forms and launches intents. */ +/** + * Forms and launches intents. + */ public final class IntentSender { private static final String TAG = "IntentSender"; - @Nullable private Activity activity; - @Nullable private Context applicationContext; + @Nullable + private Activity activity; + @Nullable + private Context applicationContext; /** * Caches the given {@code activity} and {@code applicationContext} to use for sending intents @@ -59,6 +64,15 @@ void send(Intent intent) { } } + + /** + * Like with {@code send}, creates and launches an intent with the given params, but wraps + * the {@code Intent} with {@code Intent.createChooser}. + */ + public void launchChooser(Intent intent, String title) { + send(Intent.createChooser(intent, title)); + } + /** * Verifies the given intent and returns whether the application context class can resolve it. * @@ -68,9 +82,9 @@ void send(Intent intent) { *

This currently only supports resolving activities. * * @param intent Fully built intent. - * @see #buildIntent(String, Integer, String, Uri, Bundle, String, ComponentName, String) * @return Whether the package manager found {@link android.content.pm.ResolveInfo} using its - * {@link PackageManager#resolveActivity(Intent, int)} method. + * {@link PackageManager#resolveActivity(Intent, int)} method. + * @see #buildIntent(String, Integer, String, Uri, Bundle, String, ComponentName, String) */ boolean canResolveActivity(Intent intent) { if (applicationContext == null) { @@ -83,12 +97,16 @@ boolean canResolveActivity(Intent intent) { return packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null; } - /** Caches the given {@code activity} to use for {@link #send}. */ + /** + * Caches the given {@code activity} to use for {@link #send}. + */ void setActivity(@Nullable Activity activity) { this.activity = activity; } - /** Caches the given {@code applicationContext} to use for {@link #send}. */ + /** + * Caches the given {@code applicationContext} to use for {@link #send}. + */ void setApplicationContext(@Nullable Context applicationContext) { this.applicationContext = applicationContext; } @@ -96,30 +114,30 @@ void setApplicationContext(@Nullable Context applicationContext) { /** * Constructs a new intent with the data specified. * - * @param action the Intent action, such as {@code ACTION_VIEW}. - * @param flags forwarded to {@link Intent#addFlags(int)} if non-null. - * @param category forwarded to {@link Intent#addCategory(String)} if non-null. - * @param data forwarded to {@link Intent#setData(Uri)} if non-null and 'type' parameter is null. - * If both 'data' and 'type' is non-null they're forwarded to {@link - * Intent#setDataAndType(Uri, String)} - * @param arguments forwarded to {@link Intent#putExtras(Bundle)} if non-null. - * @param packageName forwarded to {@link Intent#setPackage(String)} if non-null. This is forced - * to null if it can't be resolved. + * @param action the Intent action, such as {@code ACTION_VIEW}. + * @param flags forwarded to {@link Intent#addFlags(int)} if non-null. + * @param category forwarded to {@link Intent#addCategory(String)} if non-null. + * @param data forwarded to {@link Intent#setData(Uri)} if non-null and 'type' parameter is null. + * If both 'data' and 'type' is non-null they're forwarded to {@link + * Intent#setDataAndType(Uri, String)} + * @param arguments forwarded to {@link Intent#putExtras(Bundle)} if non-null. + * @param packageName forwarded to {@link Intent#setPackage(String)} if non-null. This is forced + * to null if it can't be resolved. * @param componentName forwarded to {@link Intent#setComponent(ComponentName)} if non-null. - * @param type forwarded to {@link Intent#setType(String)} if non-null and 'data' parameter is - * null. If both 'data' and 'type' is non-null they're forwarded to {@link - * Intent#setDataAndType(Uri, String)} + * @param type forwarded to {@link Intent#setType(String)} if non-null and 'data' parameter is + * null. If both 'data' and 'type' is non-null they're forwarded to {@link + * Intent#setDataAndType(Uri, String)} * @return Fully built intent. */ Intent buildIntent( - @Nullable String action, - @Nullable Integer flags, - @Nullable String category, - @Nullable Uri data, - @Nullable Bundle arguments, - @Nullable String packageName, - @Nullable ComponentName componentName, - @Nullable String type) { + @Nullable String action, + @Nullable Integer flags, + @Nullable String category, + @Nullable Uri data, + @Nullable Bundle arguments, + @Nullable String packageName, + @Nullable ComponentName componentName, + @Nullable String type) { if (applicationContext == null) { Log.wtf(TAG, "Trying to build an intent before the applicationContext was initialized."); return null; 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 c8343b4814..46ca514cd7 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 @@ -92,6 +92,10 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { if ("launch".equalsIgnoreCase(call.method)) { sender.send(intent); + result.success(null); + } else if ("launchChooser".equalsIgnoreCase(call.method)) { + String title = call.argument("chooserTitle"); + sender.launchChooser(intent, title); result.success(null); } else if ("canResolveActivity".equalsIgnoreCase(call.method)) { result.success(sender.canResolveActivity(intent)); diff --git a/packages/android_intent_plus/example/lib/main.dart b/packages/android_intent_plus/example/lib/main.dart index 472c434df8..9824f5af5b 100644 --- a/packages/android_intent_plus/example/lib/main.dart +++ b/packages/android_intent_plus/example/lib/main.dart @@ -65,8 +65,15 @@ class MyHomePage extends StatelessWidget { onPressed: _createAlarm, ), RaisedButton( - child: const Text('Tap here to test explicit intents.'), - onPressed: () => _openExplicitIntentsView(context)), + child: const Text( + 'Tap here to launch Intent with Chooser', + ), + onPressed: _openChooser, + ), + RaisedButton( + child: const Text('Tap here to test explicit intents.'), + onPressed: () => _openExplicitIntentsView(context), + ), ], ), ); @@ -80,6 +87,15 @@ class MyHomePage extends StatelessWidget { body: Center(child: body), ); } + + void _openChooser() { + final intent = const AndroidIntent( + action: 'android.intent.action.SEND', + type: 'plain/text', + data: 'text example', + ); + intent.launchChooser('Chose an app'); + } } /// Launches intents to specific Android activities. @@ -204,7 +220,7 @@ class ExplicitIntentsWidget extends StatelessWidget { 'Tap here to open Application Details', ), onPressed: _openApplicationDetails, - ) + ), ], ), ), diff --git a/packages/android_intent_plus/lib/android_intent.dart b/packages/android_intent_plus/lib/android_intent.dart index 93f6a3a98c..b939f0e375 100644 --- a/packages/android_intent_plus/lib/android_intent.dart +++ b/packages/android_intent_plus/lib/android_intent.dart @@ -138,6 +138,22 @@ class AndroidIntent { await _channel.invokeMethod('launch', _buildArguments()); } + /// Launch the intent with 'createChooser(intent, title)'. + /// + /// This works only on Android platforms. + Future launchChooser(String title) async { + if (!_platform.isAndroid) { + return; + } + + final buildArguments = _buildArguments(); + buildArguments['chooserTitle'] = title; + await _channel.invokeMethod( + 'launchChooser', + buildArguments, + ); + } + /// Check whether the intent can be resolved to an activity. /// /// This works only on Android platforms. From c2e11868b004b9cb72fc6c19aa2cf4cbc934164d Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Mon, 28 Dec 2020 09:52:05 +0100 Subject: [PATCH 2/6] add e2e test --- .../example/test_driver/android_intent_plus_e2e.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/android_intent_plus/example/test_driver/android_intent_plus_e2e.dart b/packages/android_intent_plus/example/test_driver/android_intent_plus_e2e.dart index f234a169e5..b6d9aed169 100644 --- a/packages/android_intent_plus/example/test_driver/android_intent_plus_e2e.dart +++ b/packages/android_intent_plus/example/test_driver/android_intent_plus_e2e.dart @@ -24,7 +24,7 @@ void main() { (Widget widget) => widget is Text && widget.data.startsWith('Tap here'), ), - findsNWidgets(2), + findsNWidgets(3), ); } else { expect( @@ -49,6 +49,15 @@ void main() { })); }, skip: !Platform.isAndroid); + testWidgets('#launchChooser should not throw', (WidgetTester tester) async { + final intent = const AndroidIntent( + action: 'android.intent.action.SEND', + type: 'plain/text', + data: 'text example', + ); + await intent.launchChooser('title'); + }, skip: !Platform.isAndroid); + testWidgets('#canResolveActivity returns true when example Activity is found', (WidgetTester tester) async { final intent = AndroidIntent( From f408d3d6d09664bef0ecdb94d3d06da3e74697d3 Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Mon, 28 Dec 2020 09:59:44 +0100 Subject: [PATCH 3/6] add dart test --- .../test/android_intent_test.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/android_intent_plus/test/android_intent_test.dart b/packages/android_intent_plus/test/android_intent_test.dart index af96a9bc4a..a5403f9d40 100644 --- a/packages/android_intent_plus/test/android_intent_test.dart +++ b/packages/android_intent_plus/test/android_intent_test.dart @@ -140,6 +140,21 @@ void main() { verifyZeroInteractions(mockChannel); }); }); + + group('launchChooser', () { + test('pass title', () async { + androidIntent = AndroidIntent.private( + action: 'action_view', + channel: mockChannel, + platform: FakePlatform(operatingSystem: 'android'), + ); + await androidIntent.launchChooser('title'); + verify(mockChannel.invokeMethod('launchChooser', { + 'action': 'action_view', + 'chooserTitle': 'title', + })); + }); + }); }); group('convertFlags ', () { From e3f3730dee08e9aae12cb175c861a58f4fef91cf Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Mon, 28 Dec 2020 10:03:21 +0100 Subject: [PATCH 4/6] modify Changelog --- packages/android_intent_plus/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/android_intent_plus/CHANGELOG.md b/packages/android_intent_plus/CHANGELOG.md index c88a662927..b6348c264a 100644 --- a/packages/android_intent_plus/CHANGELOG.md +++ b/packages/android_intent_plus/CHANGELOG.md @@ -1,3 +1,7 @@ +## pre-release + +- Add launchChooser method, which uses Intent.createChooser internally. + ## 0.4.1 - Renamed Method Channel and changed Java package to avoid collision with android_intent From 8359a2ae696f07b9ab35e732b27e6923f619b5f6 Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Mon, 28 Dec 2020 10:07:05 +0100 Subject: [PATCH 5/6] flutter format --- packages/android_intent_plus/test/android_intent_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/android_intent_plus/test/android_intent_test.dart b/packages/android_intent_plus/test/android_intent_test.dart index a5403f9d40..d9c94e1a80 100644 --- a/packages/android_intent_plus/test/android_intent_test.dart +++ b/packages/android_intent_plus/test/android_intent_test.dart @@ -144,8 +144,8 @@ void main() { group('launchChooser', () { test('pass title', () async { androidIntent = AndroidIntent.private( - action: 'action_view', - channel: mockChannel, + action: 'action_view', + channel: mockChannel, platform: FakePlatform(operatingSystem: 'android'), ); await androidIntent.launchChooser('title'); From 7ce6e55308fa6b3372a13c60e5c96db290f23a9a Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Mon, 28 Dec 2020 10:09:42 +0100 Subject: [PATCH 6/6] java format --- .../plus/androidintent/IntentSender.java | 66 ++++++++----------- 1 file changed, 28 insertions(+), 38 deletions(-) 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 1555e1feaf..d66e8a0269 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 @@ -9,19 +9,14 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.Log; - import androidx.annotation.Nullable; -/** - * Forms and launches intents. - */ +/** Forms and launches intents. */ public final class IntentSender { private static final String TAG = "IntentSender"; - @Nullable - private Activity activity; - @Nullable - private Context applicationContext; + @Nullable private Activity activity; + @Nullable private Context applicationContext; /** * Caches the given {@code activity} and {@code applicationContext} to use for sending intents @@ -64,10 +59,9 @@ void send(Intent intent) { } } - /** - * Like with {@code send}, creates and launches an intent with the given params, but wraps - * the {@code Intent} with {@code Intent.createChooser}. + * Like with {@code send}, creates and launches an intent with the given params, but wraps the + * {@code Intent} with {@code Intent.createChooser}. */ public void launchChooser(Intent intent, String title) { send(Intent.createChooser(intent, title)); @@ -83,7 +77,7 @@ public void launchChooser(Intent intent, String title) { * * @param intent Fully built intent. * @return Whether the package manager found {@link android.content.pm.ResolveInfo} using its - * {@link PackageManager#resolveActivity(Intent, int)} method. + * {@link PackageManager#resolveActivity(Intent, int)} method. * @see #buildIntent(String, Integer, String, Uri, Bundle, String, ComponentName, String) */ boolean canResolveActivity(Intent intent) { @@ -97,16 +91,12 @@ boolean canResolveActivity(Intent intent) { return packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null; } - /** - * Caches the given {@code activity} to use for {@link #send}. - */ + /** Caches the given {@code activity} to use for {@link #send}. */ void setActivity(@Nullable Activity activity) { this.activity = activity; } - /** - * Caches the given {@code applicationContext} to use for {@link #send}. - */ + /** Caches the given {@code applicationContext} to use for {@link #send}. */ void setApplicationContext(@Nullable Context applicationContext) { this.applicationContext = applicationContext; } @@ -114,30 +104,30 @@ void setApplicationContext(@Nullable Context applicationContext) { /** * Constructs a new intent with the data specified. * - * @param action the Intent action, such as {@code ACTION_VIEW}. - * @param flags forwarded to {@link Intent#addFlags(int)} if non-null. - * @param category forwarded to {@link Intent#addCategory(String)} if non-null. - * @param data forwarded to {@link Intent#setData(Uri)} if non-null and 'type' parameter is null. - * If both 'data' and 'type' is non-null they're forwarded to {@link - * Intent#setDataAndType(Uri, String)} - * @param arguments forwarded to {@link Intent#putExtras(Bundle)} if non-null. - * @param packageName forwarded to {@link Intent#setPackage(String)} if non-null. This is forced - * to null if it can't be resolved. + * @param action the Intent action, such as {@code ACTION_VIEW}. + * @param flags forwarded to {@link Intent#addFlags(int)} if non-null. + * @param category forwarded to {@link Intent#addCategory(String)} if non-null. + * @param data forwarded to {@link Intent#setData(Uri)} if non-null and 'type' parameter is null. + * If both 'data' and 'type' is non-null they're forwarded to {@link + * Intent#setDataAndType(Uri, String)} + * @param arguments forwarded to {@link Intent#putExtras(Bundle)} if non-null. + * @param packageName forwarded to {@link Intent#setPackage(String)} if non-null. This is forced + * to null if it can't be resolved. * @param componentName forwarded to {@link Intent#setComponent(ComponentName)} if non-null. - * @param type forwarded to {@link Intent#setType(String)} if non-null and 'data' parameter is - * null. If both 'data' and 'type' is non-null they're forwarded to {@link - * Intent#setDataAndType(Uri, String)} + * @param type forwarded to {@link Intent#setType(String)} if non-null and 'data' parameter is + * null. If both 'data' and 'type' is non-null they're forwarded to {@link + * Intent#setDataAndType(Uri, String)} * @return Fully built intent. */ Intent buildIntent( - @Nullable String action, - @Nullable Integer flags, - @Nullable String category, - @Nullable Uri data, - @Nullable Bundle arguments, - @Nullable String packageName, - @Nullable ComponentName componentName, - @Nullable String type) { + @Nullable String action, + @Nullable Integer flags, + @Nullable String category, + @Nullable Uri data, + @Nullable Bundle arguments, + @Nullable String packageName, + @Nullable ComponentName componentName, + @Nullable String type) { if (applicationContext == null) { Log.wtf(TAG, "Trying to build an intent before the applicationContext was initialized."); return null;