html2apk 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +323 -12
- package/examples/minimal/app.json +2 -0
- package/examples/minimal/dist/MeuApp-1.0.0-debug.apk +0 -0
- package/package.json +3 -2
- package/src/cli/index.js +10 -1
- package/src/cordova/config-xml.js +59 -1
- package/src/cordova/project.js +19 -1
- package/src/core/build-apk.js +399 -23
- package/src/core/config.js +47 -1
- package/src/core/defaults.js +6 -0
- package/src/desktop/main.js +152 -2
- package/src/desktop/preload.js +14 -0
- package/src/desktop/renderer/index.html +20 -2
- package/src/desktop/renderer/renderer.js +1254 -41
- package/src/desktop/renderer/styles.css +98 -2
- package/src/templates/cordova-plugin-html2apk-bridge/plugin.xml +4 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/Html2ApkBridge.java +1770 -38
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/NotificationReceiver.java +19 -5
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/NotificationStore.java +13 -1
- package/src/templates/cordova-plugin-html2apk-bridge/www/html2apk-bridge.js +516 -3
- package/src/templates/html2apk-auto-theme.js +144 -0
- package/src/templates/html2apk-onesignal.js +155 -0
- package/src/utils/command-runner.js +3 -0
|
@@ -1,26 +1,49 @@
|
|
|
1
1
|
package dev.html2apk.bridge;
|
|
2
2
|
|
|
3
3
|
import android.Manifest;
|
|
4
|
+
import android.app.Activity;
|
|
4
5
|
import android.app.AlarmManager;
|
|
6
|
+
import android.app.ActivityManager;
|
|
5
7
|
import android.app.NotificationChannel;
|
|
6
8
|
import android.app.NotificationManager;
|
|
7
9
|
import android.app.PendingIntent;
|
|
10
|
+
import android.content.ActivityNotFoundException;
|
|
11
|
+
import android.content.BroadcastReceiver;
|
|
8
12
|
import android.content.ClipData;
|
|
9
13
|
import android.content.ClipboardManager;
|
|
10
14
|
import android.content.Context;
|
|
11
15
|
import android.content.Intent;
|
|
16
|
+
import android.content.IntentFilter;
|
|
17
|
+
import android.content.SharedPreferences;
|
|
18
|
+
import android.content.pm.ApplicationInfo;
|
|
12
19
|
import android.content.pm.PackageManager;
|
|
20
|
+
import android.database.Cursor;
|
|
21
|
+
import android.hardware.camera2.CameraCharacteristics;
|
|
22
|
+
import android.hardware.camera2.CameraManager;
|
|
23
|
+
import android.graphics.Color;
|
|
24
|
+
import android.media.MediaRecorder;
|
|
25
|
+
import android.net.ConnectivityManager;
|
|
26
|
+
import android.net.Network;
|
|
27
|
+
import android.net.NetworkCapabilities;
|
|
13
28
|
import android.net.Uri;
|
|
29
|
+
import android.os.BatteryManager;
|
|
14
30
|
import android.os.Build;
|
|
31
|
+
import android.os.Debug;
|
|
32
|
+
import android.os.Environment;
|
|
33
|
+
import android.os.StatFs;
|
|
15
34
|
import android.os.VibrationEffect;
|
|
16
35
|
import android.os.Vibrator;
|
|
17
36
|
import android.provider.Settings;
|
|
37
|
+
import android.provider.OpenableColumns;
|
|
38
|
+
import android.util.Base64;
|
|
18
39
|
import android.view.View;
|
|
40
|
+
import android.view.Window;
|
|
19
41
|
import android.view.WindowManager;
|
|
20
42
|
import android.widget.Toast;
|
|
21
43
|
|
|
22
44
|
import androidx.core.app.NotificationCompat;
|
|
23
45
|
import androidx.core.app.NotificationManagerCompat;
|
|
46
|
+
import androidx.core.content.ContextCompat;
|
|
24
47
|
|
|
25
48
|
import org.apache.cordova.CallbackContext;
|
|
26
49
|
import org.apache.cordova.CordovaInterface;
|
|
@@ -29,40 +52,96 @@ import org.apache.cordova.CordovaWebView;
|
|
|
29
52
|
import org.json.JSONArray;
|
|
30
53
|
import org.json.JSONObject;
|
|
31
54
|
|
|
55
|
+
import java.io.ByteArrayInputStream;
|
|
56
|
+
import java.io.ByteArrayOutputStream;
|
|
57
|
+
import java.io.File;
|
|
58
|
+
import java.io.FileInputStream;
|
|
59
|
+
import java.io.InputStream;
|
|
60
|
+
import java.io.OutputStream;
|
|
61
|
+
import java.util.List;
|
|
62
|
+
|
|
32
63
|
public class Html2ApkBridge extends CordovaPlugin {
|
|
33
64
|
static final String CHANNEL_ID = "html2apk_default";
|
|
34
65
|
static final String EXTRA_NOTIFICATION_CLICKED = "html2apk_notification_clicked";
|
|
35
66
|
static final String EXTRA_NOTIFICATION_DETAIL = "html2apk_notification_detail";
|
|
36
67
|
|
|
37
68
|
private static final int REQUEST_POST_NOTIFICATIONS = 7311;
|
|
69
|
+
private static final int REQUEST_CAMERA = 7312;
|
|
70
|
+
private static final int REQUEST_RECORD_AUDIO = 7313;
|
|
71
|
+
private static final int REQUEST_PICK_FILE = 7411;
|
|
72
|
+
private static final int REQUEST_SAVE_FILE = 7412;
|
|
73
|
+
private static final int REQUEST_PICK_FOLDER = 7413;
|
|
74
|
+
private static final String PREFS_NAME = "html2apk_bridge";
|
|
75
|
+
private static final String PREF_PERMISSION_PREFIX = "permission_requested_";
|
|
38
76
|
|
|
39
77
|
private CallbackContext notificationPermissionCallback;
|
|
78
|
+
private CallbackContext cameraPermissionCallback;
|
|
79
|
+
private CallbackContext pendingNotificationCallback;
|
|
80
|
+
private CallbackContext pendingFlashlightCallback;
|
|
81
|
+
private CallbackContext microphonePermissionCallback;
|
|
82
|
+
private CallbackContext pendingMicStartCallback;
|
|
83
|
+
private CallbackContext filePickerCallback;
|
|
84
|
+
private CallbackContext saveFileCallback;
|
|
85
|
+
private CallbackContext folderPickerCallback;
|
|
86
|
+
private JSONObject pendingSaveFile;
|
|
87
|
+
private JSONObject pendingNotificationOptions;
|
|
40
88
|
private JSONObject initialNotification;
|
|
89
|
+
private JSONObject initialLink;
|
|
90
|
+
private Boolean pendingFlashlightEnabled;
|
|
91
|
+
private boolean pendingNotificationSchedule;
|
|
92
|
+
private boolean pendingFlashlightToggle;
|
|
41
93
|
private boolean overlaySettingsOpened;
|
|
94
|
+
private boolean torchEnabled;
|
|
95
|
+
private MediaRecorder micRecorder;
|
|
96
|
+
private File micRecordingFile;
|
|
97
|
+
private long micRecordingStartedAt;
|
|
98
|
+
private BroadcastReceiver systemReceiver;
|
|
42
99
|
|
|
43
100
|
@Override
|
|
44
101
|
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
|
45
102
|
super.initialize(cordova, webView);
|
|
46
103
|
handleNotificationIntent(cordova.getActivity().getIntent(), false);
|
|
104
|
+
handleLinkIntent(cordova.getActivity().getIntent(), false);
|
|
105
|
+
registerSystemReceiver();
|
|
47
106
|
startFloatingModeIfNeeded();
|
|
48
107
|
}
|
|
49
108
|
|
|
50
109
|
@Override
|
|
51
110
|
public void onNewIntent(Intent intent) {
|
|
52
111
|
handleNotificationIntent(intent, true);
|
|
112
|
+
handleLinkIntent(intent, true);
|
|
53
113
|
}
|
|
54
114
|
|
|
55
115
|
@Override
|
|
56
116
|
public void onResume(boolean multitasking) {
|
|
57
117
|
super.onResume(multitasking);
|
|
118
|
+
dispatchEvent("app:voltou", baseEvent("app:voltou"));
|
|
58
119
|
startFloatingModeIfNeeded();
|
|
59
120
|
}
|
|
60
121
|
|
|
122
|
+
@Override
|
|
123
|
+
public void onPause(boolean multitasking) {
|
|
124
|
+
dispatchEvent("app:pausado", baseEvent("app:pausado"));
|
|
125
|
+
dispatchEvent("app:background", baseEvent("app:background"));
|
|
126
|
+
super.onPause(multitasking);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@Override
|
|
130
|
+
public void onDestroy() {
|
|
131
|
+
stopMicRecorderSilently();
|
|
132
|
+
unregisterSystemReceiver();
|
|
133
|
+
dispatchEvent("app:fechado", baseEvent("app:fechado"));
|
|
134
|
+
super.onDestroy();
|
|
135
|
+
}
|
|
136
|
+
|
|
61
137
|
@Override
|
|
62
138
|
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
|
|
63
139
|
try {
|
|
64
140
|
if ("notify".equals(action)) {
|
|
65
141
|
JSONObject options = args.optJSONObject(0);
|
|
142
|
+
if (requestNotificationPermissionForAction(options == null ? new JSONObject() : options, false, callbackContext)) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
66
145
|
showNotification(options == null ? new JSONObject() : options);
|
|
67
146
|
callbackContext.success();
|
|
68
147
|
return true;
|
|
@@ -70,7 +149,15 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
70
149
|
|
|
71
150
|
if ("scheduleNotification".equals(action)) {
|
|
72
151
|
JSONObject options = args.optJSONObject(0);
|
|
73
|
-
|
|
152
|
+
if (requestNotificationPermissionForAction(options == null ? new JSONObject() : options, true, callbackContext)) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
callbackContext.success(scheduleNotification(options == null ? new JSONObject() : options));
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if ("cancelNotification".equals(action)) {
|
|
160
|
+
cancelNotification(args.opt(0));
|
|
74
161
|
callbackContext.success();
|
|
75
162
|
return true;
|
|
76
163
|
}
|
|
@@ -105,24 +192,153 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
105
192
|
return true;
|
|
106
193
|
}
|
|
107
194
|
|
|
195
|
+
if ("setSystemBarsColor".equals(action)) {
|
|
196
|
+
callbackContext.success(setSystemBarsColor(args.opt(0)));
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if ("flashlight".equals(action)) {
|
|
201
|
+
setFlashlightWithPermission(args.optBoolean(0, true), false, callbackContext);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if ("toggleFlashlight".equals(action)) {
|
|
206
|
+
setFlashlightWithPermission(false, true, callbackContext);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if ("flashlightStatus".equals(action)) {
|
|
211
|
+
callbackContext.success(flashlightStatus());
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if ("requestCameraPermission".equals(action)) {
|
|
216
|
+
requestCameraPermission(callbackContext);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if ("requestMicrophonePermission".equals(action)) {
|
|
221
|
+
requestMicrophonePermission(callbackContext);
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if ("microphoneStatus".equals(action)) {
|
|
226
|
+
callbackContext.success(microphoneStatus());
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if ("startMic".equals(action)) {
|
|
231
|
+
startMicRecording(callbackContext);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if ("stopMic".equals(action)) {
|
|
236
|
+
callbackContext.success(stopMicRecording());
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
108
240
|
if ("copyText".equals(action)) {
|
|
109
241
|
copyText(args.optString(0, ""));
|
|
110
242
|
callbackContext.success();
|
|
111
243
|
return true;
|
|
112
244
|
}
|
|
113
245
|
|
|
246
|
+
if ("readText".equals(action)) {
|
|
247
|
+
callbackContext.success(readText());
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
|
|
114
251
|
if ("shareText".equals(action)) {
|
|
115
252
|
shareText(args.optString(0, ""));
|
|
116
253
|
callbackContext.success();
|
|
117
254
|
return true;
|
|
118
255
|
}
|
|
119
256
|
|
|
257
|
+
if ("share".equals(action)) {
|
|
258
|
+
share(args.optJSONObject(0));
|
|
259
|
+
callbackContext.success();
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
120
263
|
if ("openUrl".equals(action)) {
|
|
121
264
|
openUrl(args.optString(0, ""));
|
|
122
265
|
callbackContext.success();
|
|
123
266
|
return true;
|
|
124
267
|
}
|
|
125
268
|
|
|
269
|
+
if ("dial".equals(action)) {
|
|
270
|
+
dial(args.optString(0, ""));
|
|
271
|
+
callbackContext.success();
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if ("openMap".equals(action)) {
|
|
276
|
+
openMap(args.optString(0, ""));
|
|
277
|
+
callbackContext.success();
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if ("openWhatsapp".equals(action)) {
|
|
282
|
+
openWhatsapp(args.optString(0, ""), args.optString(1, ""));
|
|
283
|
+
callbackContext.success();
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if ("pickFile".equals(action)) {
|
|
288
|
+
pickFile(args.optJSONObject(0), callbackContext);
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if ("pickFolder".equals(action)) {
|
|
293
|
+
pickFolder(callbackContext);
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if ("saveFile".equals(action)) {
|
|
298
|
+
saveFile(args.optJSONObject(0), callbackContext);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if ("deviceInfo".equals(action)) {
|
|
303
|
+
callbackContext.success(deviceInfo());
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if ("networkInfo".equals(action)) {
|
|
308
|
+
callbackContext.success(networkInfo());
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if ("batteryInfo".equals(action)) {
|
|
313
|
+
callbackContext.success(batteryInfo());
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if ("memoryInfo".equals(action)) {
|
|
318
|
+
callbackContext.success(memoryInfo());
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if ("storageInfo".equals(action)) {
|
|
323
|
+
callbackContext.success(storageInfo());
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if ("performanceInfo".equals(action)) {
|
|
328
|
+
callbackContext.success(performanceInfo());
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if ("openAppsMemory".equals(action)) {
|
|
333
|
+
callbackContext.success(openAppsMemoryInfo());
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if ("permissionStatus".equals(action)) {
|
|
338
|
+
callbackContext.success(permissionStatus(args.optJSONArray(0)));
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
|
|
126
342
|
if ("requestNotificationPermission".equals(action)) {
|
|
127
343
|
requestNotificationPermission(callbackContext);
|
|
128
344
|
return true;
|
|
@@ -140,7 +356,14 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
140
356
|
|
|
141
357
|
if ("openExactAlarmSettings".equals(action)) {
|
|
142
358
|
openExactAlarmSettings();
|
|
143
|
-
|
|
359
|
+
JSONObject result = new JSONObject();
|
|
360
|
+
result.put("permission", "android.permission.SCHEDULE_EXACT_ALARM");
|
|
361
|
+
result.put("required", Build.VERSION.SDK_INT >= Build.VERSION_CODES.S);
|
|
362
|
+
result.put("granted", canScheduleExactAlarms());
|
|
363
|
+
result.put("permissionGranted", canScheduleExactAlarms());
|
|
364
|
+
result.put("requiresSettings", Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !canScheduleExactAlarms());
|
|
365
|
+
result.put("settingsOpened", Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !canScheduleExactAlarms());
|
|
366
|
+
callbackContext.success(result);
|
|
144
367
|
return true;
|
|
145
368
|
}
|
|
146
369
|
|
|
@@ -151,11 +374,22 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
151
374
|
|
|
152
375
|
if ("requestOverlayPermission".equals(action) || "openOverlaySettings".equals(action)) {
|
|
153
376
|
openOverlaySettings();
|
|
154
|
-
|
|
377
|
+
JSONObject result = overlayPermissionStatus();
|
|
378
|
+
result.put("requested", true);
|
|
379
|
+
result.put("settingsOpened", result.optBoolean("requiresSettings"));
|
|
380
|
+
callbackContext.success(result);
|
|
155
381
|
return true;
|
|
156
382
|
}
|
|
157
383
|
|
|
158
384
|
if ("startFloatingIcon".equals(action)) {
|
|
385
|
+
if (!canDrawOverlays()) {
|
|
386
|
+
openOverlaySettings();
|
|
387
|
+
JSONObject result = overlayPermissionStatus();
|
|
388
|
+
result.put("requested", true);
|
|
389
|
+
result.put("requiresSettings", true);
|
|
390
|
+
callbackContext.success(result);
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
159
393
|
startFloatingIcon();
|
|
160
394
|
callbackContext.success(overlayPermissionStatus());
|
|
161
395
|
return true;
|
|
@@ -172,6 +406,12 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
172
406
|
initialNotification = null;
|
|
173
407
|
return true;
|
|
174
408
|
}
|
|
409
|
+
|
|
410
|
+
if ("getInitialLink".equals(action)) {
|
|
411
|
+
callbackContext.success(initialLink == null ? new JSONObject() : initialLink);
|
|
412
|
+
initialLink = null;
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
175
415
|
} catch (Exception error) {
|
|
176
416
|
callbackContext.error(error.getMessage());
|
|
177
417
|
return true;
|
|
@@ -182,13 +422,142 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
182
422
|
|
|
183
423
|
@Override
|
|
184
424
|
public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) {
|
|
185
|
-
if (requestCode
|
|
425
|
+
if (requestCode == REQUEST_CAMERA) {
|
|
426
|
+
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
|
427
|
+
if (pendingFlashlightCallback != null) {
|
|
428
|
+
CallbackContext callback = pendingFlashlightCallback;
|
|
429
|
+
Boolean enabled = pendingFlashlightEnabled;
|
|
430
|
+
boolean toggle = pendingFlashlightToggle;
|
|
431
|
+
pendingFlashlightCallback = null;
|
|
432
|
+
pendingFlashlightEnabled = null;
|
|
433
|
+
pendingFlashlightToggle = false;
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
if (!granted) {
|
|
437
|
+
JSONObject result = shouldOpenSettingsForRuntimePermission(Manifest.permission.CAMERA)
|
|
438
|
+
? openSettingsForRuntimePermission(Manifest.permission.CAMERA, true, true)
|
|
439
|
+
: flashlightStatus();
|
|
440
|
+
result.put("requested", true);
|
|
441
|
+
result.put("granted", false);
|
|
442
|
+
callback.success(result);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
setFlashlight(toggle ? !torchEnabled : Boolean.TRUE.equals(enabled));
|
|
447
|
+
JSONObject result = flashlightStatus();
|
|
448
|
+
result.put("requested", true);
|
|
449
|
+
result.put("granted", true);
|
|
450
|
+
callback.success(result);
|
|
451
|
+
} catch (Exception error) {
|
|
452
|
+
callback.error(error.getMessage());
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (cameraPermissionCallback == null) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
JSONObject result = granted
|
|
463
|
+
? runtimePermissionResult(Manifest.permission.CAMERA, true, true, true)
|
|
464
|
+
: (
|
|
465
|
+
shouldOpenSettingsForRuntimePermission(Manifest.permission.CAMERA)
|
|
466
|
+
? openSettingsForRuntimePermission(Manifest.permission.CAMERA, true, true)
|
|
467
|
+
: runtimePermissionResult(Manifest.permission.CAMERA, true, true, false)
|
|
468
|
+
);
|
|
469
|
+
cameraPermissionCallback.success(result);
|
|
470
|
+
} catch (Exception error) {
|
|
471
|
+
cameraPermissionCallback.error(error.getMessage());
|
|
472
|
+
} finally {
|
|
473
|
+
cameraPermissionCallback = null;
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (requestCode == REQUEST_RECORD_AUDIO) {
|
|
479
|
+
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
|
480
|
+
CallbackContext callback = pendingMicStartCallback != null ? pendingMicStartCallback : microphonePermissionCallback;
|
|
481
|
+
boolean shouldStartRecording = pendingMicStartCallback != null;
|
|
482
|
+
pendingMicStartCallback = null;
|
|
483
|
+
microphonePermissionCallback = null;
|
|
484
|
+
|
|
485
|
+
if (callback == null) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
try {
|
|
490
|
+
if (shouldStartRecording && granted) {
|
|
491
|
+
startMicRecorder(callback);
|
|
492
|
+
} else {
|
|
493
|
+
JSONObject result = granted
|
|
494
|
+
? microphoneStatus()
|
|
495
|
+
: (
|
|
496
|
+
shouldOpenSettingsForRuntimePermission(Manifest.permission.RECORD_AUDIO)
|
|
497
|
+
? openSettingsForRuntimePermission(Manifest.permission.RECORD_AUDIO, true, true)
|
|
498
|
+
: microphoneStatus()
|
|
499
|
+
);
|
|
500
|
+
result.put("requested", true);
|
|
501
|
+
result.put("granted", granted);
|
|
502
|
+
callback.success(result);
|
|
503
|
+
}
|
|
504
|
+
} catch (Exception error) {
|
|
505
|
+
callback.error(error.getMessage());
|
|
506
|
+
}
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (requestCode != REQUEST_POST_NOTIFICATIONS) {
|
|
186
511
|
return;
|
|
187
512
|
}
|
|
188
513
|
|
|
189
514
|
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
|
515
|
+
if (pendingNotificationCallback != null) {
|
|
516
|
+
CallbackContext callback = pendingNotificationCallback;
|
|
517
|
+
JSONObject options = pendingNotificationOptions == null ? new JSONObject() : pendingNotificationOptions;
|
|
518
|
+
boolean schedule = pendingNotificationSchedule;
|
|
519
|
+
pendingNotificationCallback = null;
|
|
520
|
+
pendingNotificationOptions = null;
|
|
521
|
+
pendingNotificationSchedule = false;
|
|
522
|
+
|
|
523
|
+
try {
|
|
524
|
+
if (!granted) {
|
|
525
|
+
JSONObject result = shouldOpenSettingsForRuntimePermission(Manifest.permission.POST_NOTIFICATIONS)
|
|
526
|
+
? openSettingsForRuntimePermission(Manifest.permission.POST_NOTIFICATIONS, true, true)
|
|
527
|
+
: notificationPermissionStatus();
|
|
528
|
+
result.put("requested", true);
|
|
529
|
+
result.put("granted", false);
|
|
530
|
+
callback.success(result);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (schedule) {
|
|
535
|
+
JSONObject result = scheduleNotification(options);
|
|
536
|
+
result.put("requested", true);
|
|
537
|
+
result.put("granted", true);
|
|
538
|
+
callback.success(result);
|
|
539
|
+
} else {
|
|
540
|
+
showNotification(options);
|
|
541
|
+
callback.success();
|
|
542
|
+
}
|
|
543
|
+
} catch (Exception error) {
|
|
544
|
+
callback.error(error.getMessage());
|
|
545
|
+
}
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (notificationPermissionCallback == null) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
190
553
|
try {
|
|
191
|
-
JSONObject result =
|
|
554
|
+
JSONObject result = granted
|
|
555
|
+
? notificationPermissionStatus()
|
|
556
|
+
: (
|
|
557
|
+
shouldOpenSettingsForRuntimePermission(Manifest.permission.POST_NOTIFICATIONS)
|
|
558
|
+
? openSettingsForRuntimePermission(Manifest.permission.POST_NOTIFICATIONS, true, true)
|
|
559
|
+
: notificationPermissionStatus()
|
|
560
|
+
);
|
|
192
561
|
result.put("requested", true);
|
|
193
562
|
result.put("granted", granted);
|
|
194
563
|
notificationPermissionCallback.success(result);
|
|
@@ -199,10 +568,99 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
199
568
|
}
|
|
200
569
|
}
|
|
201
570
|
|
|
571
|
+
@Override
|
|
572
|
+
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
|
573
|
+
if (requestCode == REQUEST_PICK_FILE) {
|
|
574
|
+
handlePickFileResult(resultCode, intent);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (requestCode == REQUEST_SAVE_FILE) {
|
|
579
|
+
handleSaveFileResult(resultCode, intent);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (requestCode == REQUEST_PICK_FOLDER) {
|
|
584
|
+
handlePickFolderResult(resultCode, intent);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
202
588
|
private Context context() {
|
|
203
589
|
return this.cordova.getActivity().getApplicationContext();
|
|
204
590
|
}
|
|
205
591
|
|
|
592
|
+
private SharedPreferences preferencesStore() {
|
|
593
|
+
return context().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
private boolean wasRuntimePermissionRequested(String permission) {
|
|
597
|
+
return preferencesStore().getBoolean(PREF_PERMISSION_PREFIX + permission, false);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private void rememberRuntimePermissionRequest(String permission) {
|
|
601
|
+
preferencesStore().edit().putBoolean(PREF_PERMISSION_PREFIX + permission, true).apply();
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
private boolean shouldOpenSettingsForRuntimePermission(String permission) {
|
|
605
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || !wasRuntimePermissionRequested(permission)) {
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return !cordova.getActivity().shouldShowRequestPermissionRationale(permission);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
private JSONObject runtimePermissionResult(String permission, boolean required, boolean requested, boolean granted) throws Exception {
|
|
613
|
+
JSONObject result = new JSONObject();
|
|
614
|
+
result.put("permission", permission);
|
|
615
|
+
result.put("required", required);
|
|
616
|
+
result.put("requested", requested);
|
|
617
|
+
result.put("granted", granted);
|
|
618
|
+
result.put("permissionGranted", granted);
|
|
619
|
+
result.put("requiresSettings", required && !granted && shouldOpenSettingsForRuntimePermission(permission));
|
|
620
|
+
result.put("settingsOpened", false);
|
|
621
|
+
return result;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private JSONObject openSettingsForRuntimePermission(String permission, boolean required, boolean requested) throws Exception {
|
|
625
|
+
JSONObject result = runtimePermissionResult(permission, required, requested, false);
|
|
626
|
+
openPermissionSettings(permission);
|
|
627
|
+
result.put("requiresSettings", true);
|
|
628
|
+
result.put("settingsOpened", true);
|
|
629
|
+
result.put("settingsScreen", Manifest.permission.POST_NOTIFICATIONS.equals(permission) ? "notifications" : "app");
|
|
630
|
+
result.put("telaConfiguracao", result.optString("settingsScreen"));
|
|
631
|
+
return result;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
private void openPermissionSettings(String permission) {
|
|
635
|
+
if (Manifest.permission.POST_NOTIFICATIONS.equals(permission)) {
|
|
636
|
+
openNotificationSettings();
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
openAppSettings();
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
private void openAppSettings() {
|
|
644
|
+
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
645
|
+
intent.setData(Uri.parse("package:" + context().getPackageName()));
|
|
646
|
+
cordova.getActivity().startActivity(intent);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
private void openNotificationSettings() {
|
|
650
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
651
|
+
openAppSettings();
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
|
|
656
|
+
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context().getPackageName());
|
|
657
|
+
try {
|
|
658
|
+
cordova.getActivity().startActivity(intent);
|
|
659
|
+
} catch (ActivityNotFoundException error) {
|
|
660
|
+
openAppSettings();
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
206
664
|
private boolean isFloatingMode() {
|
|
207
665
|
return "floating".equals(preferences.getString("Html2ApkMode", ""));
|
|
208
666
|
}
|
|
@@ -238,17 +696,24 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
238
696
|
result.put("required", Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
|
|
239
697
|
result.put("granted", canDrawOverlays());
|
|
240
698
|
result.put("permission", "android.permission.SYSTEM_ALERT_WINDOW");
|
|
699
|
+
result.put("permissionGranted", canDrawOverlays());
|
|
700
|
+
result.put("requiresSettings", Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !canDrawOverlays());
|
|
701
|
+
result.put("settingsOpened", false);
|
|
241
702
|
return result;
|
|
242
703
|
}
|
|
243
704
|
|
|
244
705
|
private void openOverlaySettings() {
|
|
245
|
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
706
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || canDrawOverlays()) {
|
|
246
707
|
return;
|
|
247
708
|
}
|
|
248
709
|
|
|
249
710
|
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
|
|
250
711
|
intent.setData(Uri.parse("package:" + context().getPackageName()));
|
|
251
|
-
|
|
712
|
+
try {
|
|
713
|
+
cordova.getActivity().startActivity(intent);
|
|
714
|
+
} catch (ActivityNotFoundException error) {
|
|
715
|
+
openAppSettings();
|
|
716
|
+
}
|
|
252
717
|
}
|
|
253
718
|
|
|
254
719
|
private void startFloatingIcon() throws Exception {
|
|
@@ -292,21 +757,42 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
292
757
|
return;
|
|
293
758
|
}
|
|
294
759
|
|
|
760
|
+
if (shouldOpenSettingsForRuntimePermission(Manifest.permission.POST_NOTIFICATIONS)) {
|
|
761
|
+
callbackContext.success(openSettingsForRuntimePermission(Manifest.permission.POST_NOTIFICATIONS, true, false));
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
|
|
295
765
|
notificationPermissionCallback = callbackContext;
|
|
766
|
+
rememberRuntimePermissionRequest(Manifest.permission.POST_NOTIFICATIONS);
|
|
296
767
|
cordova.requestPermission(this, REQUEST_POST_NOTIFICATIONS, Manifest.permission.POST_NOTIFICATIONS);
|
|
297
768
|
}
|
|
298
769
|
|
|
299
770
|
private JSONObject notificationPermissionStatus() throws Exception {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
771
|
+
boolean required = Build.VERSION.SDK_INT >= 33;
|
|
772
|
+
return runtimePermissionResult(Manifest.permission.POST_NOTIFICATIONS, required, false, hasNotificationPermission());
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
private boolean requestNotificationPermissionForAction(JSONObject options, boolean schedule, CallbackContext callbackContext) throws Exception {
|
|
776
|
+
if (hasNotificationPermission()) {
|
|
777
|
+
return false;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (shouldOpenSettingsForRuntimePermission(Manifest.permission.POST_NOTIFICATIONS)) {
|
|
781
|
+
callbackContext.success(openSettingsForRuntimePermission(Manifest.permission.POST_NOTIFICATIONS, true, false));
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
pendingNotificationOptions = options;
|
|
786
|
+
pendingNotificationSchedule = schedule;
|
|
787
|
+
pendingNotificationCallback = callbackContext;
|
|
788
|
+
rememberRuntimePermissionRequest(Manifest.permission.POST_NOTIFICATIONS);
|
|
789
|
+
cordova.requestPermission(this, REQUEST_POST_NOTIFICATIONS, Manifest.permission.POST_NOTIFICATIONS);
|
|
790
|
+
return true;
|
|
305
791
|
}
|
|
306
792
|
|
|
307
793
|
private void showNotification(JSONObject options) throws Exception {
|
|
308
794
|
if (!hasNotificationPermission()) {
|
|
309
|
-
throw new Exception("POST_NOTIFICATIONS permission is not granted.
|
|
795
|
+
throw new Exception("POST_NOTIFICATIONS permission is not granted.");
|
|
310
796
|
}
|
|
311
797
|
|
|
312
798
|
ensureNotificationChannel(context());
|
|
@@ -323,38 +809,83 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
323
809
|
.setContentIntent(createContentIntent(context(), id, detailPayload(options)))
|
|
324
810
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
|
|
325
811
|
|
|
812
|
+
addNotificationActions(builder, context(), id, options);
|
|
326
813
|
NotificationManagerCompat.from(context()).notify(id, builder.build());
|
|
814
|
+
dispatchEvent("notificacao:recebida", detailPayload(options));
|
|
327
815
|
}
|
|
328
816
|
|
|
329
|
-
private
|
|
817
|
+
private JSONObject scheduleNotification(JSONObject options) throws Exception {
|
|
330
818
|
long when = options.optLong("quando", options.optLong("when", System.currentTimeMillis() + 60000));
|
|
331
819
|
if (when < System.currentTimeMillis()) {
|
|
332
820
|
when = System.currentTimeMillis() + 1000;
|
|
333
821
|
}
|
|
334
822
|
|
|
335
823
|
int id = notificationId(options);
|
|
824
|
+
options.put("id", id);
|
|
825
|
+
options.put("quando", when);
|
|
826
|
+
options.put("when", when);
|
|
827
|
+
boolean exactRequested = wantsExactAlarm(options);
|
|
828
|
+
boolean exactAllowed = canScheduleExactAlarms();
|
|
829
|
+
boolean settingsOpened = false;
|
|
830
|
+
if (exactRequested && !exactAllowed) {
|
|
831
|
+
openExactAlarmSettings();
|
|
832
|
+
settingsOpened = true;
|
|
833
|
+
}
|
|
336
834
|
NotificationStore.save(context(), id, when, options);
|
|
337
|
-
NotificationReceiver.schedule(context(), id, when, options,
|
|
835
|
+
NotificationReceiver.schedule(context(), id, when, options, exactAllowed);
|
|
836
|
+
|
|
837
|
+
JSONObject result = new JSONObject();
|
|
838
|
+
result.put("id", id);
|
|
839
|
+
result.put("when", when);
|
|
840
|
+
result.put("quando", when);
|
|
841
|
+
result.put("repeating", repeatInterval(options) > 0);
|
|
842
|
+
result.put("loop", loopNotifications(options).length() > 0);
|
|
843
|
+
result.put("exactRequested", exactRequested);
|
|
844
|
+
result.put("exatoSolicitado", exactRequested);
|
|
845
|
+
result.put("exactAllowed", exactAllowed);
|
|
846
|
+
result.put("exatoPermitido", exactAllowed);
|
|
847
|
+
result.put("requiresSettings", exactRequested && !exactAllowed);
|
|
848
|
+
result.put("settingsOpened", settingsOpened);
|
|
849
|
+
return result;
|
|
338
850
|
}
|
|
339
851
|
|
|
340
|
-
private boolean
|
|
341
|
-
|
|
342
|
-
|
|
852
|
+
private boolean wantsExactAlarm(JSONObject options) {
|
|
853
|
+
return options.optBoolean("exato",
|
|
854
|
+
options.optBoolean("exact",
|
|
855
|
+
options.optBoolean("alarmeExato",
|
|
856
|
+
options.optBoolean("exactAlarm",
|
|
857
|
+
options.optBoolean("preciso",
|
|
858
|
+
options.optBoolean("precise", false))))));
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
private void cancelNotification(Object input) throws Exception {
|
|
862
|
+
int id = notificationIdFromObject(input);
|
|
863
|
+
if (id == 0) {
|
|
864
|
+
throw new Exception("Notification id is required.");
|
|
343
865
|
}
|
|
344
866
|
|
|
345
|
-
|
|
346
|
-
|
|
867
|
+
NotificationReceiver.cancel(context(), id);
|
|
868
|
+
NotificationStore.remove(context(), id);
|
|
869
|
+
NotificationManagerCompat.from(context()).cancel(id);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
private boolean canScheduleExactAlarms() {
|
|
873
|
+
return canScheduleExactAlarms(context());
|
|
347
874
|
}
|
|
348
875
|
|
|
349
876
|
private void openExactAlarmSettings() {
|
|
350
|
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
877
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || canScheduleExactAlarms()) {
|
|
351
878
|
return;
|
|
352
879
|
}
|
|
353
880
|
|
|
354
881
|
Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
|
|
355
882
|
intent.setData(Uri.parse("package:" + context().getPackageName()));
|
|
356
883
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
357
|
-
|
|
884
|
+
try {
|
|
885
|
+
context().startActivity(intent);
|
|
886
|
+
} catch (ActivityNotFoundException error) {
|
|
887
|
+
openAppSettings();
|
|
888
|
+
}
|
|
358
889
|
}
|
|
359
890
|
|
|
360
891
|
private void vibrate(long ms) {
|
|
@@ -428,6 +959,83 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
428
959
|
});
|
|
429
960
|
}
|
|
430
961
|
|
|
962
|
+
private JSONObject setSystemBarsColor(Object input) throws Exception {
|
|
963
|
+
final JSONObject options = normalizeSystemBarsOptions(input);
|
|
964
|
+
final String statusColor = options.optString("statusBarColor", options.optString("color", options.optString("cor", "#126fff")));
|
|
965
|
+
final String navigationColor = options.optString("navigationBarColor", options.optString("navigationColor", statusColor));
|
|
966
|
+
final boolean darkStatusIcons = options.optBoolean("darkIcons", isLightColor(statusColor));
|
|
967
|
+
final boolean darkNavigationIcons = options.optBoolean("darkNavigationIcons", isLightColor(navigationColor));
|
|
968
|
+
final int statusBarColor = Color.parseColor(statusColor);
|
|
969
|
+
final int navigationBarColor = Color.parseColor(navigationColor);
|
|
970
|
+
|
|
971
|
+
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
|
972
|
+
@Override
|
|
973
|
+
public void run() {
|
|
974
|
+
Window window = cordova.getActivity().getWindow();
|
|
975
|
+
window.setStatusBarColor(statusBarColor);
|
|
976
|
+
window.setNavigationBarColor(navigationBarColor);
|
|
977
|
+
applySystemBarIconContrast(window, darkStatusIcons, darkNavigationIcons);
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
JSONObject result = new JSONObject();
|
|
982
|
+
result.put("statusBarColor", statusColor);
|
|
983
|
+
result.put("navigationBarColor", navigationColor);
|
|
984
|
+
result.put("darkIcons", darkStatusIcons);
|
|
985
|
+
result.put("darkNavigationIcons", darkNavigationIcons);
|
|
986
|
+
result.put("applied", true);
|
|
987
|
+
return result;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
private JSONObject normalizeSystemBarsOptions(Object input) throws Exception {
|
|
991
|
+
if (input instanceof JSONObject) {
|
|
992
|
+
return (JSONObject) input;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
JSONObject options = new JSONObject();
|
|
996
|
+
String color = input == null ? "#126fff" : String.valueOf(input);
|
|
997
|
+
options.put("color", color);
|
|
998
|
+
options.put("statusBarColor", color);
|
|
999
|
+
options.put("navigationBarColor", color);
|
|
1000
|
+
return options;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
private boolean isLightColor(String color) {
|
|
1004
|
+
try {
|
|
1005
|
+
int parsed = Color.parseColor(color);
|
|
1006
|
+
int red = Color.red(parsed);
|
|
1007
|
+
int green = Color.green(parsed);
|
|
1008
|
+
int blue = Color.blue(parsed);
|
|
1009
|
+
double luminance = (0.2126 * red) + (0.7152 * green) + (0.0722 * blue);
|
|
1010
|
+
return luminance > 158;
|
|
1011
|
+
} catch (Exception ignored) {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
private void applySystemBarIconContrast(Window window, boolean darkStatusIcons, boolean darkNavigationIcons) {
|
|
1017
|
+
View decor = window.getDecorView();
|
|
1018
|
+
int flags = decor.getSystemUiVisibility();
|
|
1019
|
+
|
|
1020
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1021
|
+
if (darkStatusIcons) {
|
|
1022
|
+
flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
|
1023
|
+
} else {
|
|
1024
|
+
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
1029
|
+
if (darkNavigationIcons) {
|
|
1030
|
+
flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
|
1031
|
+
} else {
|
|
1032
|
+
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
decor.setSystemUiVisibility(flags);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
431
1039
|
private void copyText(String text) {
|
|
432
1040
|
ClipboardManager clipboard = (ClipboardManager) context().getSystemService(Context.CLIPBOARD_SERVICE);
|
|
433
1041
|
if (clipboard != null) {
|
|
@@ -451,33 +1059,926 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
451
1059
|
cordova.getActivity().startActivity(intent);
|
|
452
1060
|
}
|
|
453
1061
|
|
|
454
|
-
private
|
|
455
|
-
|
|
1062
|
+
private boolean hasCameraPermission() {
|
|
1063
|
+
return ContextCompat.checkSelfPermission(context(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
private void requestCameraPermission(CallbackContext callbackContext) throws Exception {
|
|
1067
|
+
if (hasCameraPermission()) {
|
|
1068
|
+
callbackContext.success(runtimePermissionResult(Manifest.permission.CAMERA, true, false, true));
|
|
456
1069
|
return;
|
|
457
1070
|
}
|
|
458
1071
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (dispatchToJs) {
|
|
463
|
-
dispatchNotificationClick(detail);
|
|
1072
|
+
if (shouldOpenSettingsForRuntimePermission(Manifest.permission.CAMERA)) {
|
|
1073
|
+
callbackContext.success(openSettingsForRuntimePermission(Manifest.permission.CAMERA, true, false));
|
|
1074
|
+
return;
|
|
464
1075
|
}
|
|
465
1076
|
|
|
466
|
-
|
|
467
|
-
|
|
1077
|
+
cameraPermissionCallback = callbackContext;
|
|
1078
|
+
rememberRuntimePermissionRequest(Manifest.permission.CAMERA);
|
|
1079
|
+
cordova.requestPermission(this, REQUEST_CAMERA, Manifest.permission.CAMERA);
|
|
468
1080
|
}
|
|
469
1081
|
|
|
470
|
-
private
|
|
471
|
-
|
|
1082
|
+
private boolean hasMicrophonePermission() {
|
|
1083
|
+
return ContextCompat.checkSelfPermission(context(), Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
private void requestMicrophonePermission(CallbackContext callbackContext) throws Exception {
|
|
1087
|
+
if (hasMicrophonePermission()) {
|
|
1088
|
+
JSONObject result = microphoneStatus();
|
|
1089
|
+
result.put("requested", false);
|
|
1090
|
+
result.put("granted", true);
|
|
1091
|
+
callbackContext.success(result);
|
|
472
1092
|
return;
|
|
473
1093
|
}
|
|
474
1094
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
1095
|
+
if (shouldOpenSettingsForRuntimePermission(Manifest.permission.RECORD_AUDIO)) {
|
|
1096
|
+
callbackContext.success(openSettingsForRuntimePermission(Manifest.permission.RECORD_AUDIO, true, false));
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
479
1099
|
|
|
480
|
-
|
|
1100
|
+
microphonePermissionCallback = callbackContext;
|
|
1101
|
+
rememberRuntimePermissionRequest(Manifest.permission.RECORD_AUDIO);
|
|
1102
|
+
cordova.requestPermission(this, REQUEST_RECORD_AUDIO, Manifest.permission.RECORD_AUDIO);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
private JSONObject microphoneStatus() throws Exception {
|
|
1106
|
+
JSONObject result = new JSONObject();
|
|
1107
|
+
result.put("permission", "android.permission.RECORD_AUDIO");
|
|
1108
|
+
result.put("required", true);
|
|
1109
|
+
result.put("granted", hasMicrophonePermission());
|
|
1110
|
+
result.put("permissionGranted", hasMicrophonePermission());
|
|
1111
|
+
result.put("requiresSettings", !hasMicrophonePermission() && shouldOpenSettingsForRuntimePermission(Manifest.permission.RECORD_AUDIO));
|
|
1112
|
+
result.put("settingsOpened", false);
|
|
1113
|
+
result.put("recording", micRecorder != null);
|
|
1114
|
+
result.put("gravando", micRecorder != null);
|
|
1115
|
+
if (micRecorder != null) {
|
|
1116
|
+
result.put("startedAt", micRecordingStartedAt);
|
|
1117
|
+
result.put("iniciadoEm", micRecordingStartedAt);
|
|
1118
|
+
result.put("durationMs", System.currentTimeMillis() - micRecordingStartedAt);
|
|
1119
|
+
result.put("duracaoMs", System.currentTimeMillis() - micRecordingStartedAt);
|
|
1120
|
+
}
|
|
1121
|
+
return result;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
private void startMicRecording(CallbackContext callbackContext) throws Exception {
|
|
1125
|
+
if (micRecorder != null) {
|
|
1126
|
+
JSONObject result = microphoneStatus();
|
|
1127
|
+
result.put("alreadyRecording", true);
|
|
1128
|
+
result.put("jaGravando", true);
|
|
1129
|
+
callbackContext.success(result);
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
if (!hasMicrophonePermission()) {
|
|
1134
|
+
if (shouldOpenSettingsForRuntimePermission(Manifest.permission.RECORD_AUDIO)) {
|
|
1135
|
+
callbackContext.success(openSettingsForRuntimePermission(Manifest.permission.RECORD_AUDIO, true, false));
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
pendingMicStartCallback = callbackContext;
|
|
1140
|
+
rememberRuntimePermissionRequest(Manifest.permission.RECORD_AUDIO);
|
|
1141
|
+
cordova.requestPermission(this, REQUEST_RECORD_AUDIO, Manifest.permission.RECORD_AUDIO);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
startMicRecorder(callbackContext);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
private void startMicRecorder(CallbackContext callbackContext) throws Exception {
|
|
1149
|
+
File audioDir = new File(context().getCacheDir(), "html2apk-audio");
|
|
1150
|
+
if (!audioDir.exists() && !audioDir.mkdirs()) {
|
|
1151
|
+
throw new Exception("Could not create audio cache directory.");
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
File audioFile = File.createTempFile("mic-", ".m4a", audioDir);
|
|
1155
|
+
MediaRecorder recorder = new MediaRecorder();
|
|
1156
|
+
try {
|
|
1157
|
+
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
|
1158
|
+
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
|
|
1159
|
+
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
|
|
1160
|
+
recorder.setAudioEncodingBitRate(128000);
|
|
1161
|
+
recorder.setAudioSamplingRate(44100);
|
|
1162
|
+
recorder.setOutputFile(audioFile.getAbsolutePath());
|
|
1163
|
+
recorder.prepare();
|
|
1164
|
+
recorder.start();
|
|
1165
|
+
} catch (Exception error) {
|
|
1166
|
+
try {
|
|
1167
|
+
recorder.release();
|
|
1168
|
+
} catch (Exception ignored) {
|
|
1169
|
+
}
|
|
1170
|
+
audioFile.delete();
|
|
1171
|
+
throw error;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
micRecordingFile = audioFile;
|
|
1175
|
+
micRecordingStartedAt = System.currentTimeMillis();
|
|
1176
|
+
micRecorder = recorder;
|
|
1177
|
+
|
|
1178
|
+
JSONObject result = microphoneStatus();
|
|
1179
|
+
result.put("mimeType", "audio/mp4");
|
|
1180
|
+
result.put("extension", "m4a");
|
|
1181
|
+
result.put("extensao", "m4a");
|
|
1182
|
+
result.put("fileName", micRecordingFile.getName());
|
|
1183
|
+
result.put("nomeArquivo", micRecordingFile.getName());
|
|
1184
|
+
callbackContext.success(result);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
private JSONObject stopMicRecording() throws Exception {
|
|
1188
|
+
if (micRecorder == null || micRecordingFile == null) {
|
|
1189
|
+
throw new Exception("Microphone is not recording. Call ouvirMic() first.");
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
MediaRecorder recorder = micRecorder;
|
|
1193
|
+
File audioFile = micRecordingFile;
|
|
1194
|
+
long startedAt = micRecordingStartedAt;
|
|
1195
|
+
long endedAt;
|
|
1196
|
+
Exception stopError = null;
|
|
1197
|
+
|
|
1198
|
+
micRecorder = null;
|
|
1199
|
+
micRecordingFile = null;
|
|
1200
|
+
micRecordingStartedAt = 0;
|
|
1201
|
+
|
|
1202
|
+
try {
|
|
1203
|
+
recorder.stop();
|
|
1204
|
+
} catch (RuntimeException error) {
|
|
1205
|
+
stopError = new Exception("Could not finalize audio recording. Wait a little longer before calling pararMic().");
|
|
1206
|
+
} finally {
|
|
1207
|
+
try {
|
|
1208
|
+
recorder.reset();
|
|
1209
|
+
} catch (Exception ignored) {
|
|
1210
|
+
}
|
|
1211
|
+
try {
|
|
1212
|
+
recorder.release();
|
|
1213
|
+
} catch (Exception ignored) {
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
if (stopError != null) {
|
|
1218
|
+
audioFile.delete();
|
|
1219
|
+
throw stopError;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
endedAt = System.currentTimeMillis();
|
|
1223
|
+
byte[] audioBytes = readFileBytes(audioFile);
|
|
1224
|
+
String base64 = Base64.encodeToString(audioBytes, Base64.NO_WRAP);
|
|
1225
|
+
audioFile.delete();
|
|
1226
|
+
|
|
1227
|
+
JSONObject result = new JSONObject();
|
|
1228
|
+
result.put("base64", base64);
|
|
1229
|
+
result.put("mimeType", "audio/mp4");
|
|
1230
|
+
result.put("extension", "m4a");
|
|
1231
|
+
result.put("extensao", "m4a");
|
|
1232
|
+
result.put("size", audioBytes.length);
|
|
1233
|
+
result.put("tamanho", audioBytes.length);
|
|
1234
|
+
result.put("startedAt", startedAt);
|
|
1235
|
+
result.put("iniciadoEm", startedAt);
|
|
1236
|
+
result.put("endedAt", endedAt);
|
|
1237
|
+
result.put("finalizadoEm", endedAt);
|
|
1238
|
+
result.put("durationMs", endedAt - startedAt);
|
|
1239
|
+
result.put("duracaoMs", endedAt - startedAt);
|
|
1240
|
+
result.put("recording", false);
|
|
1241
|
+
result.put("gravando", false);
|
|
1242
|
+
return result;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
private byte[] readFileBytes(File file) throws Exception {
|
|
1246
|
+
InputStream inputStream = new FileInputStream(file);
|
|
1247
|
+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
1248
|
+
try {
|
|
1249
|
+
byte[] buffer = new byte[8192];
|
|
1250
|
+
int read;
|
|
1251
|
+
while ((read = inputStream.read(buffer)) != -1) {
|
|
1252
|
+
outputStream.write(buffer, 0, read);
|
|
1253
|
+
}
|
|
1254
|
+
return outputStream.toByteArray();
|
|
1255
|
+
} finally {
|
|
1256
|
+
inputStream.close();
|
|
1257
|
+
outputStream.close();
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
private void stopMicRecorderSilently() {
|
|
1262
|
+
if (micRecorder == null) {
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
MediaRecorder recorder = micRecorder;
|
|
1267
|
+
File audioFile = micRecordingFile;
|
|
1268
|
+
micRecorder = null;
|
|
1269
|
+
micRecordingFile = null;
|
|
1270
|
+
micRecordingStartedAt = 0;
|
|
1271
|
+
|
|
1272
|
+
try {
|
|
1273
|
+
recorder.stop();
|
|
1274
|
+
} catch (Exception ignored) {
|
|
1275
|
+
}
|
|
1276
|
+
try {
|
|
1277
|
+
recorder.reset();
|
|
1278
|
+
} catch (Exception ignored) {
|
|
1279
|
+
}
|
|
1280
|
+
try {
|
|
1281
|
+
recorder.release();
|
|
1282
|
+
} catch (Exception ignored) {
|
|
1283
|
+
}
|
|
1284
|
+
if (audioFile != null) {
|
|
1285
|
+
audioFile.delete();
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
private String torchCameraId() throws Exception {
|
|
1290
|
+
CameraManager manager = (CameraManager) context().getSystemService(Context.CAMERA_SERVICE);
|
|
1291
|
+
if (manager == null) {
|
|
1292
|
+
throw new Exception("Camera service is not available.");
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
for (String cameraId : manager.getCameraIdList()) {
|
|
1296
|
+
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
|
|
1297
|
+
Boolean flashAvailable = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
|
|
1298
|
+
if (Boolean.TRUE.equals(flashAvailable)) {
|
|
1299
|
+
return cameraId;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
throw new Exception("This device does not expose a flashlight.");
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
private void setFlashlight(boolean enabled) throws Exception {
|
|
1307
|
+
if (!hasCameraPermission()) {
|
|
1308
|
+
throw new Exception("CAMERA permission is not granted.");
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
CameraManager manager = (CameraManager) context().getSystemService(Context.CAMERA_SERVICE);
|
|
1312
|
+
if (manager == null) {
|
|
1313
|
+
throw new Exception("Camera service is not available.");
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
manager.setTorchMode(torchCameraId(), enabled);
|
|
1317
|
+
torchEnabled = enabled;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
private void setFlashlightWithPermission(boolean enabled, boolean toggle, CallbackContext callbackContext) throws Exception {
|
|
1321
|
+
if (!hasCameraPermission()) {
|
|
1322
|
+
if (shouldOpenSettingsForRuntimePermission(Manifest.permission.CAMERA)) {
|
|
1323
|
+
callbackContext.success(openSettingsForRuntimePermission(Manifest.permission.CAMERA, true, false));
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
pendingFlashlightCallback = callbackContext;
|
|
1328
|
+
pendingFlashlightEnabled = enabled;
|
|
1329
|
+
pendingFlashlightToggle = toggle;
|
|
1330
|
+
rememberRuntimePermissionRequest(Manifest.permission.CAMERA);
|
|
1331
|
+
cordova.requestPermission(this, REQUEST_CAMERA, Manifest.permission.CAMERA);
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
setFlashlight(toggle ? !torchEnabled : enabled);
|
|
1336
|
+
callbackContext.success(flashlightStatus());
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
private JSONObject flashlightStatus() throws Exception {
|
|
1340
|
+
JSONObject result = new JSONObject();
|
|
1341
|
+
result.put("available", context().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH));
|
|
1342
|
+
result.put("enabled", torchEnabled);
|
|
1343
|
+
result.put("permission", "android.permission.CAMERA");
|
|
1344
|
+
result.put("permissionGranted", hasCameraPermission());
|
|
1345
|
+
result.put("requiresSettings", !hasCameraPermission() && shouldOpenSettingsForRuntimePermission(Manifest.permission.CAMERA));
|
|
1346
|
+
result.put("settingsOpened", false);
|
|
1347
|
+
return result;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
private String readText() {
|
|
1351
|
+
ClipboardManager clipboard = (ClipboardManager) context().getSystemService(Context.CLIPBOARD_SERVICE);
|
|
1352
|
+
if (clipboard == null || !clipboard.hasPrimaryClip() || clipboard.getPrimaryClip() == null) {
|
|
1353
|
+
return "";
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
ClipData clip = clipboard.getPrimaryClip();
|
|
1357
|
+
if (clip.getItemCount() == 0 || clip.getItemAt(0) == null) {
|
|
1358
|
+
return "";
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
CharSequence text = clip.getItemAt(0).coerceToText(context());
|
|
1362
|
+
return text == null ? "" : text.toString();
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
private void share(JSONObject options) {
|
|
1366
|
+
JSONObject safeOptions = options == null ? new JSONObject() : options;
|
|
1367
|
+
String text = safeOptions.optString("texto", safeOptions.optString("text", ""));
|
|
1368
|
+
String url = safeOptions.optString("url", "");
|
|
1369
|
+
String title = safeOptions.optString("titulo", safeOptions.optString("title", "Compartilhar"));
|
|
1370
|
+
StringBuilder content = new StringBuilder();
|
|
1371
|
+
if (text.length() > 0) {
|
|
1372
|
+
content.append(text);
|
|
1373
|
+
}
|
|
1374
|
+
if (url.length() > 0) {
|
|
1375
|
+
if (content.length() > 0) {
|
|
1376
|
+
content.append("\n");
|
|
1377
|
+
}
|
|
1378
|
+
content.append(url);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
Intent intent = new Intent(Intent.ACTION_SEND);
|
|
1382
|
+
intent.setType(safeOptions.optString("mimeType", "text/plain"));
|
|
1383
|
+
intent.putExtra(Intent.EXTRA_TEXT, content.toString());
|
|
1384
|
+
intent.putExtra(Intent.EXTRA_TITLE, title);
|
|
1385
|
+
cordova.getActivity().startActivity(Intent.createChooser(intent, title));
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
private void dial(String phone) throws Exception {
|
|
1389
|
+
String cleaned = phone == null ? "" : phone.trim();
|
|
1390
|
+
if (cleaned.length() == 0) {
|
|
1391
|
+
throw new Exception("Phone number is required.");
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + Uri.encode(cleaned)));
|
|
1395
|
+
cordova.getActivity().startActivity(intent);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
private void openMap(String query) throws Exception {
|
|
1399
|
+
String cleaned = query == null ? "" : query.trim();
|
|
1400
|
+
if (cleaned.length() == 0) {
|
|
1401
|
+
throw new Exception("Map query is required.");
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
Uri uri = cleaned.startsWith("geo:") || cleaned.startsWith("http")
|
|
1405
|
+
? Uri.parse(cleaned)
|
|
1406
|
+
: Uri.parse("geo:0,0?q=" + Uri.encode(cleaned));
|
|
1407
|
+
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
|
1408
|
+
cordova.getActivity().startActivity(intent);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
private void openWhatsapp(String phone, String message) throws Exception {
|
|
1412
|
+
String digits = phone == null ? "" : phone.replaceAll("[^0-9]", "");
|
|
1413
|
+
if (digits.length() == 0) {
|
|
1414
|
+
throw new Exception("WhatsApp phone number is required.");
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
String url = "https://wa.me/" + digits;
|
|
1418
|
+
if (message != null && message.trim().length() > 0) {
|
|
1419
|
+
url += "?text=" + Uri.encode(message.trim());
|
|
1420
|
+
}
|
|
1421
|
+
openUrl(url);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
private void pickFile(JSONObject options, CallbackContext callbackContext) {
|
|
1425
|
+
JSONObject safeOptions = options == null ? new JSONObject() : options;
|
|
1426
|
+
String kind = safeOptions.optString("tipo", safeOptions.optString("kind", "file"));
|
|
1427
|
+
boolean multiple = safeOptions.optBoolean("multiplo", safeOptions.optBoolean("multiple", false));
|
|
1428
|
+
String mimeType = mimeTypeForPicker(kind, safeOptions);
|
|
1429
|
+
|
|
1430
|
+
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
|
1431
|
+
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
1432
|
+
intent.setType(mimeType);
|
|
1433
|
+
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
|
|
1434
|
+
|
|
1435
|
+
filePickerCallback = callbackContext;
|
|
1436
|
+
cordova.startActivityForResult(this, intent, REQUEST_PICK_FILE);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
private String mimeTypeForPicker(String kind, JSONObject options) {
|
|
1440
|
+
String type = options.optString("mimeType", options.optString("tipoMime", ""));
|
|
1441
|
+
if (type.length() > 0) {
|
|
1442
|
+
return type;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
JSONArray types = options.optJSONArray("tipos");
|
|
1446
|
+
if (types == null) {
|
|
1447
|
+
types = options.optJSONArray("types");
|
|
1448
|
+
}
|
|
1449
|
+
if (types != null && types.length() == 1) {
|
|
1450
|
+
return types.optString(0, "*/*");
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
if ("image".equals(kind)) {
|
|
1454
|
+
return "image/*";
|
|
1455
|
+
}
|
|
1456
|
+
if ("video".equals(kind)) {
|
|
1457
|
+
return "video/*";
|
|
1458
|
+
}
|
|
1459
|
+
if ("media".equals(kind)) {
|
|
1460
|
+
return "image/*";
|
|
1461
|
+
}
|
|
1462
|
+
return "*/*";
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
private void pickFolder(CallbackContext callbackContext) {
|
|
1466
|
+
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
|
1467
|
+
folderPickerCallback = callbackContext;
|
|
1468
|
+
cordova.startActivityForResult(this, intent, REQUEST_PICK_FOLDER);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
private void saveFile(JSONObject options, CallbackContext callbackContext) throws Exception {
|
|
1472
|
+
JSONObject safeOptions = options == null ? new JSONObject() : options;
|
|
1473
|
+
String name = safeOptions.optString("nome", safeOptions.optString("name", "arquivo.txt"));
|
|
1474
|
+
String mimeType = safeOptions.optString("mimeType", "text/plain");
|
|
1475
|
+
|
|
1476
|
+
pendingSaveFile = safeOptions;
|
|
1477
|
+
saveFileCallback = callbackContext;
|
|
1478
|
+
|
|
1479
|
+
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
|
1480
|
+
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
1481
|
+
intent.setType(mimeType);
|
|
1482
|
+
intent.putExtra(Intent.EXTRA_TITLE, name);
|
|
1483
|
+
cordova.startActivityForResult(this, intent, REQUEST_SAVE_FILE);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
private void handlePickFileResult(int resultCode, Intent intent) {
|
|
1487
|
+
CallbackContext callback = filePickerCallback;
|
|
1488
|
+
filePickerCallback = null;
|
|
1489
|
+
if (callback == null) {
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
if (resultCode != Activity.RESULT_OK || intent == null) {
|
|
1493
|
+
callback.success(new JSONArray());
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
try {
|
|
1498
|
+
JSONArray items = new JSONArray();
|
|
1499
|
+
if (intent.getClipData() != null) {
|
|
1500
|
+
ClipData clipData = intent.getClipData();
|
|
1501
|
+
for (int index = 0; index < clipData.getItemCount(); index += 1) {
|
|
1502
|
+
items.put(fileInfo(clipData.getItemAt(index).getUri()));
|
|
1503
|
+
}
|
|
1504
|
+
} else if (intent.getData() != null) {
|
|
1505
|
+
items.put(fileInfo(intent.getData()));
|
|
1506
|
+
}
|
|
1507
|
+
callback.success(items);
|
|
1508
|
+
} catch (Exception error) {
|
|
1509
|
+
callback.error(error.getMessage());
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
private void handlePickFolderResult(int resultCode, Intent intent) {
|
|
1514
|
+
CallbackContext callback = folderPickerCallback;
|
|
1515
|
+
folderPickerCallback = null;
|
|
1516
|
+
if (callback == null) {
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
if (resultCode != Activity.RESULT_OK || intent == null || intent.getData() == null) {
|
|
1520
|
+
callback.success(new JSONObject());
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
try {
|
|
1525
|
+
JSONObject result = new JSONObject();
|
|
1526
|
+
result.put("uri", intent.getData().toString());
|
|
1527
|
+
callback.success(result);
|
|
1528
|
+
} catch (Exception error) {
|
|
1529
|
+
callback.error(error.getMessage());
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
private void handleSaveFileResult(int resultCode, Intent intent) {
|
|
1534
|
+
CallbackContext callback = saveFileCallback;
|
|
1535
|
+
JSONObject options = pendingSaveFile;
|
|
1536
|
+
saveFileCallback = null;
|
|
1537
|
+
pendingSaveFile = null;
|
|
1538
|
+
if (callback == null) {
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
if (resultCode != Activity.RESULT_OK || intent == null || intent.getData() == null) {
|
|
1542
|
+
callback.success(new JSONObject());
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
try {
|
|
1547
|
+
Uri uri = intent.getData();
|
|
1548
|
+
writePickedFile(uri, options == null ? new JSONObject() : options);
|
|
1549
|
+
JSONObject result = fileInfo(uri);
|
|
1550
|
+
result.put("saved", true);
|
|
1551
|
+
callback.success(result);
|
|
1552
|
+
} catch (Exception error) {
|
|
1553
|
+
callback.error(error.getMessage());
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
private JSONObject fileInfo(Uri uri) throws Exception {
|
|
1558
|
+
JSONObject result = new JSONObject();
|
|
1559
|
+
result.put("uri", uri.toString());
|
|
1560
|
+
result.put("mimeType", context().getContentResolver().getType(uri));
|
|
1561
|
+
|
|
1562
|
+
Cursor cursor = context().getContentResolver().query(uri, null, null, null, null);
|
|
1563
|
+
if (cursor != null) {
|
|
1564
|
+
try {
|
|
1565
|
+
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
|
1566
|
+
int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
|
|
1567
|
+
if (cursor.moveToFirst()) {
|
|
1568
|
+
if (nameIndex >= 0) {
|
|
1569
|
+
result.put("name", cursor.getString(nameIndex));
|
|
1570
|
+
result.put("nome", cursor.getString(nameIndex));
|
|
1571
|
+
}
|
|
1572
|
+
if (sizeIndex >= 0) {
|
|
1573
|
+
result.put("size", cursor.getLong(sizeIndex));
|
|
1574
|
+
result.put("tamanho", cursor.getLong(sizeIndex));
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
} finally {
|
|
1578
|
+
cursor.close();
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
return result;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
private void writePickedFile(Uri uri, JSONObject options) throws Exception {
|
|
1585
|
+
OutputStream outputStream = context().getContentResolver().openOutputStream(uri);
|
|
1586
|
+
if (outputStream == null) {
|
|
1587
|
+
throw new Exception("Could not open output stream.");
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
try {
|
|
1591
|
+
InputStream inputStream;
|
|
1592
|
+
String base64 = options.optString("base64", "");
|
|
1593
|
+
if (base64.length() > 0) {
|
|
1594
|
+
inputStream = new ByteArrayInputStream(Base64.decode(base64, Base64.DEFAULT));
|
|
1595
|
+
} else {
|
|
1596
|
+
String content = options.optString("conteudo", options.optString("content", ""));
|
|
1597
|
+
inputStream = new ByteArrayInputStream(content.getBytes("UTF-8"));
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
byte[] buffer = new byte[8192];
|
|
1601
|
+
int read;
|
|
1602
|
+
while ((read = inputStream.read(buffer)) != -1) {
|
|
1603
|
+
outputStream.write(buffer, 0, read);
|
|
1604
|
+
}
|
|
1605
|
+
inputStream.close();
|
|
1606
|
+
} finally {
|
|
1607
|
+
outputStream.close();
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
private JSONObject deviceInfo() throws Exception {
|
|
1612
|
+
JSONObject result = new JSONObject();
|
|
1613
|
+
result.put("manufacturer", Build.MANUFACTURER);
|
|
1614
|
+
result.put("fabricante", Build.MANUFACTURER);
|
|
1615
|
+
result.put("model", Build.MODEL);
|
|
1616
|
+
result.put("modelo", Build.MODEL);
|
|
1617
|
+
result.put("brand", Build.BRAND);
|
|
1618
|
+
result.put("androidVersion", Build.VERSION.RELEASE);
|
|
1619
|
+
result.put("sdkInt", Build.VERSION.SDK_INT);
|
|
1620
|
+
result.put("packageName", context().getPackageName());
|
|
1621
|
+
return result;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
private JSONObject networkInfo() throws Exception {
|
|
1625
|
+
JSONObject result = new JSONObject();
|
|
1626
|
+
ConnectivityManager manager = (ConnectivityManager) context().getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
1627
|
+
boolean connected = false;
|
|
1628
|
+
String type = "unknown";
|
|
1629
|
+
|
|
1630
|
+
if (manager != null) {
|
|
1631
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1632
|
+
Network network = manager.getActiveNetwork();
|
|
1633
|
+
NetworkCapabilities capabilities = network == null ? null : manager.getNetworkCapabilities(network);
|
|
1634
|
+
connected = capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
|
1635
|
+
if (capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
|
|
1636
|
+
type = "wifi";
|
|
1637
|
+
} else if (capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
|
|
1638
|
+
type = "cellular";
|
|
1639
|
+
} else if (capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
|
|
1640
|
+
type = "ethernet";
|
|
1641
|
+
}
|
|
1642
|
+
} else if (manager.getActiveNetworkInfo() != null) {
|
|
1643
|
+
connected = manager.getActiveNetworkInfo().isConnected();
|
|
1644
|
+
type = manager.getActiveNetworkInfo().getTypeName().toLowerCase();
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
result.put("online", connected);
|
|
1649
|
+
result.put("connected", connected);
|
|
1650
|
+
result.put("tipo", type);
|
|
1651
|
+
result.put("type", type);
|
|
1652
|
+
return result;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
private JSONObject batteryInfo() throws Exception {
|
|
1656
|
+
Intent intent = context().registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
|
1657
|
+
JSONObject result = new JSONObject();
|
|
1658
|
+
if (intent == null) {
|
|
1659
|
+
result.put("level", -1);
|
|
1660
|
+
result.put("nivel", -1);
|
|
1661
|
+
result.put("charging", false);
|
|
1662
|
+
return result;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
|
1666
|
+
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
|
|
1667
|
+
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
|
|
1668
|
+
boolean charging = status == BatteryManager.BATTERY_STATUS_CHARGING
|
|
1669
|
+
|| status == BatteryManager.BATTERY_STATUS_FULL;
|
|
1670
|
+
double percent = scale <= 0 ? -1 : (level * 100.0 / scale);
|
|
1671
|
+
|
|
1672
|
+
result.put("level", percent);
|
|
1673
|
+
result.put("nivel", percent);
|
|
1674
|
+
result.put("charging", charging);
|
|
1675
|
+
result.put("carregando", charging);
|
|
1676
|
+
return result;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
private JSONObject memoryInfo() throws Exception {
|
|
1680
|
+
ActivityManager manager = (ActivityManager) context().getSystemService(Context.ACTIVITY_SERVICE);
|
|
1681
|
+
ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
|
|
1682
|
+
if (manager != null) {
|
|
1683
|
+
manager.getMemoryInfo(info);
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
Runtime runtime = Runtime.getRuntime();
|
|
1687
|
+
JSONObject result = new JSONObject();
|
|
1688
|
+
result.put("availableBytes", info.availMem);
|
|
1689
|
+
result.put("totalBytes", Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN ? info.totalMem : -1);
|
|
1690
|
+
result.put("lowMemory", info.lowMemory);
|
|
1691
|
+
result.put("thresholdBytes", info.threshold);
|
|
1692
|
+
result.put("appUsedBytes", runtime.totalMemory() - runtime.freeMemory());
|
|
1693
|
+
result.put("appMaxBytes", runtime.maxMemory());
|
|
1694
|
+
return result;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
private JSONObject storageInfo() throws Exception {
|
|
1698
|
+
JSONObject result = new JSONObject();
|
|
1699
|
+
result.put("internal", statFsInfo(Environment.getDataDirectory()));
|
|
1700
|
+
result.put("cache", statFsInfo(context().getCacheDir()));
|
|
1701
|
+
if (context().getExternalFilesDir(null) != null) {
|
|
1702
|
+
result.put("appExternal", statFsInfo(context().getExternalFilesDir(null)));
|
|
1703
|
+
}
|
|
1704
|
+
return result;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
private JSONObject statFsInfo(java.io.File file) throws Exception {
|
|
1708
|
+
StatFs statFs = new StatFs(file.getAbsolutePath());
|
|
1709
|
+
long blockSize = statFs.getBlockSizeLong();
|
|
1710
|
+
long total = statFs.getBlockCountLong() * blockSize;
|
|
1711
|
+
long available = statFs.getAvailableBlocksLong() * blockSize;
|
|
1712
|
+
JSONObject result = new JSONObject();
|
|
1713
|
+
result.put("path", file.getAbsolutePath());
|
|
1714
|
+
result.put("totalBytes", total);
|
|
1715
|
+
result.put("availableBytes", available);
|
|
1716
|
+
result.put("usedBytes", total - available);
|
|
1717
|
+
return result;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
private JSONObject performanceInfo() throws Exception {
|
|
1721
|
+
JSONObject result = new JSONObject();
|
|
1722
|
+
result.put("timestamp", System.currentTimeMillis());
|
|
1723
|
+
result.put("memory", memoryInfo());
|
|
1724
|
+
result.put("storage", storageInfo());
|
|
1725
|
+
result.put("battery", batteryInfo());
|
|
1726
|
+
result.put("network", networkInfo());
|
|
1727
|
+
return result;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
private JSONObject openAppsMemoryInfo() throws Exception {
|
|
1731
|
+
ActivityManager manager = (ActivityManager) context().getSystemService(Context.ACTIVITY_SERVICE);
|
|
1732
|
+
JSONArray apps = new JSONArray();
|
|
1733
|
+
JSONObject byName = new JSONObject();
|
|
1734
|
+
JSONObject result = new JSONObject();
|
|
1735
|
+
|
|
1736
|
+
result.put("timestamp", System.currentTimeMillis());
|
|
1737
|
+
result.put("limited", true);
|
|
1738
|
+
result.put("observacao", "Android moderno limita a lista de apps de terceiros por privacidade; o retorno mostra apenas processos visiveis para este app.");
|
|
1739
|
+
result.put("note", "Modern Android limits third-party app process visibility for privacy; this returns only processes visible to this app.");
|
|
1740
|
+
|
|
1741
|
+
if (manager == null) {
|
|
1742
|
+
result.put("apps", apps);
|
|
1743
|
+
result.put("porNome", byName);
|
|
1744
|
+
result.put("byName", byName);
|
|
1745
|
+
return result;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
List<ActivityManager.RunningAppProcessInfo> processes = manager.getRunningAppProcesses();
|
|
1749
|
+
if (processes == null) {
|
|
1750
|
+
result.put("apps", apps);
|
|
1751
|
+
result.put("porNome", byName);
|
|
1752
|
+
result.put("byName", byName);
|
|
1753
|
+
return result;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
for (ActivityManager.RunningAppProcessInfo process : processes) {
|
|
1757
|
+
Debug.MemoryInfo[] memoryInfos = manager.getProcessMemoryInfo(new int[] { process.pid });
|
|
1758
|
+
Debug.MemoryInfo memory = memoryInfos != null && memoryInfos.length > 0 ? memoryInfos[0] : null;
|
|
1759
|
+
long pssKb = memory == null ? 0 : memory.getTotalPss();
|
|
1760
|
+
long ramBytes = pssKb * 1024L;
|
|
1761
|
+
String packageName = process.pkgList != null && process.pkgList.length > 0 ? process.pkgList[0] : process.processName;
|
|
1762
|
+
String name = appLabel(packageName, process.processName);
|
|
1763
|
+
|
|
1764
|
+
JSONObject item = new JSONObject();
|
|
1765
|
+
item.put("name", name);
|
|
1766
|
+
item.put("nome", name);
|
|
1767
|
+
item.put("packageName", packageName);
|
|
1768
|
+
item.put("pacote", packageName);
|
|
1769
|
+
item.put("processName", process.processName);
|
|
1770
|
+
item.put("pid", process.pid);
|
|
1771
|
+
item.put("importance", process.importance);
|
|
1772
|
+
item.put("importanceName", importanceName(process.importance));
|
|
1773
|
+
item.put("ramBytes", ramBytes);
|
|
1774
|
+
item.put("ramMb", Math.round((ramBytes / 1024.0 / 1024.0) * 100.0) / 100.0);
|
|
1775
|
+
item.put("pssKb", pssKb);
|
|
1776
|
+
if (memory != null) {
|
|
1777
|
+
item.put("privateDirtyKb", memory.getTotalPrivateDirty());
|
|
1778
|
+
item.put("sharedDirtyKb", memory.getTotalSharedDirty());
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
JSONArray packages = new JSONArray();
|
|
1782
|
+
if (process.pkgList != null) {
|
|
1783
|
+
for (String packageItem : process.pkgList) {
|
|
1784
|
+
packages.put(packageItem);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
item.put("packages", packages);
|
|
1788
|
+
item.put("pacotes", packages);
|
|
1789
|
+
apps.put(item);
|
|
1790
|
+
|
|
1791
|
+
JSONObject summary = new JSONObject();
|
|
1792
|
+
summary.put("ramBytes", ramBytes);
|
|
1793
|
+
summary.put("ramMb", Math.round((ramBytes / 1024.0 / 1024.0) * 100.0) / 100.0);
|
|
1794
|
+
summary.put("packageName", packageName);
|
|
1795
|
+
summary.put("processName", process.processName);
|
|
1796
|
+
byName.put(name, summary);
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
result.put("apps", apps);
|
|
1800
|
+
result.put("porNome", byName);
|
|
1801
|
+
result.put("byName", byName);
|
|
1802
|
+
return result;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
private String appLabel(String packageName, String fallback) {
|
|
1806
|
+
try {
|
|
1807
|
+
PackageManager packageManager = context().getPackageManager();
|
|
1808
|
+
ApplicationInfo info = packageManager.getApplicationInfo(packageName, 0);
|
|
1809
|
+
CharSequence label = packageManager.getApplicationLabel(info);
|
|
1810
|
+
return label == null ? fallback : label.toString();
|
|
1811
|
+
} catch (Exception ignored) {
|
|
1812
|
+
return fallback == null ? packageName : fallback;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
private String importanceName(int importance) {
|
|
1817
|
+
if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
|
|
1818
|
+
return "foreground";
|
|
1819
|
+
}
|
|
1820
|
+
if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
|
|
1821
|
+
return "visible";
|
|
1822
|
+
}
|
|
1823
|
+
if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE) {
|
|
1824
|
+
return "service";
|
|
1825
|
+
}
|
|
1826
|
+
if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED) {
|
|
1827
|
+
return "cached";
|
|
1828
|
+
}
|
|
1829
|
+
return "other";
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
private JSONObject permissionStatus(JSONArray requested) throws Exception {
|
|
1833
|
+
JSONArray permissions = requested == null ? new JSONArray() : requested;
|
|
1834
|
+
JSONObject result = new JSONObject();
|
|
1835
|
+
for (int index = 0; index < permissions.length(); index += 1) {
|
|
1836
|
+
String permission = androidPermissionName(permissions.optString(index, ""));
|
|
1837
|
+
if (permission.length() == 0) {
|
|
1838
|
+
continue;
|
|
1839
|
+
}
|
|
1840
|
+
result.put(permission, ContextCompat.checkSelfPermission(context(), permission) == PackageManager.PERMISSION_GRANTED);
|
|
1841
|
+
}
|
|
1842
|
+
return result;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
private String androidPermissionName(String permission) {
|
|
1846
|
+
String value = permission == null ? "" : permission.trim();
|
|
1847
|
+
if (value.length() == 0 || value.indexOf('.') >= 0) {
|
|
1848
|
+
return value;
|
|
1849
|
+
}
|
|
1850
|
+
return "android.permission." + value;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
private JSONObject baseEvent(String type) {
|
|
1854
|
+
JSONObject detail = new JSONObject();
|
|
1855
|
+
try {
|
|
1856
|
+
detail.put("type", type);
|
|
1857
|
+
detail.put("tipo", type);
|
|
1858
|
+
detail.put("timestamp", System.currentTimeMillis());
|
|
1859
|
+
} catch (Exception ignored) {
|
|
1860
|
+
}
|
|
1861
|
+
return detail;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
private void registerSystemReceiver() {
|
|
1865
|
+
if (systemReceiver != null) {
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
systemReceiver = new BroadcastReceiver() {
|
|
1870
|
+
@Override
|
|
1871
|
+
public void onReceive(Context context, Intent intent) {
|
|
1872
|
+
if (intent == null || intent.getAction() == null) {
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
try {
|
|
1877
|
+
if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
|
|
1878
|
+
dispatchEvent("bateria:mudou", batteryInfo());
|
|
1879
|
+
} else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
|
|
1880
|
+
dispatchEvent("rede:mudou", networkInfo());
|
|
1881
|
+
}
|
|
1882
|
+
} catch (Exception ignored) {
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
|
|
1887
|
+
IntentFilter filter = new IntentFilter();
|
|
1888
|
+
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
|
|
1889
|
+
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
|
1890
|
+
ContextCompat.registerReceiver(context(), systemReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
private void unregisterSystemReceiver() {
|
|
1894
|
+
if (systemReceiver == null) {
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
try {
|
|
1899
|
+
context().unregisterReceiver(systemReceiver);
|
|
1900
|
+
} catch (Exception ignored) {
|
|
1901
|
+
}
|
|
1902
|
+
systemReceiver = null;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
private void handleLinkIntent(Intent intent, boolean dispatchToJs) {
|
|
1906
|
+
if (intent == null || intent.getData() == null) {
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
JSONObject detail = new JSONObject();
|
|
1911
|
+
try {
|
|
1912
|
+
Uri uri = intent.getData();
|
|
1913
|
+
detail.put("url", uri.toString());
|
|
1914
|
+
detail.put("scheme", uri.getScheme());
|
|
1915
|
+
detail.put("host", uri.getHost());
|
|
1916
|
+
detail.put("path", uri.getPath());
|
|
1917
|
+
detail.put("query", uri.getQuery());
|
|
1918
|
+
detail.put("timestamp", System.currentTimeMillis());
|
|
1919
|
+
} catch (Exception ignored) {
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
initialLink = detail;
|
|
1923
|
+
if (dispatchToJs) {
|
|
1924
|
+
dispatchEvent("link:aberto", detail);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
private void dispatchEvent(String type, JSONObject detail) {
|
|
1929
|
+
if (webView == null) {
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
JSONObject payload = detail == null ? baseEvent(type) : detail;
|
|
1934
|
+
try {
|
|
1935
|
+
payload.put("type", type);
|
|
1936
|
+
payload.put("tipo", type);
|
|
1937
|
+
if (!payload.has("timestamp")) {
|
|
1938
|
+
payload.put("timestamp", System.currentTimeMillis());
|
|
1939
|
+
}
|
|
1940
|
+
} catch (Exception ignored) {
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
final String script = "(function(){var detail=" + payload.toString()
|
|
1944
|
+
+ ";window.dispatchEvent(new CustomEvent('html2apk:event',{detail:detail}));"
|
|
1945
|
+
+ "window.dispatchEvent(new CustomEvent('html2apk:'+detail.type,{detail:detail}));})();";
|
|
1946
|
+
|
|
1947
|
+
cordova.getActivity().runOnUiThread(new Runnable() {
|
|
1948
|
+
@Override
|
|
1949
|
+
public void run() {
|
|
1950
|
+
webView.getEngine().evaluateJavascript(script, null);
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
private void handleNotificationIntent(Intent intent, boolean dispatchToJs) {
|
|
1956
|
+
if (intent == null || !intent.getBooleanExtra(EXTRA_NOTIFICATION_CLICKED, false)) {
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
JSONObject detail = parseDetail(intent.getStringExtra(EXTRA_NOTIFICATION_DETAIL));
|
|
1961
|
+
initialNotification = detail;
|
|
1962
|
+
|
|
1963
|
+
if (dispatchToJs) {
|
|
1964
|
+
dispatchNotificationClick(detail);
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
intent.removeExtra(EXTRA_NOTIFICATION_CLICKED);
|
|
1968
|
+
intent.removeExtra(EXTRA_NOTIFICATION_DETAIL);
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
private void dispatchNotificationClick(JSONObject detail) {
|
|
1972
|
+
if (detail == null || webView == null) {
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
dispatchEvent("notificacao:clicada", detail);
|
|
1977
|
+
|
|
1978
|
+
final String script = "(function(){var detail=" + detail.toString()
|
|
1979
|
+
+ ";window.dispatchEvent(new CustomEvent('html2apk:notification',{detail:detail}));})();";
|
|
1980
|
+
|
|
1981
|
+
cordova.getActivity().runOnUiThread(new Runnable() {
|
|
481
1982
|
@Override
|
|
482
1983
|
public void run() {
|
|
483
1984
|
webView.getEngine().evaluateJavascript(script, null);
|
|
@@ -515,6 +2016,206 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
515
2016
|
);
|
|
516
2017
|
}
|
|
517
2018
|
|
|
2019
|
+
static boolean canScheduleExactAlarms(Context context) {
|
|
2020
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
2021
|
+
return true;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
|
2025
|
+
return alarmManager != null && alarmManager.canScheduleExactAlarms();
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
static JSONArray loopNotifications(JSONObject options) {
|
|
2029
|
+
JSONArray notifications = options.optJSONArray("notificacoes");
|
|
2030
|
+
if (notifications == null) {
|
|
2031
|
+
notifications = options.optJSONArray("notifications");
|
|
2032
|
+
}
|
|
2033
|
+
if (notifications == null) {
|
|
2034
|
+
notifications = options.optJSONArray("items");
|
|
2035
|
+
}
|
|
2036
|
+
return notifications == null ? new JSONArray() : notifications;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
static JSONObject notificationDisplayOptions(JSONObject options) throws Exception {
|
|
2040
|
+
JSONArray notifications = loopNotifications(options);
|
|
2041
|
+
if (notifications.length() == 0) {
|
|
2042
|
+
return options;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
int index = Math.max(0, options.optInt("loopIndex", options.optInt("indiceLoop", 0)));
|
|
2046
|
+
JSONObject current = notifications.optJSONObject(index % notifications.length());
|
|
2047
|
+
JSONObject displayOptions = new JSONObject(options.toString());
|
|
2048
|
+
if (current != null) {
|
|
2049
|
+
JSONArray names = current.names();
|
|
2050
|
+
if (names != null) {
|
|
2051
|
+
for (int nameIndex = 0; nameIndex < names.length(); nameIndex += 1) {
|
|
2052
|
+
String name = names.optString(nameIndex);
|
|
2053
|
+
displayOptions.put(name, current.opt(name));
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
displayOptions.put("id", notificationId(options));
|
|
2059
|
+
displayOptions.put("loopIndex", index);
|
|
2060
|
+
displayOptions.put("indiceLoop", index);
|
|
2061
|
+
displayOptions.put("loopTotal", notifications.length());
|
|
2062
|
+
displayOptions.put("totalLoop", notifications.length());
|
|
2063
|
+
return displayOptions;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
static long repeatInterval(JSONObject options) {
|
|
2067
|
+
long interval = intervalFromObject(options.opt("intervalo"));
|
|
2068
|
+
if (interval <= 0) {
|
|
2069
|
+
interval = intervalFromObject(options.opt("interval"));
|
|
2070
|
+
}
|
|
2071
|
+
if (interval <= 0) {
|
|
2072
|
+
interval = intervalFromObject(options.opt("aCada"));
|
|
2073
|
+
}
|
|
2074
|
+
if (interval <= 0) {
|
|
2075
|
+
interval = intervalFromObject(options.opt("every"));
|
|
2076
|
+
}
|
|
2077
|
+
if (interval <= 0) {
|
|
2078
|
+
interval = intervalFromObject(options.opt("repetir"));
|
|
2079
|
+
}
|
|
2080
|
+
if (interval <= 0) {
|
|
2081
|
+
interval = intervalFromObject(options.opt("repeat"));
|
|
2082
|
+
}
|
|
2083
|
+
return interval;
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
static long nextRepeatTime(JSONObject options, long after) {
|
|
2087
|
+
long interval = repeatInterval(options);
|
|
2088
|
+
if (interval <= 0) {
|
|
2089
|
+
return 0;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
long next = options.optLong("when", options.optLong("quando", after)) + interval;
|
|
2093
|
+
while (next <= after) {
|
|
2094
|
+
next += interval;
|
|
2095
|
+
}
|
|
2096
|
+
return next;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
static JSONObject nextRepeatOptions(JSONObject options, long nextWhen) throws Exception {
|
|
2100
|
+
JSONObject next = new JSONObject(options.toString());
|
|
2101
|
+
JSONArray notifications = loopNotifications(options);
|
|
2102
|
+
int nextIndex = Math.max(0, options.optInt("loopIndex", options.optInt("indiceLoop", 0))) + 1;
|
|
2103
|
+
int runCount = Math.max(0, options.optInt("_runCount", options.optInt("runCount", 0))) + 1;
|
|
2104
|
+
|
|
2105
|
+
next.put("when", nextWhen);
|
|
2106
|
+
next.put("quando", nextWhen);
|
|
2107
|
+
next.put("loopIndex", notifications.length() == 0 ? nextIndex : nextIndex % notifications.length());
|
|
2108
|
+
next.put("indiceLoop", notifications.length() == 0 ? nextIndex : nextIndex % notifications.length());
|
|
2109
|
+
next.put("_runCount", runCount);
|
|
2110
|
+
next.put("runCount", runCount);
|
|
2111
|
+
return next;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
static boolean shouldStopRepeating(JSONObject options, long nextWhen) {
|
|
2115
|
+
long until = options.optLong("ate", options.optLong("until", 0));
|
|
2116
|
+
if (until > 0 && nextWhen > until) {
|
|
2117
|
+
return true;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
int maxRuns = options.optInt("vezes", options.optInt("times", options.optInt("count", options.optInt("limit", 0))));
|
|
2121
|
+
int runCount = Math.max(0, options.optInt("_runCount", options.optInt("runCount", 0))) + 1;
|
|
2122
|
+
return maxRuns > 0 && runCount >= maxRuns;
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
static void rescheduleRepeatingNotification(Context context, int id, JSONObject options, boolean exactAllowed) {
|
|
2126
|
+
try {
|
|
2127
|
+
long nextWhen = nextRepeatTime(options, System.currentTimeMillis());
|
|
2128
|
+
if (nextWhen <= 0 || shouldStopRepeating(options, nextWhen)) {
|
|
2129
|
+
NotificationStore.remove(context, id);
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
JSONObject nextOptions = nextRepeatOptions(options, nextWhen);
|
|
2134
|
+
nextOptions.put("id", id);
|
|
2135
|
+
NotificationStore.save(context, id, nextWhen, nextOptions);
|
|
2136
|
+
NotificationReceiver.schedule(context, id, nextWhen, nextOptions, exactAllowed);
|
|
2137
|
+
} catch (Exception ignored) {
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
private static long intervalFromObject(Object value) {
|
|
2142
|
+
if (value == null || value == JSONObject.NULL) {
|
|
2143
|
+
return 0;
|
|
2144
|
+
}
|
|
2145
|
+
if (value instanceof Number) {
|
|
2146
|
+
return Math.max(0, ((Number) value).longValue());
|
|
2147
|
+
}
|
|
2148
|
+
if (value instanceof JSONObject) {
|
|
2149
|
+
JSONObject object = (JSONObject) value;
|
|
2150
|
+
long nested = intervalFromObject(object.opt("intervalo"));
|
|
2151
|
+
if (nested <= 0) {
|
|
2152
|
+
nested = intervalFromObject(object.opt("interval"));
|
|
2153
|
+
}
|
|
2154
|
+
if (nested <= 0) {
|
|
2155
|
+
nested = intervalFromObject(object.opt("aCada"));
|
|
2156
|
+
}
|
|
2157
|
+
if (nested <= 0) {
|
|
2158
|
+
nested = intervalFromObject(object.opt("every"));
|
|
2159
|
+
}
|
|
2160
|
+
return nested;
|
|
2161
|
+
}
|
|
2162
|
+
if (value instanceof Boolean) {
|
|
2163
|
+
return 0;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
return parseIntervalString(String.valueOf(value));
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
private static long parseIntervalString(String value) {
|
|
2170
|
+
String text = value == null ? "" : value.trim().toLowerCase();
|
|
2171
|
+
if (text.length() == 0) {
|
|
2172
|
+
return 0;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
int unitStart = text.length();
|
|
2176
|
+
for (int index = 0; index < text.length(); index += 1) {
|
|
2177
|
+
char character = text.charAt(index);
|
|
2178
|
+
if (!(Character.isDigit(character) || character == '.')) {
|
|
2179
|
+
unitStart = index;
|
|
2180
|
+
break;
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
try {
|
|
2185
|
+
double amount = Double.parseDouble(text.substring(0, unitStart).trim());
|
|
2186
|
+
String unit = text.substring(unitStart).trim();
|
|
2187
|
+
if ("s".equals(unit) || "seg".equals(unit)) {
|
|
2188
|
+
return Math.round(amount * 1000);
|
|
2189
|
+
}
|
|
2190
|
+
if ("m".equals(unit) || "min".equals(unit)) {
|
|
2191
|
+
return Math.round(amount * 60 * 1000);
|
|
2192
|
+
}
|
|
2193
|
+
if ("h".equals(unit) || "hr".equals(unit)) {
|
|
2194
|
+
return Math.round(amount * 60 * 60 * 1000);
|
|
2195
|
+
}
|
|
2196
|
+
if ("d".equals(unit) || "dia".equals(unit) || "dias".equals(unit)) {
|
|
2197
|
+
return Math.round(amount * 24 * 60 * 60 * 1000);
|
|
2198
|
+
}
|
|
2199
|
+
return Math.round(amount);
|
|
2200
|
+
} catch (Exception ignored) {
|
|
2201
|
+
return 0;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
private static int notificationIdFromObject(Object input) {
|
|
2206
|
+
if (input instanceof Number) {
|
|
2207
|
+
return ((Number) input).intValue();
|
|
2208
|
+
}
|
|
2209
|
+
if (input instanceof JSONObject) {
|
|
2210
|
+
return notificationId((JSONObject) input);
|
|
2211
|
+
}
|
|
2212
|
+
try {
|
|
2213
|
+
return Integer.parseInt(String.valueOf(input));
|
|
2214
|
+
} catch (Exception ignored) {
|
|
2215
|
+
return 0;
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
|
|
518
2219
|
static JSONObject detailPayload(JSONObject options) throws Exception {
|
|
519
2220
|
JSONObject detail = new JSONObject();
|
|
520
2221
|
JSONObject click = options.optJSONObject("aoClicar");
|
|
@@ -534,6 +2235,37 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
534
2235
|
return detail;
|
|
535
2236
|
}
|
|
536
2237
|
|
|
2238
|
+
static void addNotificationActions(NotificationCompat.Builder builder, Context context, int notificationId, JSONObject options) {
|
|
2239
|
+
JSONArray actions = options.optJSONArray("acoes");
|
|
2240
|
+
if (actions == null) {
|
|
2241
|
+
actions = options.optJSONArray("actions");
|
|
2242
|
+
}
|
|
2243
|
+
if (actions == null) {
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
for (int index = 0; index < actions.length(); index += 1) {
|
|
2248
|
+
JSONObject action = actions.optJSONObject(index);
|
|
2249
|
+
if (action == null) {
|
|
2250
|
+
continue;
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
try {
|
|
2254
|
+
String title = action.optString("titulo", action.optString("title", action.optString("id", "Abrir")));
|
|
2255
|
+
JSONObject detail = detailPayload(options);
|
|
2256
|
+
detail.put("notificationAction", action.optString("id", String.valueOf(index)));
|
|
2257
|
+
detail.put("acaoNotificacao", action.optString("id", String.valueOf(index)));
|
|
2258
|
+
detail.put("action", action);
|
|
2259
|
+
builder.addAction(
|
|
2260
|
+
context.getApplicationInfo().icon,
|
|
2261
|
+
title,
|
|
2262
|
+
createContentIntent(context, notificationId * 100 + index + 1, detail)
|
|
2263
|
+
);
|
|
2264
|
+
} catch (Exception ignored) {
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
|
|
537
2269
|
static int notificationId(JSONObject options) {
|
|
538
2270
|
int id = options.optInt("id", 0);
|
|
539
2271
|
if (id != 0) {
|