From 3d57dd3dd0886b1c6cb0ae702ff034a041b2ba44 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Wed, 4 Mar 2020 17:55:56 +0000 Subject: [PATCH 1/5] An intent takes an action, component or both, but at least one of them. --- .../io/flutter/plugins/androidintent/IntentSender.java | 9 ++++++--- .../plugins/androidintent/MethodCallHandlerImpl.java | 4 ++++ packages/android_intent/lib/android_intent.dart | 5 +++-- packages/android_intent/test/android_intent_test.dart | 8 ++++++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/IntentSender.java b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/IntentSender.java index aac9226b9465..5aa7919699da 100644 --- a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/IntentSender.java +++ b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/IntentSender.java @@ -42,7 +42,7 @@ public IntentSender(@Nullable Activity activity, @Nullable Context applicationCo * back to {@code applicationContext} and adds {@link Intent#FLAG_ACTIVITY_NEW_TASK} to the intent * before launching it. * - * @param action the Intent action, such as {@code ACTION_VIEW}. + * @param action the Intent action, such as {@code ACTION_VIEW} if non-null. * @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. @@ -57,7 +57,7 @@ public IntentSender(@Nullable Activity activity, @Nullable Context applicationCo * Intent#setDataAndType(Uri, String)} */ void send( - String action, + @Nullable String action, @Nullable Integer flags, @Nullable String category, @Nullable Uri data, @@ -70,8 +70,11 @@ void send( return; } - Intent intent = new Intent(action); + Intent intent = new Intent(); + if (action != null) { + intent.setAction(action); + } if (flags != null) { intent.addFlags(flags); } diff --git a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/MethodCallHandlerImpl.java b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/MethodCallHandlerImpl.java index a77d181015de..09846d5e51ef 100644 --- a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/MethodCallHandlerImpl.java +++ b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/MethodCallHandlerImpl.java @@ -91,6 +91,10 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { } private static String convertAction(String action) { + if (action == null) { + return null; + } + switch (action) { case "action_view": return Intent.ACTION_VIEW; diff --git a/packages/android_intent/lib/android_intent.dart b/packages/android_intent/lib/android_intent.dart index eb2f486408cc..b6939227557e 100644 --- a/packages/android_intent/lib/android_intent.dart +++ b/packages/android_intent/lib/android_intent.dart @@ -29,7 +29,7 @@ class AndroidIntent { /// If not null, then [package] but also be provided. /// [type] refers to the type of the intent, can be null. const AndroidIntent({ - @required this.action, + this.action, this.flags, this.category, this.data, @@ -38,7 +38,8 @@ class AndroidIntent { this.componentName, Platform platform, this.type, - }) : assert(action != null), + }) : assert(action != null || componentName != null, + 'action or component (or both) must be specified'), _channel = const MethodChannel(_kChannelName), _platform = platform ?? const LocalPlatform(); diff --git a/packages/android_intent/test/android_intent_test.dart b/packages/android_intent/test/android_intent_test.dart index e36a8b7827c9..7068881ee225 100644 --- a/packages/android_intent/test/android_intent_test.dart +++ b/packages/android_intent/test/android_intent_test.dart @@ -43,6 +43,14 @@ void main() { })); }); + test('asserts on missing action & component', () { + try { + AndroidIntent(data: 'https://flutter.io'); + } on AssertionError catch (e) { + expect(e.message, 'action or component (or both) must be specified'); + } + }); + test('call in ios platform', () async { androidIntent = AndroidIntent.private( action: null, From 36a5b28d218c75414b45296725fd91c467edd591 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Wed, 4 Mar 2020 18:49:14 +0000 Subject: [PATCH 2/5] Bumps version, adds changelog --- packages/android_intent/CHANGELOG.md | 5 +++++ packages/android_intent/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/android_intent/CHANGELOG.md b/packages/android_intent/CHANGELOG.md index 3d16c1422441..47f1917e3e8a 100644 --- a/packages/android_intent/CHANGELOG.md +++ b/packages/android_intent/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.3.6 + +* Marks the `action` parameter as optional +* Adds an assertion to ensure the intent receives an action, component or both. + ## 0.3.5+1 * Make the pedantic dev_dependency explicit. diff --git a/packages/android_intent/pubspec.yaml b/packages/android_intent/pubspec.yaml index c6e38bc0d93b..2faf45e915f6 100644 --- a/packages/android_intent/pubspec.yaml +++ b/packages/android_intent/pubspec.yaml @@ -1,7 +1,7 @@ name: android_intent description: Flutter plugin for launching Android Intents. Not supported on iOS. homepage: https://github.com/flutter/plugins/tree/master/packages/android_intent -version: 0.3.5+1 +version: 0.3.6 flutter: plugin: From 83a6ba451bcf5a493969ec72e511212b1953614b Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Wed, 4 Mar 2020 21:19:07 +0000 Subject: [PATCH 3/5] extend the tests and enforce the new assertion on the private constructor as well --- .../android_intent/lib/android_intent.dart | 11 +++-- .../test/android_intent_test.dart | 44 ++++++++++++++----- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/packages/android_intent/lib/android_intent.dart b/packages/android_intent/lib/android_intent.dart index b6939227557e..26e270a3a79b 100644 --- a/packages/android_intent/lib/android_intent.dart +++ b/packages/android_intent/lib/android_intent.dart @@ -47,9 +47,9 @@ class AndroidIntent { /// app code, it may break without warning. @visibleForTesting AndroidIntent.private({ - @required this.action, @required Platform platform, @required MethodChannel channel, + this.action, this.flags, this.category, this.data, @@ -57,7 +57,9 @@ class AndroidIntent { this.package, this.componentName, this.type, - }) : _channel = channel, + }) : assert(action != null || componentName != null, + 'action or component (or both) must be specified'), + _channel = channel, _platform = platform; /// This is the general verb that the intent should attempt to do. This @@ -132,7 +134,10 @@ class AndroidIntent { if (!_platform.isAndroid) { return; } - final Map args = {'action': action}; + final Map args = {}; + if (action != null) { + args['action'] = action; + } if (flags != null) { args['flags'] = convertFlags(flags); } diff --git a/packages/android_intent/test/android_intent_test.dart b/packages/android_intent/test/android_intent_test.dart index 7068881ee225..60cf3b58a507 100644 --- a/packages/android_intent/test/android_intent_test.dart +++ b/packages/android_intent/test/android_intent_test.dart @@ -15,6 +15,7 @@ void main() { setUp(() { mockChannel = MockMethodChannel(); }); + group('AndroidIntent', () { test('pass right params', () async { androidIntent = AndroidIntent.private( @@ -32,34 +33,53 @@ void main() { 'type': 'video/*', })); }); - test('pass null value to action param', () async { + + test('raises error if neither an action nor a component is provided', () { + try { + androidIntent = AndroidIntent(data: 'https://flutter.io'); + fail('should raise an AssertionError'); + } on AssertionError catch (e) { + expect(e.message, 'action or component (or both) must be specified'); + } catch (e) { + fail('should raise an AssertionError'); + } + }); + test('can send Intent with an action and no component', () async { androidIntent = AndroidIntent.private( - action: null, - channel: mockChannel, - platform: FakePlatform(operatingSystem: 'android')); + action: 'action_view', + channel: mockChannel, + platform: FakePlatform(operatingSystem: 'android'), + ); await androidIntent.launch(); verify(mockChannel.invokeMethod('launch', { - 'action': null, + 'action': 'action_view', })); }); - test('asserts on missing action & component', () { - try { - AndroidIntent(data: 'https://flutter.io'); - } on AssertionError catch (e) { - expect(e.message, 'action or component (or both) must be specified'); - } + 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.launch(); + verify(mockChannel.invokeMethod('launch', { + 'package': 'packageName', + 'componentName': 'componentName', + })); }); test('call in ios platform', () async { androidIntent = AndroidIntent.private( - action: null, + action: 'action_view', channel: mockChannel, platform: FakePlatform(operatingSystem: 'ios')); await androidIntent.launch(); verifyZeroInteractions(mockChannel); }); }); + group('convertFlags ', () { androidIntent = const AndroidIntent( action: 'action_view', From ace6530a8f9605894de36b98464563e22d407f4e Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Wed, 4 Mar 2020 21:22:52 +0000 Subject: [PATCH 4/5] Extends unit test to cover no-action, but component name case. --- .../MethodCallHandlerImplTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java b/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java index 4ce7ddc3718e..ab332b1daff7 100644 --- a/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java +++ b/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java @@ -213,6 +213,30 @@ public void onMethodCall_setsComponentName() { methodCallHandler.onMethodCall(new MethodCall("launch", args), result); + verify(result, times(1)).success(null); + Intent intent = shadowOf((Application) context).getNextStartedActivity(); + assertNotNull(intent); + assertNotNull(intent.getComponent()); + assertEquals("foo", intent.getAction()); + assertEquals(expectedComponent.getPackageName(), intent.getPackage()); + assertEquals(expectedComponent.flattenToString(), intent.getComponent().flattenToString()); + } + + @Test + public void onMethodCall_setsOnlyComponentName() { + sender.setApplicationContext(context); + Map args = new HashMap<>(); + ComponentName expectedComponent = + new ComponentName("io.flutter.plugins.androidintent", "MainActivity"); + args.put("package", expectedComponent.getPackageName()); + args.put("componentName", expectedComponent.getClassName()); + Result result = mock(Result.class); + ShadowPackageManager shadowPm = + shadowOf(ApplicationProvider.getApplicationContext().getPackageManager()); + shadowPm.addActivityIfNotPresent(expectedComponent); + + methodCallHandler.onMethodCall(new MethodCall("launch", args), result); + verify(result, times(1)).success(null); Intent intent = shadowOf((Application) context).getNextStartedActivity(); assertNotNull(intent); From e60802af240fbb0c0892f8f3f10b398e56bb35df Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Thu, 5 Mar 2020 10:14:37 +0000 Subject: [PATCH 5/5] Improves unit test even more by writing literal values in the code --- .../androidintent/MethodCallHandlerImplTest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java b/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java index ab332b1daff7..cf0a28e822d4 100644 --- a/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java +++ b/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java @@ -218,8 +218,9 @@ public void onMethodCall_setsComponentName() { assertNotNull(intent); assertNotNull(intent.getComponent()); assertEquals("foo", intent.getAction()); - assertEquals(expectedComponent.getPackageName(), intent.getPackage()); - assertEquals(expectedComponent.flattenToString(), intent.getComponent().flattenToString()); + assertEquals("io.flutter.plugins.androidintent", intent.getPackage()); + assertEquals( + "io.flutter.plugins.androidintent/MainActivity", intent.getComponent().flattenToString()); } @Test @@ -241,8 +242,9 @@ public void onMethodCall_setsOnlyComponentName() { Intent intent = shadowOf((Application) context).getNextStartedActivity(); assertNotNull(intent); assertNotNull(intent.getComponent()); - assertEquals(expectedComponent.getPackageName(), intent.getPackage()); - assertEquals(expectedComponent.flattenToString(), intent.getComponent().flattenToString()); + assertEquals("io.flutter.plugins.androidintent", intent.getPackage()); + assertEquals( + "io.flutter.plugins.androidintent/MainActivity", intent.getComponent().flattenToString()); } @Test