html2apk 0.2.0 → 0.3.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 +309 -3
- 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/core/build-apk.js +150 -1
- package/src/core/config.js +47 -1
- package/src/core/defaults.js +6 -0
- package/src/desktop/main.js +20 -1
- package/src/desktop/preload.js +1 -0
- package/src/desktop/renderer/index.html +17 -0
- package/src/desktop/renderer/renderer.js +359 -19
- package/src/desktop/renderer/styles.css +38 -0
- package/src/templates/cordova-plugin-html2apk-bridge/plugin.xml +4 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/Html2ApkBridge.java +1507 -55
- 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 +485 -1
- package/src/templates/html2apk-auto-theme.js +144 -0
- package/src/templates/html2apk-onesignal.js +155 -0
|
@@ -1,26 +1,48 @@
|
|
|
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.pm.ApplicationInfo;
|
|
12
18
|
import android.content.pm.PackageManager;
|
|
19
|
+
import android.database.Cursor;
|
|
20
|
+
import android.hardware.camera2.CameraCharacteristics;
|
|
21
|
+
import android.hardware.camera2.CameraManager;
|
|
22
|
+
import android.graphics.Color;
|
|
23
|
+
import android.media.MediaRecorder;
|
|
24
|
+
import android.net.ConnectivityManager;
|
|
25
|
+
import android.net.Network;
|
|
26
|
+
import android.net.NetworkCapabilities;
|
|
13
27
|
import android.net.Uri;
|
|
28
|
+
import android.os.BatteryManager;
|
|
14
29
|
import android.os.Build;
|
|
30
|
+
import android.os.Debug;
|
|
31
|
+
import android.os.Environment;
|
|
32
|
+
import android.os.StatFs;
|
|
15
33
|
import android.os.VibrationEffect;
|
|
16
34
|
import android.os.Vibrator;
|
|
17
35
|
import android.provider.Settings;
|
|
36
|
+
import android.provider.OpenableColumns;
|
|
37
|
+
import android.util.Base64;
|
|
18
38
|
import android.view.View;
|
|
39
|
+
import android.view.Window;
|
|
19
40
|
import android.view.WindowManager;
|
|
20
41
|
import android.widget.Toast;
|
|
21
42
|
|
|
22
43
|
import androidx.core.app.NotificationCompat;
|
|
23
44
|
import androidx.core.app.NotificationManagerCompat;
|
|
45
|
+
import androidx.core.content.ContextCompat;
|
|
24
46
|
|
|
25
47
|
import org.apache.cordova.CallbackContext;
|
|
26
48
|
import org.apache.cordova.CordovaInterface;
|
|
@@ -29,35 +51,80 @@ import org.apache.cordova.CordovaWebView;
|
|
|
29
51
|
import org.json.JSONArray;
|
|
30
52
|
import org.json.JSONObject;
|
|
31
53
|
|
|
54
|
+
import java.io.ByteArrayInputStream;
|
|
55
|
+
import java.io.ByteArrayOutputStream;
|
|
56
|
+
import java.io.File;
|
|
57
|
+
import java.io.FileInputStream;
|
|
58
|
+
import java.io.InputStream;
|
|
59
|
+
import java.io.OutputStream;
|
|
60
|
+
import java.util.List;
|
|
61
|
+
|
|
32
62
|
public class Html2ApkBridge extends CordovaPlugin {
|
|
33
63
|
static final String CHANNEL_ID = "html2apk_default";
|
|
34
64
|
static final String EXTRA_NOTIFICATION_CLICKED = "html2apk_notification_clicked";
|
|
35
65
|
static final String EXTRA_NOTIFICATION_DETAIL = "html2apk_notification_detail";
|
|
36
66
|
|
|
37
67
|
private static final int REQUEST_POST_NOTIFICATIONS = 7311;
|
|
68
|
+
private static final int REQUEST_CAMERA = 7312;
|
|
69
|
+
private static final int REQUEST_RECORD_AUDIO = 7313;
|
|
70
|
+
private static final int REQUEST_PICK_FILE = 7411;
|
|
71
|
+
private static final int REQUEST_SAVE_FILE = 7412;
|
|
72
|
+
private static final int REQUEST_PICK_FOLDER = 7413;
|
|
38
73
|
|
|
39
74
|
private CallbackContext notificationPermissionCallback;
|
|
75
|
+
private CallbackContext cameraPermissionCallback;
|
|
76
|
+
private CallbackContext microphonePermissionCallback;
|
|
77
|
+
private CallbackContext pendingMicStartCallback;
|
|
78
|
+
private CallbackContext filePickerCallback;
|
|
79
|
+
private CallbackContext saveFileCallback;
|
|
80
|
+
private CallbackContext folderPickerCallback;
|
|
81
|
+
private JSONObject pendingSaveFile;
|
|
40
82
|
private JSONObject initialNotification;
|
|
83
|
+
private JSONObject initialLink;
|
|
41
84
|
private boolean overlaySettingsOpened;
|
|
85
|
+
private boolean torchEnabled;
|
|
86
|
+
private MediaRecorder micRecorder;
|
|
87
|
+
private File micRecordingFile;
|
|
88
|
+
private long micRecordingStartedAt;
|
|
89
|
+
private BroadcastReceiver systemReceiver;
|
|
42
90
|
|
|
43
91
|
@Override
|
|
44
92
|
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
|
45
93
|
super.initialize(cordova, webView);
|
|
46
94
|
handleNotificationIntent(cordova.getActivity().getIntent(), false);
|
|
95
|
+
handleLinkIntent(cordova.getActivity().getIntent(), false);
|
|
96
|
+
registerSystemReceiver();
|
|
47
97
|
startFloatingModeIfNeeded();
|
|
48
98
|
}
|
|
49
99
|
|
|
50
100
|
@Override
|
|
51
101
|
public void onNewIntent(Intent intent) {
|
|
52
102
|
handleNotificationIntent(intent, true);
|
|
103
|
+
handleLinkIntent(intent, true);
|
|
53
104
|
}
|
|
54
105
|
|
|
55
106
|
@Override
|
|
56
107
|
public void onResume(boolean multitasking) {
|
|
57
108
|
super.onResume(multitasking);
|
|
109
|
+
dispatchEvent("app:voltou", baseEvent("app:voltou"));
|
|
58
110
|
startFloatingModeIfNeeded();
|
|
59
111
|
}
|
|
60
112
|
|
|
113
|
+
@Override
|
|
114
|
+
public void onPause(boolean multitasking) {
|
|
115
|
+
dispatchEvent("app:pausado", baseEvent("app:pausado"));
|
|
116
|
+
dispatchEvent("app:background", baseEvent("app:background"));
|
|
117
|
+
super.onPause(multitasking);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@Override
|
|
121
|
+
public void onDestroy() {
|
|
122
|
+
stopMicRecorderSilently();
|
|
123
|
+
unregisterSystemReceiver();
|
|
124
|
+
dispatchEvent("app:fechado", baseEvent("app:fechado"));
|
|
125
|
+
super.onDestroy();
|
|
126
|
+
}
|
|
127
|
+
|
|
61
128
|
@Override
|
|
62
129
|
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
|
|
63
130
|
try {
|
|
@@ -70,7 +137,12 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
70
137
|
|
|
71
138
|
if ("scheduleNotification".equals(action)) {
|
|
72
139
|
JSONObject options = args.optJSONObject(0);
|
|
73
|
-
scheduleNotification(options == null ? new JSONObject() : options);
|
|
140
|
+
callbackContext.success(scheduleNotification(options == null ? new JSONObject() : options));
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if ("cancelNotification".equals(action)) {
|
|
145
|
+
cancelNotification(args.opt(0));
|
|
74
146
|
callbackContext.success();
|
|
75
147
|
return true;
|
|
76
148
|
}
|
|
@@ -105,24 +177,155 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
105
177
|
return true;
|
|
106
178
|
}
|
|
107
179
|
|
|
180
|
+
if ("setSystemBarsColor".equals(action)) {
|
|
181
|
+
callbackContext.success(setSystemBarsColor(args.opt(0)));
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if ("flashlight".equals(action)) {
|
|
186
|
+
setFlashlight(args.optBoolean(0, true));
|
|
187
|
+
callbackContext.success(flashlightStatus());
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if ("toggleFlashlight".equals(action)) {
|
|
192
|
+
setFlashlight(!torchEnabled);
|
|
193
|
+
callbackContext.success(flashlightStatus());
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if ("flashlightStatus".equals(action)) {
|
|
198
|
+
callbackContext.success(flashlightStatus());
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if ("requestCameraPermission".equals(action)) {
|
|
203
|
+
requestCameraPermission(callbackContext);
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if ("requestMicrophonePermission".equals(action)) {
|
|
208
|
+
requestMicrophonePermission(callbackContext);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if ("microphoneStatus".equals(action)) {
|
|
213
|
+
callbackContext.success(microphoneStatus());
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if ("startMic".equals(action)) {
|
|
218
|
+
startMicRecording(callbackContext);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if ("stopMic".equals(action)) {
|
|
223
|
+
callbackContext.success(stopMicRecording());
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
|
|
108
227
|
if ("copyText".equals(action)) {
|
|
109
228
|
copyText(args.optString(0, ""));
|
|
110
229
|
callbackContext.success();
|
|
111
230
|
return true;
|
|
112
231
|
}
|
|
113
232
|
|
|
233
|
+
if ("readText".equals(action)) {
|
|
234
|
+
callbackContext.success(readText());
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
|
|
114
238
|
if ("shareText".equals(action)) {
|
|
115
239
|
shareText(args.optString(0, ""));
|
|
116
240
|
callbackContext.success();
|
|
117
241
|
return true;
|
|
118
242
|
}
|
|
119
243
|
|
|
244
|
+
if ("share".equals(action)) {
|
|
245
|
+
share(args.optJSONObject(0));
|
|
246
|
+
callbackContext.success();
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
120
250
|
if ("openUrl".equals(action)) {
|
|
121
251
|
openUrl(args.optString(0, ""));
|
|
122
252
|
callbackContext.success();
|
|
123
253
|
return true;
|
|
124
254
|
}
|
|
125
255
|
|
|
256
|
+
if ("dial".equals(action)) {
|
|
257
|
+
dial(args.optString(0, ""));
|
|
258
|
+
callbackContext.success();
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if ("openMap".equals(action)) {
|
|
263
|
+
openMap(args.optString(0, ""));
|
|
264
|
+
callbackContext.success();
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if ("openWhatsapp".equals(action)) {
|
|
269
|
+
openWhatsapp(args.optString(0, ""), args.optString(1, ""));
|
|
270
|
+
callbackContext.success();
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if ("pickFile".equals(action)) {
|
|
275
|
+
pickFile(args.optJSONObject(0), callbackContext);
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if ("pickFolder".equals(action)) {
|
|
280
|
+
pickFolder(callbackContext);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if ("saveFile".equals(action)) {
|
|
285
|
+
saveFile(args.optJSONObject(0), callbackContext);
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if ("deviceInfo".equals(action)) {
|
|
290
|
+
callbackContext.success(deviceInfo());
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if ("networkInfo".equals(action)) {
|
|
295
|
+
callbackContext.success(networkInfo());
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if ("batteryInfo".equals(action)) {
|
|
300
|
+
callbackContext.success(batteryInfo());
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if ("memoryInfo".equals(action)) {
|
|
305
|
+
callbackContext.success(memoryInfo());
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if ("storageInfo".equals(action)) {
|
|
310
|
+
callbackContext.success(storageInfo());
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if ("performanceInfo".equals(action)) {
|
|
315
|
+
callbackContext.success(performanceInfo());
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if ("openAppsMemory".equals(action)) {
|
|
320
|
+
callbackContext.success(openAppsMemoryInfo());
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if ("permissionStatus".equals(action)) {
|
|
325
|
+
callbackContext.success(permissionStatus(args.optJSONArray(0)));
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
126
329
|
if ("requestNotificationPermission".equals(action)) {
|
|
127
330
|
requestNotificationPermission(callbackContext);
|
|
128
331
|
return true;
|
|
@@ -172,6 +375,12 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
172
375
|
initialNotification = null;
|
|
173
376
|
return true;
|
|
174
377
|
}
|
|
378
|
+
|
|
379
|
+
if ("getInitialLink".equals(action)) {
|
|
380
|
+
callbackContext.success(initialLink == null ? new JSONObject() : initialLink);
|
|
381
|
+
initialLink = null;
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
175
384
|
} catch (Exception error) {
|
|
176
385
|
callbackContext.error(error.getMessage());
|
|
177
386
|
return true;
|
|
@@ -182,6 +391,49 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
182
391
|
|
|
183
392
|
@Override
|
|
184
393
|
public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) {
|
|
394
|
+
if (requestCode == REQUEST_CAMERA && cameraPermissionCallback != null) {
|
|
395
|
+
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
|
396
|
+
try {
|
|
397
|
+
JSONObject result = new JSONObject();
|
|
398
|
+
result.put("permission", "android.permission.CAMERA");
|
|
399
|
+
result.put("required", true);
|
|
400
|
+
result.put("requested", true);
|
|
401
|
+
result.put("granted", granted);
|
|
402
|
+
cameraPermissionCallback.success(result);
|
|
403
|
+
} catch (Exception error) {
|
|
404
|
+
cameraPermissionCallback.error(error.getMessage());
|
|
405
|
+
} finally {
|
|
406
|
+
cameraPermissionCallback = null;
|
|
407
|
+
}
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (requestCode == REQUEST_RECORD_AUDIO) {
|
|
412
|
+
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
|
413
|
+
CallbackContext callback = pendingMicStartCallback != null ? pendingMicStartCallback : microphonePermissionCallback;
|
|
414
|
+
boolean shouldStartRecording = pendingMicStartCallback != null;
|
|
415
|
+
pendingMicStartCallback = null;
|
|
416
|
+
microphonePermissionCallback = null;
|
|
417
|
+
|
|
418
|
+
if (callback == null) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
if (shouldStartRecording && granted) {
|
|
424
|
+
startMicRecorder(callback);
|
|
425
|
+
} else {
|
|
426
|
+
JSONObject result = microphoneStatus();
|
|
427
|
+
result.put("requested", true);
|
|
428
|
+
result.put("granted", granted);
|
|
429
|
+
callback.success(result);
|
|
430
|
+
}
|
|
431
|
+
} catch (Exception error) {
|
|
432
|
+
callback.error(error.getMessage());
|
|
433
|
+
}
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
185
437
|
if (requestCode != REQUEST_POST_NOTIFICATIONS || notificationPermissionCallback == null) {
|
|
186
438
|
return;
|
|
187
439
|
}
|
|
@@ -199,6 +451,23 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
199
451
|
}
|
|
200
452
|
}
|
|
201
453
|
|
|
454
|
+
@Override
|
|
455
|
+
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
|
456
|
+
if (requestCode == REQUEST_PICK_FILE) {
|
|
457
|
+
handlePickFileResult(resultCode, intent);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (requestCode == REQUEST_SAVE_FILE) {
|
|
462
|
+
handleSaveFileResult(resultCode, intent);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (requestCode == REQUEST_PICK_FOLDER) {
|
|
467
|
+
handlePickFolderResult(resultCode, intent);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
202
471
|
private Context context() {
|
|
203
472
|
return this.cordova.getActivity().getApplicationContext();
|
|
204
473
|
}
|
|
@@ -323,27 +592,46 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
323
592
|
.setContentIntent(createContentIntent(context(), id, detailPayload(options)))
|
|
324
593
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
|
|
325
594
|
|
|
595
|
+
addNotificationActions(builder, context(), id, options);
|
|
326
596
|
NotificationManagerCompat.from(context()).notify(id, builder.build());
|
|
597
|
+
dispatchEvent("notificacao:recebida", detailPayload(options));
|
|
327
598
|
}
|
|
328
599
|
|
|
329
|
-
private
|
|
600
|
+
private JSONObject scheduleNotification(JSONObject options) throws Exception {
|
|
330
601
|
long when = options.optLong("quando", options.optLong("when", System.currentTimeMillis() + 60000));
|
|
331
602
|
if (when < System.currentTimeMillis()) {
|
|
332
603
|
when = System.currentTimeMillis() + 1000;
|
|
333
604
|
}
|
|
334
605
|
|
|
335
606
|
int id = notificationId(options);
|
|
607
|
+
options.put("id", id);
|
|
608
|
+
options.put("quando", when);
|
|
609
|
+
options.put("when", when);
|
|
336
610
|
NotificationStore.save(context(), id, when, options);
|
|
337
611
|
NotificationReceiver.schedule(context(), id, when, options, canScheduleExactAlarms());
|
|
612
|
+
|
|
613
|
+
JSONObject result = new JSONObject();
|
|
614
|
+
result.put("id", id);
|
|
615
|
+
result.put("when", when);
|
|
616
|
+
result.put("quando", when);
|
|
617
|
+
result.put("repeating", repeatInterval(options) > 0);
|
|
618
|
+
result.put("loop", loopNotifications(options).length() > 0);
|
|
619
|
+
return result;
|
|
338
620
|
}
|
|
339
621
|
|
|
340
|
-
private
|
|
341
|
-
|
|
342
|
-
|
|
622
|
+
private void cancelNotification(Object input) throws Exception {
|
|
623
|
+
int id = notificationIdFromObject(input);
|
|
624
|
+
if (id == 0) {
|
|
625
|
+
throw new Exception("Notification id is required.");
|
|
343
626
|
}
|
|
344
627
|
|
|
345
|
-
|
|
346
|
-
|
|
628
|
+
NotificationReceiver.cancel(context(), id);
|
|
629
|
+
NotificationStore.remove(context(), id);
|
|
630
|
+
NotificationManagerCompat.from(context()).cancel(id);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private boolean canScheduleExactAlarms() {
|
|
634
|
+
return canScheduleExactAlarms(context());
|
|
347
635
|
}
|
|
348
636
|
|
|
349
637
|
private void openExactAlarmSettings() {
|
|
@@ -428,6 +716,83 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
428
716
|
});
|
|
429
717
|
}
|
|
430
718
|
|
|
719
|
+
private JSONObject setSystemBarsColor(Object input) throws Exception {
|
|
720
|
+
final JSONObject options = normalizeSystemBarsOptions(input);
|
|
721
|
+
final String statusColor = options.optString("statusBarColor", options.optString("color", options.optString("cor", "#126fff")));
|
|
722
|
+
final String navigationColor = options.optString("navigationBarColor", options.optString("navigationColor", statusColor));
|
|
723
|
+
final boolean darkStatusIcons = options.optBoolean("darkIcons", isLightColor(statusColor));
|
|
724
|
+
final boolean darkNavigationIcons = options.optBoolean("darkNavigationIcons", isLightColor(navigationColor));
|
|
725
|
+
final int statusBarColor = Color.parseColor(statusColor);
|
|
726
|
+
final int navigationBarColor = Color.parseColor(navigationColor);
|
|
727
|
+
|
|
728
|
+
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
|
729
|
+
@Override
|
|
730
|
+
public void run() {
|
|
731
|
+
Window window = cordova.getActivity().getWindow();
|
|
732
|
+
window.setStatusBarColor(statusBarColor);
|
|
733
|
+
window.setNavigationBarColor(navigationBarColor);
|
|
734
|
+
applySystemBarIconContrast(window, darkStatusIcons, darkNavigationIcons);
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
JSONObject result = new JSONObject();
|
|
739
|
+
result.put("statusBarColor", statusColor);
|
|
740
|
+
result.put("navigationBarColor", navigationColor);
|
|
741
|
+
result.put("darkIcons", darkStatusIcons);
|
|
742
|
+
result.put("darkNavigationIcons", darkNavigationIcons);
|
|
743
|
+
result.put("applied", true);
|
|
744
|
+
return result;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private JSONObject normalizeSystemBarsOptions(Object input) throws Exception {
|
|
748
|
+
if (input instanceof JSONObject) {
|
|
749
|
+
return (JSONObject) input;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
JSONObject options = new JSONObject();
|
|
753
|
+
String color = input == null ? "#126fff" : String.valueOf(input);
|
|
754
|
+
options.put("color", color);
|
|
755
|
+
options.put("statusBarColor", color);
|
|
756
|
+
options.put("navigationBarColor", color);
|
|
757
|
+
return options;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
private boolean isLightColor(String color) {
|
|
761
|
+
try {
|
|
762
|
+
int parsed = Color.parseColor(color);
|
|
763
|
+
int red = Color.red(parsed);
|
|
764
|
+
int green = Color.green(parsed);
|
|
765
|
+
int blue = Color.blue(parsed);
|
|
766
|
+
double luminance = (0.2126 * red) + (0.7152 * green) + (0.0722 * blue);
|
|
767
|
+
return luminance > 158;
|
|
768
|
+
} catch (Exception ignored) {
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
private void applySystemBarIconContrast(Window window, boolean darkStatusIcons, boolean darkNavigationIcons) {
|
|
774
|
+
View decor = window.getDecorView();
|
|
775
|
+
int flags = decor.getSystemUiVisibility();
|
|
776
|
+
|
|
777
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
778
|
+
if (darkStatusIcons) {
|
|
779
|
+
flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
|
780
|
+
} else {
|
|
781
|
+
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
786
|
+
if (darkNavigationIcons) {
|
|
787
|
+
flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
|
788
|
+
} else {
|
|
789
|
+
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
decor.setSystemUiVisibility(flags);
|
|
794
|
+
}
|
|
795
|
+
|
|
431
796
|
private void copyText(String text) {
|
|
432
797
|
ClipboardManager clipboard = (ClipboardManager) context().getSystemService(Context.CLIPBOARD_SERVICE);
|
|
433
798
|
if (clipboard != null) {
|
|
@@ -451,82 +816,1138 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
451
816
|
cordova.getActivity().startActivity(intent);
|
|
452
817
|
}
|
|
453
818
|
|
|
454
|
-
private
|
|
455
|
-
|
|
819
|
+
private boolean hasCameraPermission() {
|
|
820
|
+
return ContextCompat.checkSelfPermission(context(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
private void requestCameraPermission(CallbackContext callbackContext) throws Exception {
|
|
824
|
+
if (hasCameraPermission()) {
|
|
825
|
+
JSONObject result = new JSONObject();
|
|
826
|
+
result.put("permission", "android.permission.CAMERA");
|
|
827
|
+
result.put("required", true);
|
|
828
|
+
result.put("granted", true);
|
|
829
|
+
callbackContext.success(result);
|
|
456
830
|
return;
|
|
457
831
|
}
|
|
458
832
|
|
|
459
|
-
|
|
460
|
-
|
|
833
|
+
cameraPermissionCallback = callbackContext;
|
|
834
|
+
cordova.requestPermission(this, REQUEST_CAMERA, Manifest.permission.CAMERA);
|
|
835
|
+
}
|
|
461
836
|
|
|
462
|
-
|
|
463
|
-
|
|
837
|
+
private boolean hasMicrophonePermission() {
|
|
838
|
+
return ContextCompat.checkSelfPermission(context(), Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
private void requestMicrophonePermission(CallbackContext callbackContext) throws Exception {
|
|
842
|
+
if (hasMicrophonePermission()) {
|
|
843
|
+
JSONObject result = microphoneStatus();
|
|
844
|
+
result.put("requested", false);
|
|
845
|
+
result.put("granted", true);
|
|
846
|
+
callbackContext.success(result);
|
|
847
|
+
return;
|
|
464
848
|
}
|
|
465
849
|
|
|
466
|
-
|
|
467
|
-
|
|
850
|
+
microphonePermissionCallback = callbackContext;
|
|
851
|
+
cordova.requestPermission(this, REQUEST_RECORD_AUDIO, Manifest.permission.RECORD_AUDIO);
|
|
468
852
|
}
|
|
469
853
|
|
|
470
|
-
private
|
|
471
|
-
|
|
854
|
+
private JSONObject microphoneStatus() throws Exception {
|
|
855
|
+
JSONObject result = new JSONObject();
|
|
856
|
+
result.put("permission", "android.permission.RECORD_AUDIO");
|
|
857
|
+
result.put("required", true);
|
|
858
|
+
result.put("granted", hasMicrophonePermission());
|
|
859
|
+
result.put("permissionGranted", hasMicrophonePermission());
|
|
860
|
+
result.put("recording", micRecorder != null);
|
|
861
|
+
result.put("gravando", micRecorder != null);
|
|
862
|
+
if (micRecorder != null) {
|
|
863
|
+
result.put("startedAt", micRecordingStartedAt);
|
|
864
|
+
result.put("iniciadoEm", micRecordingStartedAt);
|
|
865
|
+
result.put("durationMs", System.currentTimeMillis() - micRecordingStartedAt);
|
|
866
|
+
result.put("duracaoMs", System.currentTimeMillis() - micRecordingStartedAt);
|
|
867
|
+
}
|
|
868
|
+
return result;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
private void startMicRecording(CallbackContext callbackContext) throws Exception {
|
|
872
|
+
if (micRecorder != null) {
|
|
873
|
+
JSONObject result = microphoneStatus();
|
|
874
|
+
result.put("alreadyRecording", true);
|
|
875
|
+
result.put("jaGravando", true);
|
|
876
|
+
callbackContext.success(result);
|
|
472
877
|
return;
|
|
473
878
|
}
|
|
474
879
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
880
|
+
if (!hasMicrophonePermission()) {
|
|
881
|
+
pendingMicStartCallback = callbackContext;
|
|
882
|
+
cordova.requestPermission(this, REQUEST_RECORD_AUDIO, Manifest.permission.RECORD_AUDIO);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
479
885
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
886
|
+
startMicRecorder(callbackContext);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
private void startMicRecorder(CallbackContext callbackContext) throws Exception {
|
|
890
|
+
File audioDir = new File(context().getCacheDir(), "html2apk-audio");
|
|
891
|
+
if (!audioDir.exists() && !audioDir.mkdirs()) {
|
|
892
|
+
throw new Exception("Could not create audio cache directory.");
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
File audioFile = File.createTempFile("mic-", ".m4a", audioDir);
|
|
896
|
+
MediaRecorder recorder = new MediaRecorder();
|
|
897
|
+
try {
|
|
898
|
+
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
|
899
|
+
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
|
|
900
|
+
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
|
|
901
|
+
recorder.setAudioEncodingBitRate(128000);
|
|
902
|
+
recorder.setAudioSamplingRate(44100);
|
|
903
|
+
recorder.setOutputFile(audioFile.getAbsolutePath());
|
|
904
|
+
recorder.prepare();
|
|
905
|
+
recorder.start();
|
|
906
|
+
} catch (Exception error) {
|
|
907
|
+
try {
|
|
908
|
+
recorder.release();
|
|
909
|
+
} catch (Exception ignored) {
|
|
484
910
|
}
|
|
485
|
-
|
|
911
|
+
audioFile.delete();
|
|
912
|
+
throw error;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
micRecordingFile = audioFile;
|
|
916
|
+
micRecordingStartedAt = System.currentTimeMillis();
|
|
917
|
+
micRecorder = recorder;
|
|
918
|
+
|
|
919
|
+
JSONObject result = microphoneStatus();
|
|
920
|
+
result.put("mimeType", "audio/mp4");
|
|
921
|
+
result.put("extension", "m4a");
|
|
922
|
+
result.put("extensao", "m4a");
|
|
923
|
+
result.put("fileName", micRecordingFile.getName());
|
|
924
|
+
result.put("nomeArquivo", micRecordingFile.getName());
|
|
925
|
+
callbackContext.success(result);
|
|
486
926
|
}
|
|
487
927
|
|
|
488
|
-
private JSONObject
|
|
928
|
+
private JSONObject stopMicRecording() throws Exception {
|
|
929
|
+
if (micRecorder == null || micRecordingFile == null) {
|
|
930
|
+
throw new Exception("Microphone is not recording. Call ouvirMic() first.");
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
MediaRecorder recorder = micRecorder;
|
|
934
|
+
File audioFile = micRecordingFile;
|
|
935
|
+
long startedAt = micRecordingStartedAt;
|
|
936
|
+
long endedAt;
|
|
937
|
+
Exception stopError = null;
|
|
938
|
+
|
|
939
|
+
micRecorder = null;
|
|
940
|
+
micRecordingFile = null;
|
|
941
|
+
micRecordingStartedAt = 0;
|
|
942
|
+
|
|
489
943
|
try {
|
|
490
|
-
|
|
491
|
-
|
|
944
|
+
recorder.stop();
|
|
945
|
+
} catch (RuntimeException error) {
|
|
946
|
+
stopError = new Exception("Could not finalize audio recording. Wait a little longer before calling pararMic().");
|
|
947
|
+
} finally {
|
|
948
|
+
try {
|
|
949
|
+
recorder.reset();
|
|
950
|
+
} catch (Exception ignored) {
|
|
492
951
|
}
|
|
493
|
-
|
|
952
|
+
try {
|
|
953
|
+
recorder.release();
|
|
954
|
+
} catch (Exception ignored) {
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (stopError != null) {
|
|
959
|
+
audioFile.delete();
|
|
960
|
+
throw stopError;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
endedAt = System.currentTimeMillis();
|
|
964
|
+
byte[] audioBytes = readFileBytes(audioFile);
|
|
965
|
+
String base64 = Base64.encodeToString(audioBytes, Base64.NO_WRAP);
|
|
966
|
+
audioFile.delete();
|
|
967
|
+
|
|
968
|
+
JSONObject result = new JSONObject();
|
|
969
|
+
result.put("base64", base64);
|
|
970
|
+
result.put("mimeType", "audio/mp4");
|
|
971
|
+
result.put("extension", "m4a");
|
|
972
|
+
result.put("extensao", "m4a");
|
|
973
|
+
result.put("size", audioBytes.length);
|
|
974
|
+
result.put("tamanho", audioBytes.length);
|
|
975
|
+
result.put("startedAt", startedAt);
|
|
976
|
+
result.put("iniciadoEm", startedAt);
|
|
977
|
+
result.put("endedAt", endedAt);
|
|
978
|
+
result.put("finalizadoEm", endedAt);
|
|
979
|
+
result.put("durationMs", endedAt - startedAt);
|
|
980
|
+
result.put("duracaoMs", endedAt - startedAt);
|
|
981
|
+
result.put("recording", false);
|
|
982
|
+
result.put("gravando", false);
|
|
983
|
+
return result;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
private byte[] readFileBytes(File file) throws Exception {
|
|
987
|
+
InputStream inputStream = new FileInputStream(file);
|
|
988
|
+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
989
|
+
try {
|
|
990
|
+
byte[] buffer = new byte[8192];
|
|
991
|
+
int read;
|
|
992
|
+
while ((read = inputStream.read(buffer)) != -1) {
|
|
993
|
+
outputStream.write(buffer, 0, read);
|
|
994
|
+
}
|
|
995
|
+
return outputStream.toByteArray();
|
|
996
|
+
} finally {
|
|
997
|
+
inputStream.close();
|
|
998
|
+
outputStream.close();
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
private void stopMicRecorderSilently() {
|
|
1003
|
+
if (micRecorder == null) {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
MediaRecorder recorder = micRecorder;
|
|
1008
|
+
File audioFile = micRecordingFile;
|
|
1009
|
+
micRecorder = null;
|
|
1010
|
+
micRecordingFile = null;
|
|
1011
|
+
micRecordingStartedAt = 0;
|
|
1012
|
+
|
|
1013
|
+
try {
|
|
1014
|
+
recorder.stop();
|
|
494
1015
|
} catch (Exception ignored) {
|
|
495
|
-
|
|
1016
|
+
}
|
|
1017
|
+
try {
|
|
1018
|
+
recorder.reset();
|
|
1019
|
+
} catch (Exception ignored) {
|
|
1020
|
+
}
|
|
1021
|
+
try {
|
|
1022
|
+
recorder.release();
|
|
1023
|
+
} catch (Exception ignored) {
|
|
1024
|
+
}
|
|
1025
|
+
if (audioFile != null) {
|
|
1026
|
+
audioFile.delete();
|
|
496
1027
|
}
|
|
497
1028
|
}
|
|
498
1029
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
launchIntent.setPackage(context.getPackageName());
|
|
1030
|
+
private String torchCameraId() throws Exception {
|
|
1031
|
+
CameraManager manager = (CameraManager) context().getSystemService(Context.CAMERA_SERVICE);
|
|
1032
|
+
if (manager == null) {
|
|
1033
|
+
throw new Exception("Camera service is not available.");
|
|
504
1034
|
}
|
|
505
1035
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
1036
|
+
for (String cameraId : manager.getCameraIdList()) {
|
|
1037
|
+
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
|
|
1038
|
+
Boolean flashAvailable = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
|
|
1039
|
+
if (Boolean.TRUE.equals(flashAvailable)) {
|
|
1040
|
+
return cameraId;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
509
1043
|
|
|
510
|
-
|
|
511
|
-
context,
|
|
512
|
-
id,
|
|
513
|
-
launchIntent,
|
|
514
|
-
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
|
515
|
-
);
|
|
1044
|
+
throw new Exception("This device does not expose a flashlight.");
|
|
516
1045
|
}
|
|
517
1046
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if (click == null) {
|
|
522
|
-
click = options.optJSONObject("onClick");
|
|
1047
|
+
private void setFlashlight(boolean enabled) throws Exception {
|
|
1048
|
+
if (!hasCameraPermission()) {
|
|
1049
|
+
throw new Exception("CAMERA permission is not granted. Call solicitarPermissaoCamera() first.");
|
|
523
1050
|
}
|
|
524
1051
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
1052
|
+
CameraManager manager = (CameraManager) context().getSystemService(Context.CAMERA_SERVICE);
|
|
1053
|
+
if (manager == null) {
|
|
1054
|
+
throw new Exception("Camera service is not available.");
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
manager.setTorchMode(torchCameraId(), enabled);
|
|
1058
|
+
torchEnabled = enabled;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
private JSONObject flashlightStatus() throws Exception {
|
|
1062
|
+
JSONObject result = new JSONObject();
|
|
1063
|
+
result.put("available", context().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH));
|
|
1064
|
+
result.put("enabled", torchEnabled);
|
|
1065
|
+
result.put("permission", "android.permission.CAMERA");
|
|
1066
|
+
result.put("permissionGranted", hasCameraPermission());
|
|
1067
|
+
return result;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
private String readText() {
|
|
1071
|
+
ClipboardManager clipboard = (ClipboardManager) context().getSystemService(Context.CLIPBOARD_SERVICE);
|
|
1072
|
+
if (clipboard == null || !clipboard.hasPrimaryClip() || clipboard.getPrimaryClip() == null) {
|
|
1073
|
+
return "";
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
ClipData clip = clipboard.getPrimaryClip();
|
|
1077
|
+
if (clip.getItemCount() == 0 || clip.getItemAt(0) == null) {
|
|
1078
|
+
return "";
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
CharSequence text = clip.getItemAt(0).coerceToText(context());
|
|
1082
|
+
return text == null ? "" : text.toString();
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
private void share(JSONObject options) {
|
|
1086
|
+
JSONObject safeOptions = options == null ? new JSONObject() : options;
|
|
1087
|
+
String text = safeOptions.optString("texto", safeOptions.optString("text", ""));
|
|
1088
|
+
String url = safeOptions.optString("url", "");
|
|
1089
|
+
String title = safeOptions.optString("titulo", safeOptions.optString("title", "Compartilhar"));
|
|
1090
|
+
StringBuilder content = new StringBuilder();
|
|
1091
|
+
if (text.length() > 0) {
|
|
1092
|
+
content.append(text);
|
|
1093
|
+
}
|
|
1094
|
+
if (url.length() > 0) {
|
|
1095
|
+
if (content.length() > 0) {
|
|
1096
|
+
content.append("\n");
|
|
1097
|
+
}
|
|
1098
|
+
content.append(url);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
Intent intent = new Intent(Intent.ACTION_SEND);
|
|
1102
|
+
intent.setType(safeOptions.optString("mimeType", "text/plain"));
|
|
1103
|
+
intent.putExtra(Intent.EXTRA_TEXT, content.toString());
|
|
1104
|
+
intent.putExtra(Intent.EXTRA_TITLE, title);
|
|
1105
|
+
cordova.getActivity().startActivity(Intent.createChooser(intent, title));
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
private void dial(String phone) throws Exception {
|
|
1109
|
+
String cleaned = phone == null ? "" : phone.trim();
|
|
1110
|
+
if (cleaned.length() == 0) {
|
|
1111
|
+
throw new Exception("Phone number is required.");
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + Uri.encode(cleaned)));
|
|
1115
|
+
cordova.getActivity().startActivity(intent);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
private void openMap(String query) throws Exception {
|
|
1119
|
+
String cleaned = query == null ? "" : query.trim();
|
|
1120
|
+
if (cleaned.length() == 0) {
|
|
1121
|
+
throw new Exception("Map query is required.");
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
Uri uri = cleaned.startsWith("geo:") || cleaned.startsWith("http")
|
|
1125
|
+
? Uri.parse(cleaned)
|
|
1126
|
+
: Uri.parse("geo:0,0?q=" + Uri.encode(cleaned));
|
|
1127
|
+
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
|
1128
|
+
cordova.getActivity().startActivity(intent);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
private void openWhatsapp(String phone, String message) throws Exception {
|
|
1132
|
+
String digits = phone == null ? "" : phone.replaceAll("[^0-9]", "");
|
|
1133
|
+
if (digits.length() == 0) {
|
|
1134
|
+
throw new Exception("WhatsApp phone number is required.");
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
String url = "https://wa.me/" + digits;
|
|
1138
|
+
if (message != null && message.trim().length() > 0) {
|
|
1139
|
+
url += "?text=" + Uri.encode(message.trim());
|
|
1140
|
+
}
|
|
1141
|
+
openUrl(url);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
private void pickFile(JSONObject options, CallbackContext callbackContext) {
|
|
1145
|
+
JSONObject safeOptions = options == null ? new JSONObject() : options;
|
|
1146
|
+
String kind = safeOptions.optString("tipo", safeOptions.optString("kind", "file"));
|
|
1147
|
+
boolean multiple = safeOptions.optBoolean("multiplo", safeOptions.optBoolean("multiple", false));
|
|
1148
|
+
String mimeType = mimeTypeForPicker(kind, safeOptions);
|
|
1149
|
+
|
|
1150
|
+
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
|
1151
|
+
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
1152
|
+
intent.setType(mimeType);
|
|
1153
|
+
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
|
|
1154
|
+
|
|
1155
|
+
filePickerCallback = callbackContext;
|
|
1156
|
+
cordova.startActivityForResult(this, intent, REQUEST_PICK_FILE);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
private String mimeTypeForPicker(String kind, JSONObject options) {
|
|
1160
|
+
String type = options.optString("mimeType", options.optString("tipoMime", ""));
|
|
1161
|
+
if (type.length() > 0) {
|
|
1162
|
+
return type;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
JSONArray types = options.optJSONArray("tipos");
|
|
1166
|
+
if (types == null) {
|
|
1167
|
+
types = options.optJSONArray("types");
|
|
1168
|
+
}
|
|
1169
|
+
if (types != null && types.length() == 1) {
|
|
1170
|
+
return types.optString(0, "*/*");
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
if ("image".equals(kind)) {
|
|
1174
|
+
return "image/*";
|
|
1175
|
+
}
|
|
1176
|
+
if ("video".equals(kind)) {
|
|
1177
|
+
return "video/*";
|
|
1178
|
+
}
|
|
1179
|
+
if ("media".equals(kind)) {
|
|
1180
|
+
return "image/*";
|
|
1181
|
+
}
|
|
1182
|
+
return "*/*";
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
private void pickFolder(CallbackContext callbackContext) {
|
|
1186
|
+
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
|
1187
|
+
folderPickerCallback = callbackContext;
|
|
1188
|
+
cordova.startActivityForResult(this, intent, REQUEST_PICK_FOLDER);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
private void saveFile(JSONObject options, CallbackContext callbackContext) throws Exception {
|
|
1192
|
+
JSONObject safeOptions = options == null ? new JSONObject() : options;
|
|
1193
|
+
String name = safeOptions.optString("nome", safeOptions.optString("name", "arquivo.txt"));
|
|
1194
|
+
String mimeType = safeOptions.optString("mimeType", "text/plain");
|
|
1195
|
+
|
|
1196
|
+
pendingSaveFile = safeOptions;
|
|
1197
|
+
saveFileCallback = callbackContext;
|
|
1198
|
+
|
|
1199
|
+
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
|
1200
|
+
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
1201
|
+
intent.setType(mimeType);
|
|
1202
|
+
intent.putExtra(Intent.EXTRA_TITLE, name);
|
|
1203
|
+
cordova.startActivityForResult(this, intent, REQUEST_SAVE_FILE);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
private void handlePickFileResult(int resultCode, Intent intent) {
|
|
1207
|
+
CallbackContext callback = filePickerCallback;
|
|
1208
|
+
filePickerCallback = null;
|
|
1209
|
+
if (callback == null) {
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
if (resultCode != Activity.RESULT_OK || intent == null) {
|
|
1213
|
+
callback.success(new JSONArray());
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
try {
|
|
1218
|
+
JSONArray items = new JSONArray();
|
|
1219
|
+
if (intent.getClipData() != null) {
|
|
1220
|
+
ClipData clipData = intent.getClipData();
|
|
1221
|
+
for (int index = 0; index < clipData.getItemCount(); index += 1) {
|
|
1222
|
+
items.put(fileInfo(clipData.getItemAt(index).getUri()));
|
|
1223
|
+
}
|
|
1224
|
+
} else if (intent.getData() != null) {
|
|
1225
|
+
items.put(fileInfo(intent.getData()));
|
|
1226
|
+
}
|
|
1227
|
+
callback.success(items);
|
|
1228
|
+
} catch (Exception error) {
|
|
1229
|
+
callback.error(error.getMessage());
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
private void handlePickFolderResult(int resultCode, Intent intent) {
|
|
1234
|
+
CallbackContext callback = folderPickerCallback;
|
|
1235
|
+
folderPickerCallback = null;
|
|
1236
|
+
if (callback == null) {
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
if (resultCode != Activity.RESULT_OK || intent == null || intent.getData() == null) {
|
|
1240
|
+
callback.success(new JSONObject());
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
try {
|
|
1245
|
+
JSONObject result = new JSONObject();
|
|
1246
|
+
result.put("uri", intent.getData().toString());
|
|
1247
|
+
callback.success(result);
|
|
1248
|
+
} catch (Exception error) {
|
|
1249
|
+
callback.error(error.getMessage());
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
private void handleSaveFileResult(int resultCode, Intent intent) {
|
|
1254
|
+
CallbackContext callback = saveFileCallback;
|
|
1255
|
+
JSONObject options = pendingSaveFile;
|
|
1256
|
+
saveFileCallback = null;
|
|
1257
|
+
pendingSaveFile = null;
|
|
1258
|
+
if (callback == null) {
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
if (resultCode != Activity.RESULT_OK || intent == null || intent.getData() == null) {
|
|
1262
|
+
callback.success(new JSONObject());
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
try {
|
|
1267
|
+
Uri uri = intent.getData();
|
|
1268
|
+
writePickedFile(uri, options == null ? new JSONObject() : options);
|
|
1269
|
+
JSONObject result = fileInfo(uri);
|
|
1270
|
+
result.put("saved", true);
|
|
1271
|
+
callback.success(result);
|
|
1272
|
+
} catch (Exception error) {
|
|
1273
|
+
callback.error(error.getMessage());
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
private JSONObject fileInfo(Uri uri) throws Exception {
|
|
1278
|
+
JSONObject result = new JSONObject();
|
|
1279
|
+
result.put("uri", uri.toString());
|
|
1280
|
+
result.put("mimeType", context().getContentResolver().getType(uri));
|
|
1281
|
+
|
|
1282
|
+
Cursor cursor = context().getContentResolver().query(uri, null, null, null, null);
|
|
1283
|
+
if (cursor != null) {
|
|
1284
|
+
try {
|
|
1285
|
+
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
|
1286
|
+
int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
|
|
1287
|
+
if (cursor.moveToFirst()) {
|
|
1288
|
+
if (nameIndex >= 0) {
|
|
1289
|
+
result.put("name", cursor.getString(nameIndex));
|
|
1290
|
+
result.put("nome", cursor.getString(nameIndex));
|
|
1291
|
+
}
|
|
1292
|
+
if (sizeIndex >= 0) {
|
|
1293
|
+
result.put("size", cursor.getLong(sizeIndex));
|
|
1294
|
+
result.put("tamanho", cursor.getLong(sizeIndex));
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
} finally {
|
|
1298
|
+
cursor.close();
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
return result;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
private void writePickedFile(Uri uri, JSONObject options) throws Exception {
|
|
1305
|
+
OutputStream outputStream = context().getContentResolver().openOutputStream(uri);
|
|
1306
|
+
if (outputStream == null) {
|
|
1307
|
+
throw new Exception("Could not open output stream.");
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
try {
|
|
1311
|
+
InputStream inputStream;
|
|
1312
|
+
String base64 = options.optString("base64", "");
|
|
1313
|
+
if (base64.length() > 0) {
|
|
1314
|
+
inputStream = new ByteArrayInputStream(Base64.decode(base64, Base64.DEFAULT));
|
|
1315
|
+
} else {
|
|
1316
|
+
String content = options.optString("conteudo", options.optString("content", ""));
|
|
1317
|
+
inputStream = new ByteArrayInputStream(content.getBytes("UTF-8"));
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
byte[] buffer = new byte[8192];
|
|
1321
|
+
int read;
|
|
1322
|
+
while ((read = inputStream.read(buffer)) != -1) {
|
|
1323
|
+
outputStream.write(buffer, 0, read);
|
|
1324
|
+
}
|
|
1325
|
+
inputStream.close();
|
|
1326
|
+
} finally {
|
|
1327
|
+
outputStream.close();
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
private JSONObject deviceInfo() throws Exception {
|
|
1332
|
+
JSONObject result = new JSONObject();
|
|
1333
|
+
result.put("manufacturer", Build.MANUFACTURER);
|
|
1334
|
+
result.put("fabricante", Build.MANUFACTURER);
|
|
1335
|
+
result.put("model", Build.MODEL);
|
|
1336
|
+
result.put("modelo", Build.MODEL);
|
|
1337
|
+
result.put("brand", Build.BRAND);
|
|
1338
|
+
result.put("androidVersion", Build.VERSION.RELEASE);
|
|
1339
|
+
result.put("sdkInt", Build.VERSION.SDK_INT);
|
|
1340
|
+
result.put("packageName", context().getPackageName());
|
|
1341
|
+
return result;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
private JSONObject networkInfo() throws Exception {
|
|
1345
|
+
JSONObject result = new JSONObject();
|
|
1346
|
+
ConnectivityManager manager = (ConnectivityManager) context().getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
1347
|
+
boolean connected = false;
|
|
1348
|
+
String type = "unknown";
|
|
1349
|
+
|
|
1350
|
+
if (manager != null) {
|
|
1351
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1352
|
+
Network network = manager.getActiveNetwork();
|
|
1353
|
+
NetworkCapabilities capabilities = network == null ? null : manager.getNetworkCapabilities(network);
|
|
1354
|
+
connected = capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
|
1355
|
+
if (capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
|
|
1356
|
+
type = "wifi";
|
|
1357
|
+
} else if (capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
|
|
1358
|
+
type = "cellular";
|
|
1359
|
+
} else if (capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
|
|
1360
|
+
type = "ethernet";
|
|
1361
|
+
}
|
|
1362
|
+
} else if (manager.getActiveNetworkInfo() != null) {
|
|
1363
|
+
connected = manager.getActiveNetworkInfo().isConnected();
|
|
1364
|
+
type = manager.getActiveNetworkInfo().getTypeName().toLowerCase();
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
result.put("online", connected);
|
|
1369
|
+
result.put("connected", connected);
|
|
1370
|
+
result.put("tipo", type);
|
|
1371
|
+
result.put("type", type);
|
|
1372
|
+
return result;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
private JSONObject batteryInfo() throws Exception {
|
|
1376
|
+
Intent intent = context().registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
|
1377
|
+
JSONObject result = new JSONObject();
|
|
1378
|
+
if (intent == null) {
|
|
1379
|
+
result.put("level", -1);
|
|
1380
|
+
result.put("nivel", -1);
|
|
1381
|
+
result.put("charging", false);
|
|
1382
|
+
return result;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
|
1386
|
+
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
|
|
1387
|
+
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
|
|
1388
|
+
boolean charging = status == BatteryManager.BATTERY_STATUS_CHARGING
|
|
1389
|
+
|| status == BatteryManager.BATTERY_STATUS_FULL;
|
|
1390
|
+
double percent = scale <= 0 ? -1 : (level * 100.0 / scale);
|
|
1391
|
+
|
|
1392
|
+
result.put("level", percent);
|
|
1393
|
+
result.put("nivel", percent);
|
|
1394
|
+
result.put("charging", charging);
|
|
1395
|
+
result.put("carregando", charging);
|
|
1396
|
+
return result;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
private JSONObject memoryInfo() throws Exception {
|
|
1400
|
+
ActivityManager manager = (ActivityManager) context().getSystemService(Context.ACTIVITY_SERVICE);
|
|
1401
|
+
ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
|
|
1402
|
+
if (manager != null) {
|
|
1403
|
+
manager.getMemoryInfo(info);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
Runtime runtime = Runtime.getRuntime();
|
|
1407
|
+
JSONObject result = new JSONObject();
|
|
1408
|
+
result.put("availableBytes", info.availMem);
|
|
1409
|
+
result.put("totalBytes", Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN ? info.totalMem : -1);
|
|
1410
|
+
result.put("lowMemory", info.lowMemory);
|
|
1411
|
+
result.put("thresholdBytes", info.threshold);
|
|
1412
|
+
result.put("appUsedBytes", runtime.totalMemory() - runtime.freeMemory());
|
|
1413
|
+
result.put("appMaxBytes", runtime.maxMemory());
|
|
1414
|
+
return result;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
private JSONObject storageInfo() throws Exception {
|
|
1418
|
+
JSONObject result = new JSONObject();
|
|
1419
|
+
result.put("internal", statFsInfo(Environment.getDataDirectory()));
|
|
1420
|
+
result.put("cache", statFsInfo(context().getCacheDir()));
|
|
1421
|
+
if (context().getExternalFilesDir(null) != null) {
|
|
1422
|
+
result.put("appExternal", statFsInfo(context().getExternalFilesDir(null)));
|
|
1423
|
+
}
|
|
1424
|
+
return result;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
private JSONObject statFsInfo(java.io.File file) throws Exception {
|
|
1428
|
+
StatFs statFs = new StatFs(file.getAbsolutePath());
|
|
1429
|
+
long blockSize = statFs.getBlockSizeLong();
|
|
1430
|
+
long total = statFs.getBlockCountLong() * blockSize;
|
|
1431
|
+
long available = statFs.getAvailableBlocksLong() * blockSize;
|
|
1432
|
+
JSONObject result = new JSONObject();
|
|
1433
|
+
result.put("path", file.getAbsolutePath());
|
|
1434
|
+
result.put("totalBytes", total);
|
|
1435
|
+
result.put("availableBytes", available);
|
|
1436
|
+
result.put("usedBytes", total - available);
|
|
1437
|
+
return result;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
private JSONObject performanceInfo() throws Exception {
|
|
1441
|
+
JSONObject result = new JSONObject();
|
|
1442
|
+
result.put("timestamp", System.currentTimeMillis());
|
|
1443
|
+
result.put("memory", memoryInfo());
|
|
1444
|
+
result.put("storage", storageInfo());
|
|
1445
|
+
result.put("battery", batteryInfo());
|
|
1446
|
+
result.put("network", networkInfo());
|
|
1447
|
+
return result;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
private JSONObject openAppsMemoryInfo() throws Exception {
|
|
1451
|
+
ActivityManager manager = (ActivityManager) context().getSystemService(Context.ACTIVITY_SERVICE);
|
|
1452
|
+
JSONArray apps = new JSONArray();
|
|
1453
|
+
JSONObject byName = new JSONObject();
|
|
1454
|
+
JSONObject result = new JSONObject();
|
|
1455
|
+
|
|
1456
|
+
result.put("timestamp", System.currentTimeMillis());
|
|
1457
|
+
result.put("limited", true);
|
|
1458
|
+
result.put("observacao", "Android moderno limita a lista de apps de terceiros por privacidade; o retorno mostra apenas processos visiveis para este app.");
|
|
1459
|
+
result.put("note", "Modern Android limits third-party app process visibility for privacy; this returns only processes visible to this app.");
|
|
1460
|
+
|
|
1461
|
+
if (manager == null) {
|
|
1462
|
+
result.put("apps", apps);
|
|
1463
|
+
result.put("porNome", byName);
|
|
1464
|
+
result.put("byName", byName);
|
|
1465
|
+
return result;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
List<ActivityManager.RunningAppProcessInfo> processes = manager.getRunningAppProcesses();
|
|
1469
|
+
if (processes == null) {
|
|
1470
|
+
result.put("apps", apps);
|
|
1471
|
+
result.put("porNome", byName);
|
|
1472
|
+
result.put("byName", byName);
|
|
1473
|
+
return result;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
for (ActivityManager.RunningAppProcessInfo process : processes) {
|
|
1477
|
+
Debug.MemoryInfo[] memoryInfos = manager.getProcessMemoryInfo(new int[] { process.pid });
|
|
1478
|
+
Debug.MemoryInfo memory = memoryInfos != null && memoryInfos.length > 0 ? memoryInfos[0] : null;
|
|
1479
|
+
long pssKb = memory == null ? 0 : memory.getTotalPss();
|
|
1480
|
+
long ramBytes = pssKb * 1024L;
|
|
1481
|
+
String packageName = process.pkgList != null && process.pkgList.length > 0 ? process.pkgList[0] : process.processName;
|
|
1482
|
+
String name = appLabel(packageName, process.processName);
|
|
1483
|
+
|
|
1484
|
+
JSONObject item = new JSONObject();
|
|
1485
|
+
item.put("name", name);
|
|
1486
|
+
item.put("nome", name);
|
|
1487
|
+
item.put("packageName", packageName);
|
|
1488
|
+
item.put("pacote", packageName);
|
|
1489
|
+
item.put("processName", process.processName);
|
|
1490
|
+
item.put("pid", process.pid);
|
|
1491
|
+
item.put("importance", process.importance);
|
|
1492
|
+
item.put("importanceName", importanceName(process.importance));
|
|
1493
|
+
item.put("ramBytes", ramBytes);
|
|
1494
|
+
item.put("ramMb", Math.round((ramBytes / 1024.0 / 1024.0) * 100.0) / 100.0);
|
|
1495
|
+
item.put("pssKb", pssKb);
|
|
1496
|
+
if (memory != null) {
|
|
1497
|
+
item.put("privateDirtyKb", memory.getTotalPrivateDirty());
|
|
1498
|
+
item.put("sharedDirtyKb", memory.getTotalSharedDirty());
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
JSONArray packages = new JSONArray();
|
|
1502
|
+
if (process.pkgList != null) {
|
|
1503
|
+
for (String packageItem : process.pkgList) {
|
|
1504
|
+
packages.put(packageItem);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
item.put("packages", packages);
|
|
1508
|
+
item.put("pacotes", packages);
|
|
1509
|
+
apps.put(item);
|
|
1510
|
+
|
|
1511
|
+
JSONObject summary = new JSONObject();
|
|
1512
|
+
summary.put("ramBytes", ramBytes);
|
|
1513
|
+
summary.put("ramMb", Math.round((ramBytes / 1024.0 / 1024.0) * 100.0) / 100.0);
|
|
1514
|
+
summary.put("packageName", packageName);
|
|
1515
|
+
summary.put("processName", process.processName);
|
|
1516
|
+
byName.put(name, summary);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
result.put("apps", apps);
|
|
1520
|
+
result.put("porNome", byName);
|
|
1521
|
+
result.put("byName", byName);
|
|
1522
|
+
return result;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
private String appLabel(String packageName, String fallback) {
|
|
1526
|
+
try {
|
|
1527
|
+
PackageManager packageManager = context().getPackageManager();
|
|
1528
|
+
ApplicationInfo info = packageManager.getApplicationInfo(packageName, 0);
|
|
1529
|
+
CharSequence label = packageManager.getApplicationLabel(info);
|
|
1530
|
+
return label == null ? fallback : label.toString();
|
|
1531
|
+
} catch (Exception ignored) {
|
|
1532
|
+
return fallback == null ? packageName : fallback;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
private String importanceName(int importance) {
|
|
1537
|
+
if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
|
|
1538
|
+
return "foreground";
|
|
1539
|
+
}
|
|
1540
|
+
if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
|
|
1541
|
+
return "visible";
|
|
1542
|
+
}
|
|
1543
|
+
if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE) {
|
|
1544
|
+
return "service";
|
|
1545
|
+
}
|
|
1546
|
+
if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED) {
|
|
1547
|
+
return "cached";
|
|
1548
|
+
}
|
|
1549
|
+
return "other";
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
private JSONObject permissionStatus(JSONArray requested) throws Exception {
|
|
1553
|
+
JSONArray permissions = requested == null ? new JSONArray() : requested;
|
|
1554
|
+
JSONObject result = new JSONObject();
|
|
1555
|
+
for (int index = 0; index < permissions.length(); index += 1) {
|
|
1556
|
+
String permission = androidPermissionName(permissions.optString(index, ""));
|
|
1557
|
+
if (permission.length() == 0) {
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
result.put(permission, ContextCompat.checkSelfPermission(context(), permission) == PackageManager.PERMISSION_GRANTED);
|
|
1561
|
+
}
|
|
1562
|
+
return result;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
private String androidPermissionName(String permission) {
|
|
1566
|
+
String value = permission == null ? "" : permission.trim();
|
|
1567
|
+
if (value.length() == 0 || value.indexOf('.') >= 0) {
|
|
1568
|
+
return value;
|
|
1569
|
+
}
|
|
1570
|
+
return "android.permission." + value;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
private JSONObject baseEvent(String type) {
|
|
1574
|
+
JSONObject detail = new JSONObject();
|
|
1575
|
+
try {
|
|
1576
|
+
detail.put("type", type);
|
|
1577
|
+
detail.put("tipo", type);
|
|
1578
|
+
detail.put("timestamp", System.currentTimeMillis());
|
|
1579
|
+
} catch (Exception ignored) {
|
|
1580
|
+
}
|
|
1581
|
+
return detail;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
private void registerSystemReceiver() {
|
|
1585
|
+
if (systemReceiver != null) {
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
systemReceiver = new BroadcastReceiver() {
|
|
1590
|
+
@Override
|
|
1591
|
+
public void onReceive(Context context, Intent intent) {
|
|
1592
|
+
if (intent == null || intent.getAction() == null) {
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
try {
|
|
1597
|
+
if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
|
|
1598
|
+
dispatchEvent("bateria:mudou", batteryInfo());
|
|
1599
|
+
} else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
|
|
1600
|
+
dispatchEvent("rede:mudou", networkInfo());
|
|
1601
|
+
}
|
|
1602
|
+
} catch (Exception ignored) {
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1607
|
+
IntentFilter filter = new IntentFilter();
|
|
1608
|
+
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
|
|
1609
|
+
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
|
1610
|
+
ContextCompat.registerReceiver(context(), systemReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
private void unregisterSystemReceiver() {
|
|
1614
|
+
if (systemReceiver == null) {
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
try {
|
|
1619
|
+
context().unregisterReceiver(systemReceiver);
|
|
1620
|
+
} catch (Exception ignored) {
|
|
1621
|
+
}
|
|
1622
|
+
systemReceiver = null;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
private void handleLinkIntent(Intent intent, boolean dispatchToJs) {
|
|
1626
|
+
if (intent == null || intent.getData() == null) {
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
JSONObject detail = new JSONObject();
|
|
1631
|
+
try {
|
|
1632
|
+
Uri uri = intent.getData();
|
|
1633
|
+
detail.put("url", uri.toString());
|
|
1634
|
+
detail.put("scheme", uri.getScheme());
|
|
1635
|
+
detail.put("host", uri.getHost());
|
|
1636
|
+
detail.put("path", uri.getPath());
|
|
1637
|
+
detail.put("query", uri.getQuery());
|
|
1638
|
+
detail.put("timestamp", System.currentTimeMillis());
|
|
1639
|
+
} catch (Exception ignored) {
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
initialLink = detail;
|
|
1643
|
+
if (dispatchToJs) {
|
|
1644
|
+
dispatchEvent("link:aberto", detail);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
private void dispatchEvent(String type, JSONObject detail) {
|
|
1649
|
+
if (webView == null) {
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
JSONObject payload = detail == null ? baseEvent(type) : detail;
|
|
1654
|
+
try {
|
|
1655
|
+
payload.put("type", type);
|
|
1656
|
+
payload.put("tipo", type);
|
|
1657
|
+
if (!payload.has("timestamp")) {
|
|
1658
|
+
payload.put("timestamp", System.currentTimeMillis());
|
|
1659
|
+
}
|
|
1660
|
+
} catch (Exception ignored) {
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
final String script = "(function(){var detail=" + payload.toString()
|
|
1664
|
+
+ ";window.dispatchEvent(new CustomEvent('html2apk:event',{detail:detail}));"
|
|
1665
|
+
+ "window.dispatchEvent(new CustomEvent('html2apk:'+detail.type,{detail:detail}));})();";
|
|
1666
|
+
|
|
1667
|
+
cordova.getActivity().runOnUiThread(new Runnable() {
|
|
1668
|
+
@Override
|
|
1669
|
+
public void run() {
|
|
1670
|
+
webView.getEngine().evaluateJavascript(script, null);
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
private void handleNotificationIntent(Intent intent, boolean dispatchToJs) {
|
|
1676
|
+
if (intent == null || !intent.getBooleanExtra(EXTRA_NOTIFICATION_CLICKED, false)) {
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
JSONObject detail = parseDetail(intent.getStringExtra(EXTRA_NOTIFICATION_DETAIL));
|
|
1681
|
+
initialNotification = detail;
|
|
1682
|
+
|
|
1683
|
+
if (dispatchToJs) {
|
|
1684
|
+
dispatchNotificationClick(detail);
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
intent.removeExtra(EXTRA_NOTIFICATION_CLICKED);
|
|
1688
|
+
intent.removeExtra(EXTRA_NOTIFICATION_DETAIL);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
private void dispatchNotificationClick(JSONObject detail) {
|
|
1692
|
+
if (detail == null || webView == null) {
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
dispatchEvent("notificacao:clicada", detail);
|
|
1697
|
+
|
|
1698
|
+
final String script = "(function(){var detail=" + detail.toString()
|
|
1699
|
+
+ ";window.dispatchEvent(new CustomEvent('html2apk:notification',{detail:detail}));})();";
|
|
1700
|
+
|
|
1701
|
+
cordova.getActivity().runOnUiThread(new Runnable() {
|
|
1702
|
+
@Override
|
|
1703
|
+
public void run() {
|
|
1704
|
+
webView.getEngine().evaluateJavascript(script, null);
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
private JSONObject parseDetail(String raw) {
|
|
1710
|
+
try {
|
|
1711
|
+
if (raw == null || raw.length() == 0) {
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
return new JSONObject(raw);
|
|
1715
|
+
} catch (Exception ignored) {
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
static PendingIntent createContentIntent(Context context, int id, JSONObject detail) {
|
|
1721
|
+
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
|
|
1722
|
+
if (launchIntent == null) {
|
|
1723
|
+
launchIntent = new Intent();
|
|
1724
|
+
launchIntent.setPackage(context.getPackageName());
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
1728
|
+
launchIntent.putExtra(EXTRA_NOTIFICATION_CLICKED, true);
|
|
1729
|
+
launchIntent.putExtra(EXTRA_NOTIFICATION_DETAIL, detail == null ? "{}" : detail.toString());
|
|
1730
|
+
|
|
1731
|
+
return PendingIntent.getActivity(
|
|
1732
|
+
context,
|
|
1733
|
+
id,
|
|
1734
|
+
launchIntent,
|
|
1735
|
+
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
static boolean canScheduleExactAlarms(Context context) {
|
|
1740
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
1741
|
+
return true;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
|
1745
|
+
return alarmManager != null && alarmManager.canScheduleExactAlarms();
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
static JSONArray loopNotifications(JSONObject options) {
|
|
1749
|
+
JSONArray notifications = options.optJSONArray("notificacoes");
|
|
1750
|
+
if (notifications == null) {
|
|
1751
|
+
notifications = options.optJSONArray("notifications");
|
|
1752
|
+
}
|
|
1753
|
+
if (notifications == null) {
|
|
1754
|
+
notifications = options.optJSONArray("items");
|
|
1755
|
+
}
|
|
1756
|
+
return notifications == null ? new JSONArray() : notifications;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
static JSONObject notificationDisplayOptions(JSONObject options) throws Exception {
|
|
1760
|
+
JSONArray notifications = loopNotifications(options);
|
|
1761
|
+
if (notifications.length() == 0) {
|
|
1762
|
+
return options;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
int index = Math.max(0, options.optInt("loopIndex", options.optInt("indiceLoop", 0)));
|
|
1766
|
+
JSONObject current = notifications.optJSONObject(index % notifications.length());
|
|
1767
|
+
JSONObject displayOptions = new JSONObject(options.toString());
|
|
1768
|
+
if (current != null) {
|
|
1769
|
+
JSONArray names = current.names();
|
|
1770
|
+
if (names != null) {
|
|
1771
|
+
for (int nameIndex = 0; nameIndex < names.length(); nameIndex += 1) {
|
|
1772
|
+
String name = names.optString(nameIndex);
|
|
1773
|
+
displayOptions.put(name, current.opt(name));
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
displayOptions.put("id", notificationId(options));
|
|
1779
|
+
displayOptions.put("loopIndex", index);
|
|
1780
|
+
displayOptions.put("indiceLoop", index);
|
|
1781
|
+
displayOptions.put("loopTotal", notifications.length());
|
|
1782
|
+
displayOptions.put("totalLoop", notifications.length());
|
|
1783
|
+
return displayOptions;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
static long repeatInterval(JSONObject options) {
|
|
1787
|
+
long interval = intervalFromObject(options.opt("intervalo"));
|
|
1788
|
+
if (interval <= 0) {
|
|
1789
|
+
interval = intervalFromObject(options.opt("interval"));
|
|
1790
|
+
}
|
|
1791
|
+
if (interval <= 0) {
|
|
1792
|
+
interval = intervalFromObject(options.opt("aCada"));
|
|
1793
|
+
}
|
|
1794
|
+
if (interval <= 0) {
|
|
1795
|
+
interval = intervalFromObject(options.opt("every"));
|
|
1796
|
+
}
|
|
1797
|
+
if (interval <= 0) {
|
|
1798
|
+
interval = intervalFromObject(options.opt("repetir"));
|
|
1799
|
+
}
|
|
1800
|
+
if (interval <= 0) {
|
|
1801
|
+
interval = intervalFromObject(options.opt("repeat"));
|
|
1802
|
+
}
|
|
1803
|
+
return interval;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
static long nextRepeatTime(JSONObject options, long after) {
|
|
1807
|
+
long interval = repeatInterval(options);
|
|
1808
|
+
if (interval <= 0) {
|
|
1809
|
+
return 0;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
long next = options.optLong("when", options.optLong("quando", after)) + interval;
|
|
1813
|
+
while (next <= after) {
|
|
1814
|
+
next += interval;
|
|
1815
|
+
}
|
|
1816
|
+
return next;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
static JSONObject nextRepeatOptions(JSONObject options, long nextWhen) throws Exception {
|
|
1820
|
+
JSONObject next = new JSONObject(options.toString());
|
|
1821
|
+
JSONArray notifications = loopNotifications(options);
|
|
1822
|
+
int nextIndex = Math.max(0, options.optInt("loopIndex", options.optInt("indiceLoop", 0))) + 1;
|
|
1823
|
+
int runCount = Math.max(0, options.optInt("_runCount", options.optInt("runCount", 0))) + 1;
|
|
1824
|
+
|
|
1825
|
+
next.put("when", nextWhen);
|
|
1826
|
+
next.put("quando", nextWhen);
|
|
1827
|
+
next.put("loopIndex", notifications.length() == 0 ? nextIndex : nextIndex % notifications.length());
|
|
1828
|
+
next.put("indiceLoop", notifications.length() == 0 ? nextIndex : nextIndex % notifications.length());
|
|
1829
|
+
next.put("_runCount", runCount);
|
|
1830
|
+
next.put("runCount", runCount);
|
|
1831
|
+
return next;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
static boolean shouldStopRepeating(JSONObject options, long nextWhen) {
|
|
1835
|
+
long until = options.optLong("ate", options.optLong("until", 0));
|
|
1836
|
+
if (until > 0 && nextWhen > until) {
|
|
1837
|
+
return true;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
int maxRuns = options.optInt("vezes", options.optInt("times", options.optInt("count", options.optInt("limit", 0))));
|
|
1841
|
+
int runCount = Math.max(0, options.optInt("_runCount", options.optInt("runCount", 0))) + 1;
|
|
1842
|
+
return maxRuns > 0 && runCount >= maxRuns;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
static void rescheduleRepeatingNotification(Context context, int id, JSONObject options, boolean exactAllowed) {
|
|
1846
|
+
try {
|
|
1847
|
+
long nextWhen = nextRepeatTime(options, System.currentTimeMillis());
|
|
1848
|
+
if (nextWhen <= 0 || shouldStopRepeating(options, nextWhen)) {
|
|
1849
|
+
NotificationStore.remove(context, id);
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
JSONObject nextOptions = nextRepeatOptions(options, nextWhen);
|
|
1854
|
+
nextOptions.put("id", id);
|
|
1855
|
+
NotificationStore.save(context, id, nextWhen, nextOptions);
|
|
1856
|
+
NotificationReceiver.schedule(context, id, nextWhen, nextOptions, exactAllowed);
|
|
1857
|
+
} catch (Exception ignored) {
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
private static long intervalFromObject(Object value) {
|
|
1862
|
+
if (value == null || value == JSONObject.NULL) {
|
|
1863
|
+
return 0;
|
|
1864
|
+
}
|
|
1865
|
+
if (value instanceof Number) {
|
|
1866
|
+
return Math.max(0, ((Number) value).longValue());
|
|
1867
|
+
}
|
|
1868
|
+
if (value instanceof JSONObject) {
|
|
1869
|
+
JSONObject object = (JSONObject) value;
|
|
1870
|
+
long nested = intervalFromObject(object.opt("intervalo"));
|
|
1871
|
+
if (nested <= 0) {
|
|
1872
|
+
nested = intervalFromObject(object.opt("interval"));
|
|
1873
|
+
}
|
|
1874
|
+
if (nested <= 0) {
|
|
1875
|
+
nested = intervalFromObject(object.opt("aCada"));
|
|
1876
|
+
}
|
|
1877
|
+
if (nested <= 0) {
|
|
1878
|
+
nested = intervalFromObject(object.opt("every"));
|
|
1879
|
+
}
|
|
1880
|
+
return nested;
|
|
1881
|
+
}
|
|
1882
|
+
if (value instanceof Boolean) {
|
|
1883
|
+
return 0;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
return parseIntervalString(String.valueOf(value));
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
private static long parseIntervalString(String value) {
|
|
1890
|
+
String text = value == null ? "" : value.trim().toLowerCase();
|
|
1891
|
+
if (text.length() == 0) {
|
|
1892
|
+
return 0;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
int unitStart = text.length();
|
|
1896
|
+
for (int index = 0; index < text.length(); index += 1) {
|
|
1897
|
+
char character = text.charAt(index);
|
|
1898
|
+
if (!(Character.isDigit(character) || character == '.')) {
|
|
1899
|
+
unitStart = index;
|
|
1900
|
+
break;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
try {
|
|
1905
|
+
double amount = Double.parseDouble(text.substring(0, unitStart).trim());
|
|
1906
|
+
String unit = text.substring(unitStart).trim();
|
|
1907
|
+
if ("s".equals(unit) || "seg".equals(unit)) {
|
|
1908
|
+
return Math.round(amount * 1000);
|
|
1909
|
+
}
|
|
1910
|
+
if ("m".equals(unit) || "min".equals(unit)) {
|
|
1911
|
+
return Math.round(amount * 60 * 1000);
|
|
1912
|
+
}
|
|
1913
|
+
if ("h".equals(unit) || "hr".equals(unit)) {
|
|
1914
|
+
return Math.round(amount * 60 * 60 * 1000);
|
|
1915
|
+
}
|
|
1916
|
+
if ("d".equals(unit) || "dia".equals(unit) || "dias".equals(unit)) {
|
|
1917
|
+
return Math.round(amount * 24 * 60 * 60 * 1000);
|
|
1918
|
+
}
|
|
1919
|
+
return Math.round(amount);
|
|
1920
|
+
} catch (Exception ignored) {
|
|
1921
|
+
return 0;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
private static int notificationIdFromObject(Object input) {
|
|
1926
|
+
if (input instanceof Number) {
|
|
1927
|
+
return ((Number) input).intValue();
|
|
1928
|
+
}
|
|
1929
|
+
if (input instanceof JSONObject) {
|
|
1930
|
+
return notificationId((JSONObject) input);
|
|
1931
|
+
}
|
|
1932
|
+
try {
|
|
1933
|
+
return Integer.parseInt(String.valueOf(input));
|
|
1934
|
+
} catch (Exception ignored) {
|
|
1935
|
+
return 0;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
static JSONObject detailPayload(JSONObject options) throws Exception {
|
|
1940
|
+
JSONObject detail = new JSONObject();
|
|
1941
|
+
JSONObject click = options.optJSONObject("aoClicar");
|
|
1942
|
+
if (click == null) {
|
|
1943
|
+
click = options.optJSONObject("onClick");
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
detail.put("id", notificationId(options));
|
|
1947
|
+
detail.put("title", title(options));
|
|
1948
|
+
detail.put("titulo", title(options));
|
|
1949
|
+
detail.put("text", text(options));
|
|
1950
|
+
detail.put("texto", text(options));
|
|
530
1951
|
detail.put("when", options.optLong("quando", options.optLong("when", System.currentTimeMillis())));
|
|
531
1952
|
detail.put("clickedAt", System.currentTimeMillis());
|
|
532
1953
|
detail.put("onClick", click == null ? new JSONObject().put("action", "open-app") : click);
|
|
@@ -534,6 +1955,37 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
534
1955
|
return detail;
|
|
535
1956
|
}
|
|
536
1957
|
|
|
1958
|
+
static void addNotificationActions(NotificationCompat.Builder builder, Context context, int notificationId, JSONObject options) {
|
|
1959
|
+
JSONArray actions = options.optJSONArray("acoes");
|
|
1960
|
+
if (actions == null) {
|
|
1961
|
+
actions = options.optJSONArray("actions");
|
|
1962
|
+
}
|
|
1963
|
+
if (actions == null) {
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
for (int index = 0; index < actions.length(); index += 1) {
|
|
1968
|
+
JSONObject action = actions.optJSONObject(index);
|
|
1969
|
+
if (action == null) {
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
try {
|
|
1974
|
+
String title = action.optString("titulo", action.optString("title", action.optString("id", "Abrir")));
|
|
1975
|
+
JSONObject detail = detailPayload(options);
|
|
1976
|
+
detail.put("notificationAction", action.optString("id", String.valueOf(index)));
|
|
1977
|
+
detail.put("acaoNotificacao", action.optString("id", String.valueOf(index)));
|
|
1978
|
+
detail.put("action", action);
|
|
1979
|
+
builder.addAction(
|
|
1980
|
+
context.getApplicationInfo().icon,
|
|
1981
|
+
title,
|
|
1982
|
+
createContentIntent(context, notificationId * 100 + index + 1, detail)
|
|
1983
|
+
);
|
|
1984
|
+
} catch (Exception ignored) {
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
|
|
537
1989
|
static int notificationId(JSONObject options) {
|
|
538
1990
|
int id = options.optInt("id", 0);
|
|
539
1991
|
if (id != 0) {
|