[android_intent] add flags option (#2000)

Added "flags" option to call intent.addFlags(int) in native.
diff --git a/packages/android_intent/CHANGELOG.md b/packages/android_intent/CHANGELOG.md
index ea1b8f8..7a818f3 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 9c924d6..a66116c 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 @@
     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 c94ffe5..becf3d6 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 @@
     intent.launch();
   }
 
+  void _startActivityInNewTask() {
+    final AndroidIntent intent = AndroidIntent(
+      action: 'action_view',
+      data: Uri.encodeFull('https://flutter.io'),
+      flags: <int>[Flag.FLAG_ACTIVITY_NEW_TASK],
+    );
+    intent.launch();
+  }
+
   void _testExplicitIntentFallback() {
     final AndroidIntent intent = AndroidIntent(
         action: 'action_view',
@@ -163,6 +173,10 @@
                 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.'),
                 onPressed: _testExplicitIntentFallback,
diff --git a/packages/android_intent/lib/android_intent.dart b/packages/android_intent/lib/android_intent.dart
index 5e00b30..9c036cf 100644
--- a/packages/android_intent/lib/android_intent.dart
+++ b/packages/android_intent/lib/android_intent.dart
@@ -14,6 +14,7 @@
 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 @@
   /// 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 @@
         _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<int> flags;
   final String category;
   final String data;
   final Map<String, dynamic> arguments;
@@ -43,13 +60,34 @@
   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<int> 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<void> launch() async {
-    assert(_platform.isAndroid);
+    if (!_platform.isAndroid) {
+      return;
+    }
     final Map<String, dynamic> args = <String, dynamic>{'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 0000000..b4e6ed1
--- /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 cf205a6..11cbc31 100644
--- a/packages/android_intent/pubspec.yaml
+++ b/packages/android_intent/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Flutter plugin for launching Android Intents. Not supported on iOS.
 author: Flutter Team <flutter-dev@googlegroups.com>
 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 @@
     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 0000000..b13438b
--- /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: <int>[Flag.FLAG_ACTIVITY_NEW_TASK],
+          channel: mockChannel,
+          platform: FakePlatform(operatingSystem: 'android'));
+      androidIntent.launch();
+      verify(mockChannel.invokeMethod<void>('launch', <String, Object>{
+        'action': 'action_view',
+        'data': Uri.encodeFull('https://flutter.io'),
+        'flags': androidIntent.convertFlags(<int>[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<void>('launch', <String, Object>{
+        '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<int> flags = <int>[];
+      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<int> flags = <int>[];
+      flags.add(100);
+      flags.add(10);
+      expect(
+        () => androidIntent.convertFlags(flags),
+        throwsArgumentError,
+      );
+    });
+    test('add empty flag list', () async {
+      final List<int> flags = <int>[];
+      expect(
+        androidIntent.convertFlags(flags),
+        0,
+      );
+    });
+  });
+}
+
+class MockMethodChannel extends Mock implements MethodChannel {}