/
IntentModule.java
244 lines (216 loc) · 8.09 KB
/
IntentModule.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.modules.intent;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.provider.Settings;
import androidx.annotation.Nullable;
import com.facebook.fbreact.specs.NativeIntentAndroidSpec;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.module.annotations.ReactModule;
/** Intent module. Launch other activities or open URLs. */
@ReactModule(name = IntentModule.NAME)
public class IntentModule extends NativeIntentAndroidSpec {
public static final String NAME = "IntentAndroid";
public IntentModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return NAME;
}
/**
* Return the URL the activity was started with
*
* @param promise a promise which is resolved with the initial URL
*/
@Override
public void getInitialURL(Promise promise) {
try {
Activity currentActivity = getCurrentActivity();
String initialURL = null;
if (currentActivity != null) {
Intent intent = currentActivity.getIntent();
String action = intent.getAction();
Uri uri = intent.getData();
if (uri != null
&& (Intent.ACTION_VIEW.equals(action)
|| NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action))) {
initialURL = uri.toString();
}
}
promise.resolve(initialURL);
} catch (Exception e) {
promise.reject(
new JSApplicationIllegalArgumentException(
"Could not get the initial URL : " + e.getMessage()));
}
}
/**
* Starts a corresponding external activity for the given URL.
*
* <p>For example, if the URL is "https://www.facebook.com", the system browser will be opened, or
* the "choose application" dialog will be shown.
*
* @param url the URL to open
*/
@Override
public void openURL(String url, Promise promise) {
if (url == null || url.isEmpty()) {
promise.reject(new JSApplicationIllegalArgumentException("Invalid URL: " + url));
return;
}
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url).normalizeScheme());
sendOSIntent(intent, false);
promise.resolve(true);
} catch (Exception e) {
promise.reject(
new JSApplicationIllegalArgumentException(
"Could not open URL '" + url + "': " + e.getMessage()));
}
}
/**
* Determine whether or not an installed app can handle a given URL.
*
* @param url the URL to open
* @param promise a promise that is always resolved with a boolean argument
*/
@Override
public void canOpenURL(String url, Promise promise) {
if (url == null || url.isEmpty()) {
promise.reject(new JSApplicationIllegalArgumentException("Invalid URL: " + url));
return;
}
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
// We need Intent.FLAG_ACTIVITY_NEW_TASK since getReactApplicationContext() returns
// the ApplicationContext instead of the Activity context.
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
boolean canOpen =
intent.resolveActivity(getReactApplicationContext().getPackageManager()) != null;
promise.resolve(canOpen);
} catch (Exception e) {
promise.reject(
new JSApplicationIllegalArgumentException(
"Could not check if URL '" + url + "' can be opened: " + e.getMessage()));
}
}
/**
* Starts an external activity to open app's settings into Android Settings
*
* @param promise a promise which is resolved when the Settings is opened
*/
@Override
public void openSettings(Promise promise) {
try {
Intent intent = new Intent();
Activity currentActivity = getCurrentActivity();
String selfPackageName = getReactApplicationContext().getPackageName();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse("package:" + selfPackageName));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
currentActivity.startActivity(intent);
promise.resolve(true);
} catch (Exception e) {
promise.reject(
new JSApplicationIllegalArgumentException(
"Could not open the Settings: " + e.getMessage()));
}
}
/**
* Allows to send intents on Android
*
* <p>For example, you can open the Notification Category screen for a specific application
* passing action = 'android.settings.CHANNEL_NOTIFICATION_SETTINGS' and extras = [ {
* 'android.provider.extra.APP_PACKAGE': 'your.package.name.here' }, {
* 'android.provider.extra.CHANNEL_ID': 'your.channel.id.here } ]
*
* @param action The general action to be performed
* @param extras An array of extras [{ String, String | Number | Boolean }]
*/
@Override
public void sendIntent(String action, @Nullable ReadableArray extras, Promise promise) {
if (action == null || action.isEmpty()) {
promise.reject(new JSApplicationIllegalArgumentException("Invalid Action: " + action + "."));
return;
}
Intent intent = new Intent(action);
PackageManager packageManager = getReactApplicationContext().getPackageManager();
if (intent.resolveActivity(packageManager) == null) {
promise.reject(
new JSApplicationIllegalArgumentException(
"Could not launch Intent with action " + action + "."));
return;
}
if (extras != null) {
for (int i = 0; i < extras.size(); i++) {
ReadableMap map = extras.getMap(i);
String name = map.keySetIterator().nextKey();
ReadableType type = map.getType(name);
switch (type) {
case String:
{
intent.putExtra(name, map.getString(name));
break;
}
case Number:
{
// We cannot know from JS if is an Integer or Double
// See: https://github.com/facebook/react-native/issues/4141
// We might need to find a workaround if this is really an issue
Double number = map.getDouble(name);
intent.putExtra(name, number);
break;
}
case Boolean:
{
intent.putExtra(name, map.getBoolean(name));
break;
}
default:
{
promise.reject(
new JSApplicationIllegalArgumentException(
"Extra type for " + name + " not supported."));
return;
}
}
}
}
sendOSIntent(intent, true);
}
private void sendOSIntent(Intent intent, Boolean useNewTaskFlag) {
Activity currentActivity = getCurrentActivity();
String selfPackageName = getReactApplicationContext().getPackageName();
ComponentName componentName =
intent.resolveActivity(getReactApplicationContext().getPackageManager());
String otherPackageName = (componentName != null ? componentName.getPackageName() : "");
// If there is no currentActivity or we are launching to a different package we need to set
// the FLAG_ACTIVITY_NEW_TASK flag
if (useNewTaskFlag || currentActivity == null || !selfPackageName.equals(otherPackageName)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (currentActivity != null) {
currentActivity.startActivity(intent);
} else {
getReactApplicationContext().startActivity(intent);
}
}
}