Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit c03166b

Browse files
authored
[android_intent] Adds canResolveActivity method (#2598)
* Adds canResolveActivity method
1 parent 92e4145 commit c03166b

File tree

8 files changed

+219
-70
lines changed

8 files changed

+219
-70
lines changed

packages/android_intent/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.3.7
2+
3+
* Add a `Future<bool> canResolveActivity` method to the AndroidIntent class. It
4+
can be used to determine whether a device supports a particular intent or has
5+
an app installed that can resolve it. It is based on PackageManager
6+
[resolveActivity](https://developer.android.com/reference/android/content/pm/PackageManager#resolveActivity(android.content.Intent,%20int)).
7+
18
## 0.3.6+1
29

310
* Bump the minimum Flutter version to 1.12.13+hotfix.5.

packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/IntentSender.java

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import android.content.ComponentName;
55
import android.content.Context;
66
import android.content.Intent;
7+
import android.content.pm.PackageManager;
78
import android.net.Uri;
89
import android.os.Bundle;
910
import android.text.TextUtils;
@@ -41,8 +42,61 @@ public IntentSender(@Nullable Activity activity, @Nullable Context applicationCo
4142
* <p>This uses {@code activity} to start the intent whenever it's not null. Otherwise it falls
4243
* back to {@code applicationContext} and adds {@link Intent#FLAG_ACTIVITY_NEW_TASK} to the intent
4344
* before launching it.
45+
*/
46+
void send(Intent intent) {
47+
if (applicationContext == null) {
48+
Log.wtf(TAG, "Trying to send an intent before the applicationContext was initialized.");
49+
return;
50+
}
51+
52+
Log.v(TAG, "Sending intent " + intent);
53+
54+
if (activity != null) {
55+
activity.startActivity(intent);
56+
} else {
57+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
58+
applicationContext.startActivity(intent);
59+
}
60+
}
61+
62+
/**
63+
* Verifies the given intent and returns whether the application context class can resolve it.
64+
*
65+
* <p>This will fail to create and send the intent if {@code applicationContext} hasn't been set *
66+
* at the time of calling.
67+
*
68+
* <p>This currently only supports resolving activities.
69+
*
70+
* @param intent Fully built intent.
71+
* @see #buildIntent(String, Integer, String, Uri, Bundle, String, ComponentName, String)
72+
* @return Whether the package manager found {@link android.content.pm.ResolveInfo} using its
73+
* {@link PackageManager#resolveActivity(Intent, int)} method.
74+
*/
75+
boolean canResolveActivity(Intent intent) {
76+
if (applicationContext == null) {
77+
Log.wtf(TAG, "Trying to resolve an activity before the applicationContext was initialized.");
78+
return false;
79+
}
80+
81+
final PackageManager packageManager = applicationContext.getPackageManager();
82+
83+
return packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null;
84+
}
85+
86+
/** Caches the given {@code activity} to use for {@link #send}. */
87+
void setActivity(@Nullable Activity activity) {
88+
this.activity = activity;
89+
}
90+
91+
/** Caches the given {@code applicationContext} to use for {@link #send}. */
92+
void setApplicationContext(@Nullable Context applicationContext) {
93+
this.applicationContext = applicationContext;
94+
}
95+
96+
/**
97+
* Constructs a new intent with the data specified.
4498
*
45-
* @param action the Intent action, such as {@code ACTION_VIEW} if non-null.
99+
* @param action the Intent action, such as {@code ACTION_VIEW}.
46100
* @param flags forwarded to {@link Intent#addFlags(int)} if non-null.
47101
* @param category forwarded to {@link Intent#addCategory(String)} if non-null.
48102
* @param data forwarded to {@link Intent#setData(Uri)} if non-null and 'type' parameter is null.
@@ -55,8 +109,9 @@ public IntentSender(@Nullable Activity activity, @Nullable Context applicationCo
55109
* @param type forwarded to {@link Intent#setType(String)} if non-null and 'data' parameter is
56110
* null. If both 'data' and 'type' is non-null they're forwarded to {@link
57111
* Intent#setDataAndType(Uri, String)}
112+
* @return Fully built intent.
58113
*/
59-
void send(
114+
Intent buildIntent(
60115
@Nullable String action,
61116
@Nullable Integer flags,
62117
@Nullable String category,
@@ -66,8 +121,8 @@ void send(
66121
@Nullable ComponentName componentName,
67122
@Nullable String type) {
68123
if (applicationContext == null) {
69-
Log.wtf(TAG, "Trying to send an intent before the applicationContext was initialized.");
70-
return;
124+
Log.wtf(TAG, "Trying to build an intent before the applicationContext was initialized.");
125+
return null;
71126
}
72127

73128
Intent intent = new Intent();
@@ -104,22 +159,6 @@ void send(
104159
}
105160
}
106161

107-
Log.v(TAG, "Sending intent " + intent);
108-
if (activity != null) {
109-
activity.startActivity(intent);
110-
} else {
111-
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
112-
applicationContext.startActivity(intent);
113-
}
114-
}
115-
116-
/** Caches the given {@code activity} to use for {@link #send}. */
117-
void setActivity(@Nullable Activity activity) {
118-
this.activity = activity;
119-
}
120-
121-
/** Caches the given {@code applicationContext} to use for {@link #send}. */
122-
void setApplicationContext(@Nullable Context applicationContext) {
123-
this.applicationContext = applicationContext;
162+
return intent;
124163
}
125164
}

packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/MethodCallHandlerImpl.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,19 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
8585
: null;
8686
String type = call.argument("type");
8787

88-
sender.send(action, flags, category, data, arguments, packageName, componentName, type);
88+
Intent intent =
89+
sender.buildIntent(
90+
action, flags, category, data, arguments, packageName, componentName, type);
8991

90-
result.success(null);
92+
if ("launch".equalsIgnoreCase(call.method)) {
93+
sender.send(intent);
94+
95+
result.success(null);
96+
} else if ("canResolveActivity".equalsIgnoreCase(call.method)) {
97+
result.success(sender.canResolveActivity(intent));
98+
} else {
99+
result.notImplemented();
100+
}
91101
}
92102

93103
private static String convertAction(String action) {

packages/android_intent/example/test_driver/android_intent_e2e.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,21 @@ void main() {
4949
e.message.contains('No Activity found to handle Intent');
5050
}));
5151
}, skip: !Platform.isAndroid);
52+
53+
testWidgets('#canResolveActivity returns true when example Activity is found',
54+
(WidgetTester tester) async {
55+
AndroidIntent intent = AndroidIntent(
56+
action: 'action_view',
57+
package: 'io.flutter.plugins.androidintentexample',
58+
componentName: 'io.flutter.embedding.android.FlutterActivity',
59+
);
60+
await expectLater(() async => await intent.canResolveActivity(), isFalse);
61+
}, skip: !Platform.isAndroid);
62+
63+
testWidgets('#canResolveActivity returns false when no Activity is found',
64+
(WidgetTester tester) async {
65+
const AndroidIntent intent =
66+
AndroidIntent(action: 'LAUNCH', package: 'foobar');
67+
await expectLater(() async => await intent.canResolveActivity(), isFalse);
68+
}, skip: !Platform.isAndroid);
5269
}

packages/android_intent/ios/android_intent.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44
Pod::Spec.new do |s|
55
s.name = 'android_intent'
6-
s.version = '0.0.1'
6+
s.version = '0.3.7'
77
s.summary = 'A new flutter plugin project.'
88
s.description = <<-DESC
99
A new flutter plugin project.

packages/android_intent/lib/android_intent.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,20 @@ class AndroidIntent {
138138
await _channel.invokeMethod<void>('launch', _buildArguments());
139139
}
140140

141+
/// Check whether the intent can be resolved to an activity.
142+
///
143+
/// This works only on Android platforms.
144+
Future<bool> canResolveActivity() async {
145+
if (!_platform.isAndroid) {
146+
return false;
147+
}
148+
149+
return await _channel.invokeMethod<bool>(
150+
'canResolveActivity',
151+
_buildArguments(),
152+
);
153+
}
154+
141155
/// Constructs the map of arguments which is passed to the plugin.
142156
Map<String, dynamic> _buildArguments() {
143157
return {

packages/android_intent/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: android_intent
22
description: Flutter plugin for launching Android Intents. Not supported on iOS.
33
homepage: https://github.com/flutter/plugins/tree/master/packages/android_intent
4-
version: 0.3.6+1
4+
version: 0.3.7
55

66
flutter:
77
plugin:

packages/android_intent/test/android_intent_test.dart

Lines changed: 107 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,6 @@ void main() {
1717
});
1818

1919
group('AndroidIntent', () {
20-
test('pass right params', () async {
21-
androidIntent = AndroidIntent.private(
22-
action: 'action_view',
23-
data: Uri.encodeFull('https://flutter.io'),
24-
flags: <int>[Flag.FLAG_ACTIVITY_NEW_TASK],
25-
channel: mockChannel,
26-
platform: FakePlatform(operatingSystem: 'android'),
27-
type: 'video/*');
28-
await androidIntent.launch();
29-
verify(mockChannel.invokeMethod<void>('launch', <String, Object>{
30-
'action': 'action_view',
31-
'data': Uri.encodeFull('https://flutter.io'),
32-
'flags': androidIntent.convertFlags(<int>[Flag.FLAG_ACTIVITY_NEW_TASK]),
33-
'type': 'video/*',
34-
}));
35-
});
36-
3720
test('raises error if neither an action nor a component is provided', () {
3821
try {
3922
androidIntent = AndroidIntent(data: 'https://flutter.io');
@@ -44,39 +27,118 @@ void main() {
4427
fail('should raise an AssertionError');
4528
}
4629
});
47-
test('can send Intent with an action and no component', () async {
48-
androidIntent = AndroidIntent.private(
49-
action: 'action_view',
50-
channel: mockChannel,
51-
platform: FakePlatform(operatingSystem: 'android'),
52-
);
53-
await androidIntent.launch();
54-
verify(mockChannel.invokeMethod<void>('launch', <String, Object>{
55-
'action': 'action_view',
56-
}));
57-
});
5830

59-
test('can send Intent with a component and no action', () async {
60-
androidIntent = AndroidIntent.private(
61-
package: 'packageName',
62-
componentName: 'componentName',
63-
channel: mockChannel,
64-
platform: FakePlatform(operatingSystem: 'android'),
65-
);
66-
await androidIntent.launch();
67-
verify(mockChannel.invokeMethod<void>('launch', <String, Object>{
68-
'package': 'packageName',
69-
'componentName': 'componentName',
70-
}));
31+
group('launch', () {
32+
test('pass right params', () async {
33+
androidIntent = AndroidIntent.private(
34+
action: 'action_view',
35+
data: Uri.encodeFull('https://flutter.io'),
36+
flags: <int>[Flag.FLAG_ACTIVITY_NEW_TASK],
37+
channel: mockChannel,
38+
platform: FakePlatform(operatingSystem: 'android'),
39+
type: 'video/*');
40+
await androidIntent.launch();
41+
verify(mockChannel.invokeMethod<void>('launch', <String, Object>{
42+
'action': 'action_view',
43+
'data': Uri.encodeFull('https://flutter.io'),
44+
'flags':
45+
androidIntent.convertFlags(<int>[Flag.FLAG_ACTIVITY_NEW_TASK]),
46+
'type': 'video/*',
47+
}));
48+
});
49+
50+
test('can send Intent with an action and no component', () async {
51+
androidIntent = AndroidIntent.private(
52+
action: 'action_view',
53+
channel: mockChannel,
54+
platform: FakePlatform(operatingSystem: 'android'),
55+
);
56+
await androidIntent.launch();
57+
verify(mockChannel.invokeMethod<void>('launch', <String, Object>{
58+
'action': 'action_view',
59+
}));
60+
});
61+
62+
test('can send Intent with a component and no action', () async {
63+
androidIntent = AndroidIntent.private(
64+
package: 'packageName',
65+
componentName: 'componentName',
66+
channel: mockChannel,
67+
platform: FakePlatform(operatingSystem: 'android'),
68+
);
69+
await androidIntent.launch();
70+
verify(mockChannel.invokeMethod<void>('launch', <String, Object>{
71+
'package': 'packageName',
72+
'componentName': 'componentName',
73+
}));
74+
});
75+
76+
test('call in ios platform', () async {
77+
androidIntent = AndroidIntent.private(
78+
action: 'action_view',
79+
channel: mockChannel,
80+
platform: FakePlatform(operatingSystem: 'ios'));
81+
await androidIntent.launch();
82+
verifyZeroInteractions(mockChannel);
83+
});
7184
});
7285

73-
test('call in ios platform', () async {
74-
androidIntent = AndroidIntent.private(
86+
group('canResolveActivity', () {
87+
test('pass right params', () async {
88+
androidIntent = AndroidIntent.private(
89+
action: 'action_view',
90+
data: Uri.encodeFull('https://flutter.io'),
91+
flags: <int>[Flag.FLAG_ACTIVITY_NEW_TASK],
92+
channel: mockChannel,
93+
platform: FakePlatform(operatingSystem: 'android'),
94+
type: 'video/*');
95+
await androidIntent.canResolveActivity();
96+
verify(mockChannel
97+
.invokeMethod<void>('canResolveActivity', <String, Object>{
98+
'action': 'action_view',
99+
'data': Uri.encodeFull('https://flutter.io'),
100+
'flags':
101+
androidIntent.convertFlags(<int>[Flag.FLAG_ACTIVITY_NEW_TASK]),
102+
'type': 'video/*',
103+
}));
104+
});
105+
106+
test('can send Intent with an action and no component', () async {
107+
androidIntent = AndroidIntent.private(
75108
action: 'action_view',
76109
channel: mockChannel,
77-
platform: FakePlatform(operatingSystem: 'ios'));
78-
await androidIntent.launch();
79-
verifyZeroInteractions(mockChannel);
110+
platform: FakePlatform(operatingSystem: 'android'),
111+
);
112+
await androidIntent.canResolveActivity();
113+
verify(mockChannel
114+
.invokeMethod<void>('canResolveActivity', <String, Object>{
115+
'action': 'action_view',
116+
}));
117+
});
118+
119+
test('can send Intent with a component and no action', () async {
120+
androidIntent = AndroidIntent.private(
121+
package: 'packageName',
122+
componentName: 'componentName',
123+
channel: mockChannel,
124+
platform: FakePlatform(operatingSystem: 'android'),
125+
);
126+
await androidIntent.canResolveActivity();
127+
verify(mockChannel
128+
.invokeMethod<void>('canResolveActivity', <String, Object>{
129+
'package': 'packageName',
130+
'componentName': 'componentName',
131+
}));
132+
});
133+
134+
test('call in ios platform', () async {
135+
androidIntent = AndroidIntent.private(
136+
action: 'action_view',
137+
channel: mockChannel,
138+
platform: FakePlatform(operatingSystem: 'ios'));
139+
await androidIntent.canResolveActivity();
140+
verifyZeroInteractions(mockChannel);
141+
});
80142
});
81143
});
82144

0 commit comments

Comments
 (0)