diff --git a/packages/android_intent/CHANGELOG.md b/packages/android_intent/CHANGELOG.md index ea1b8f85c626..7a818f38548a 100644 --- a/packages/android_intent/CHANGELOG.md +++ b/packages/android_intent/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.3 + +* Added "flags" option to call intent.addFlags(int) in native. + ## 0.3.2 * Added "action_location_source_settings" action to start Location Settings Activity. diff --git a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/AndroidIntentPlugin.java b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/AndroidIntentPlugin.java index 9c924d6fa524..a66116cdceeb 100644 --- a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/AndroidIntentPlugin.java +++ b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/AndroidIntentPlugin.java @@ -126,6 +126,9 @@ public void onMethodCall(MethodCall call, Result result) { if (mRegistrar.activity() == null) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } + if (call.argument("flag") != null) { + intent.addFlags((Integer) call.argument("flags")); + } if (call.argument("category") != null) { intent.addCategory((String) call.argument("category")); } diff --git a/packages/android_intent/example/lib/main.dart b/packages/android_intent/example/lib/main.dart index c94ffe50aef5..becf3d6e1e75 100644 --- a/packages/android_intent/example/lib/main.dart +++ b/packages/android_intent/example/lib/main.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:android_intent/android_intent.dart'; +import 'package:android_intent/flag.dart'; import 'package:flutter/material.dart'; import 'package:platform/platform.dart'; @@ -117,6 +118,15 @@ class ExplicitIntentsWidget extends StatelessWidget { intent.launch(); } + void _startActivityInNewTask() { + final AndroidIntent intent = AndroidIntent( + action: 'action_view', + data: Uri.encodeFull('https://flutter.io'), + flags: [Flag.FLAG_ACTIVITY_NEW_TASK], + ); + intent.launch(); + } + void _testExplicitIntentFallback() { final AndroidIntent intent = AndroidIntent( action: 'action_view', @@ -162,6 +172,10 @@ class ExplicitIntentsWidget extends StatelessWidget { child: const Text('Tap here to open link in Google Chrome.'), onPressed: _openLinkInGoogleChrome, ), + RaisedButton( + child: const Text('Tap here to start activity in new task.'), + onPressed: _startActivityInNewTask, + ), RaisedButton( child: const Text( 'Tap here to test explicit intent fallback to implicit.'), diff --git a/packages/android_intent/lib/android_intent.dart b/packages/android_intent/lib/android_intent.dart index 5e00b30d7d43..9c036cf98e15 100644 --- a/packages/android_intent/lib/android_intent.dart +++ b/packages/android_intent/lib/android_intent.dart @@ -14,6 +14,7 @@ const String kChannelName = 'plugins.flutter.io/android_intent'; class AndroidIntent { /// Builds an Android intent with the following parameters /// [action] refers to the action parameter of the intent. + /// [flags] is the list of int that will be converted to native flags. /// [category] refers to the category of the intent, can be null. /// [data] refers to the string format of the URI that will be passed to /// intent. @@ -24,6 +25,7 @@ class AndroidIntent { /// If not null, then [package] but also be provided. const AndroidIntent({ @required this.action, + this.flags, this.category, this.data, this.arguments, @@ -34,7 +36,22 @@ class AndroidIntent { _channel = const MethodChannel(kChannelName), _platform = platform ?? const LocalPlatform(); + @visibleForTesting + AndroidIntent.private({ + @required this.action, + @required Platform platform, + @required MethodChannel channel, + this.flags, + this.category, + this.data, + this.arguments, + this.package, + this.componentName, + }) : _channel = channel, + _platform = platform; + final String action; + final List flags; final String category; final String data; final Map arguments; @@ -43,13 +60,34 @@ class AndroidIntent { final MethodChannel _channel; final Platform _platform; + bool _isPowerOfTwo(int x) { + /* First x in the below expression is for the case when x is 0 */ + return x != 0 && ((x & (x - 1)) == 0); + } + + @visibleForTesting + int convertFlags(List flags) { + int finalValue = 0; + for (int i = 0; i < flags.length; i++) { + if (!_isPowerOfTwo(flags[i])) { + throw ArgumentError.value(flags[i], 'flag\'s value must be power of 2'); + } + finalValue |= flags[i]; + } + return finalValue; + } + /// Launch the intent. /// - /// This works only on Android platforms. Please guard the call so that your - /// iOS app does not crash. Checked mode will throw an assert exception. + /// This works only on Android platforms. Future launch() async { - assert(_platform.isAndroid); + if (!_platform.isAndroid) { + return; + } final Map args = {'action': action}; + if (flags != null) { + args['flags'] = convertFlags(flags); + } if (category != null) { args['category'] = category; } diff --git a/packages/android_intent/lib/flag.dart b/packages/android_intent/lib/flag.dart new file mode 100644 index 000000000000..b4e6ed100146 --- /dev/null +++ b/packages/android_intent/lib/flag.dart @@ -0,0 +1,37 @@ +// flag values from https://developer.android.com/reference/android/content/Intent.html +class Flag { + static const int FLAG_ACTIVITY_BROUGHT_TO_FRONT = 4194304; + static const int FLAG_ACTIVITY_CLEAR_TASK = 32768; + static const int FLAG_ACTIVITY_CLEAR_TOP = 67108864; + static const int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 524288; + static const int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 8388608; + static const int FLAG_ACTIVITY_FORWARD_RESULT = 33554432; + static const int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 1048576; + static const int FLAG_ACTIVITY_LAUNCH_ADJACENT = 4096; + static const int FLAG_ACTIVITY_MATCH_EXTERNAL = 2048; + static const int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; + static const int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; + static const int FLAG_ACTIVITY_NEW_TASK = 268435456; + static const int FLAG_ACTIVITY_NO_ANIMATION = 65536; + static const int FLAG_ACTIVITY_NO_HISTORY = 1073741824; + static const int FLAG_ACTIVITY_NO_USER_ACTION = 262144; + static const int FLAG_ACTIVITY_PREVIOUS_IS_TOP = 16777216; + static const int FLAG_ACTIVITY_REORDER_TO_FRONT = 131072; + static const int FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 2097152; + static const int FLAG_ACTIVITY_RETAIN_IN_RECENTS = 8192; + static const int FLAG_ACTIVITY_SINGLE_TOP = 536870912; + static const int FLAG_ACTIVITY_TASK_ON_HOME = 16384; + static const int FLAG_DEBUG_LOG_RESOLUTION = 8; + static const int FLAG_EXCLUDE_STOPPED_PACKAGES = 16; + static const int FLAG_FROM_BACKGROUND = 4; + static const int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 64; + static const int FLAG_GRANT_PREFIX_URI_PERMISSION = 128; + static const int FLAG_GRANT_READ_URI_PERMISSION = 1; + static const int FLAG_GRANT_WRITE_URI_PERMISSION = 2; + static const int FLAG_INCLUDE_STOPPED_PACKAGES = 32; + static const int FLAG_RECEIVER_FOREGROUND = 268435456; + static const int FLAG_RECEIVER_NO_ABORT = 134217728; + static const int FLAG_RECEIVER_REGISTERED_ONLY = 1073741824; + static const int FLAG_RECEIVER_REPLACE_PENDING = 536870912; + static const int FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS = 2097152; +} diff --git a/packages/android_intent/pubspec.yaml b/packages/android_intent/pubspec.yaml index cf205a693daf..11cbc319bbf0 100644 --- a/packages/android_intent/pubspec.yaml +++ b/packages/android_intent/pubspec.yaml @@ -2,7 +2,7 @@ name: android_intent description: Flutter plugin for launching Android Intents. Not supported on iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/android_intent -version: 0.3.2 +version: 0.3.3 flutter: plugin: @@ -15,7 +15,11 @@ dependencies: sdk: flutter platform: ^2.0.0 meta: ^1.0.5 - +dev_dependencies: + test: ^1.3.0 + mockito: ^3.0.0 + flutter_test: + sdk: flutter environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" flutter: ">=1.2.0 <2.0.0" diff --git a/packages/android_intent/test/android_intent_test.dart b/packages/android_intent/test/android_intent_test.dart new file mode 100644 index 000000000000..b13438bf7469 --- /dev/null +++ b/packages/android_intent/test/android_intent_test.dart @@ -0,0 +1,85 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:android_intent/flag.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:android_intent/android_intent.dart'; +import 'package:mockito/mockito.dart'; +import 'package:platform/platform.dart'; + +void main() { + AndroidIntent androidIntent; + MockMethodChannel mockChannel; + setUp(() { + mockChannel = MockMethodChannel(); + }); + group('AndroidIntent', () { + test('pass right params', () async { + androidIntent = AndroidIntent.private( + action: 'action_view', + data: Uri.encodeFull('https://flutter.io'), + flags: [Flag.FLAG_ACTIVITY_NEW_TASK], + channel: mockChannel, + platform: FakePlatform(operatingSystem: 'android')); + androidIntent.launch(); + verify(mockChannel.invokeMethod('launch', { + 'action': 'action_view', + 'data': Uri.encodeFull('https://flutter.io'), + 'flags': androidIntent.convertFlags([Flag.FLAG_ACTIVITY_NEW_TASK]), + })); + }); + test('pass null value to action param', () async { + androidIntent = AndroidIntent.private( + action: null, + channel: mockChannel, + platform: FakePlatform(operatingSystem: 'android')); + androidIntent.launch(); + verify(mockChannel.invokeMethod('launch', { + 'action': null, + })); + }); + + test('call in ios platform', () async { + androidIntent = AndroidIntent.private( + action: null, + channel: mockChannel, + platform: FakePlatform(operatingSystem: 'ios')); + androidIntent.launch(); + verifyZeroInteractions(mockChannel); + }); + }); + group('convertFlags ', () { + androidIntent = const AndroidIntent( + action: 'action_view', + ); + test('add filled flag list', () async { + final List flags = []; + flags.add(Flag.FLAG_ACTIVITY_NEW_TASK); + flags.add(Flag.FLAG_ACTIVITY_NEW_DOCUMENT); + expect( + androidIntent.convertFlags(flags), + 268959744, + ); + }); + test('add flags whose values are not power of 2', () async { + final List flags = []; + flags.add(100); + flags.add(10); + expect( + () => androidIntent.convertFlags(flags), + throwsArgumentError, + ); + }); + test('add empty flag list', () async { + final List flags = []; + expect( + androidIntent.convertFlags(flags), + 0, + ); + }); + }); +} + +class MockMethodChannel extends Mock implements MethodChannel {}