@capgo/capacitor-downloader 8.4.1 → 8.5.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 +47 -11
- package/android/build.gradle +3 -0
- package/android/src/main/java/ee/forgr/capacitor/plugin/downloader/AndroidDownloadDestination.java +143 -0
- package/android/src/main/java/ee/forgr/capacitor/plugin/downloader/AndroidHttpEngineSelector.java +33 -0
- package/android/src/main/java/ee/forgr/capacitor/plugin/downloader/CapacitorDownloaderPlugin.java +276 -11
- package/android/src/main/java/ee/forgr/capacitor/plugin/downloader/DownloadCompletionNotifier.java +70 -0
- package/android/src/main/java/ee/forgr/capacitor/plugin/downloader/OkHttpDownloadExecutor.java +420 -0
- package/dist/docs.json +16 -4
- package/dist/esm/definitions.d.ts +38 -6
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorDownloaderPlugin/CapacitorDownloaderPlugin.swift +1 -1
- package/package.json +1 -1
package/android/src/main/java/ee/forgr/capacitor/plugin/downloader/CapacitorDownloaderPlugin.java
CHANGED
|
@@ -27,13 +27,15 @@ import java.util.concurrent.atomic.AtomicLong;
|
|
|
27
27
|
@CapacitorPlugin(name = "CapacitorDownloader")
|
|
28
28
|
public class CapacitorDownloaderPlugin extends Plugin {
|
|
29
29
|
|
|
30
|
-
private final String pluginVersion = "8.
|
|
30
|
+
private final String pluginVersion = "8.5.0";
|
|
31
31
|
|
|
32
32
|
private DownloadManager downloadManager;
|
|
33
33
|
private final Map<String, Long> downloads = new ConcurrentHashMap<>();
|
|
34
|
+
private final Map<String, File> okHttpDestinationFiles = new ConcurrentHashMap<>();
|
|
34
35
|
private final Map<String, Long> downloadGenerations = new ConcurrentHashMap<>();
|
|
35
36
|
private final AtomicLong pendingDownloadSequence = new AtomicLong(0);
|
|
36
37
|
private ExecutorService downloadManagerExecutor;
|
|
38
|
+
private OkHttpDownloadExecutor okHttpDownloadExecutor;
|
|
37
39
|
private final Handler handler = new Handler(Looper.getMainLooper());
|
|
38
40
|
private BroadcastReceiver downloadReceiver;
|
|
39
41
|
private DownloadCompletedNotificationStore completedNotificationStore;
|
|
@@ -42,6 +44,7 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
42
44
|
public void load() {
|
|
43
45
|
downloadManager = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
|
|
44
46
|
downloadManagerExecutor = Executors.newCachedThreadPool();
|
|
47
|
+
okHttpDownloadExecutor = new OkHttpDownloadExecutor(getContext());
|
|
45
48
|
completedNotificationStore = new DownloadCompletedNotificationStore(getContext());
|
|
46
49
|
registerDownloadReceiver();
|
|
47
50
|
}
|
|
@@ -75,11 +78,15 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
private static boolean isPendingDownload(long downloadId) {
|
|
78
|
-
return downloadId < 0;
|
|
81
|
+
return downloadId < 0 && downloadId != OkHttpDownloadExecutor.TRACKED_DOWNLOAD_ID;
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
private long reservePendingDownload() {
|
|
82
|
-
|
|
85
|
+
long token;
|
|
86
|
+
do {
|
|
87
|
+
token = -pendingDownloadSequence.incrementAndGet();
|
|
88
|
+
} while (token == OkHttpDownloadExecutor.TRACKED_DOWNLOAD_ID);
|
|
89
|
+
return token;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
private boolean isTrackedDownload(String id, long systemDownloadId) {
|
|
@@ -87,6 +94,11 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
87
94
|
return trackedDownloadId != null && trackedDownloadId.longValue() == systemDownloadId;
|
|
88
95
|
}
|
|
89
96
|
|
|
97
|
+
private boolean isCurrentOkHttpDownload(String id) {
|
|
98
|
+
Long trackedDownloadId = downloads.get(id);
|
|
99
|
+
return trackedDownloadId != null && isOkHttpDownload(trackedDownloadId);
|
|
100
|
+
}
|
|
101
|
+
|
|
90
102
|
private long nextGeneration(String id) {
|
|
91
103
|
while (true) {
|
|
92
104
|
Long current = downloadGenerations.get(id);
|
|
@@ -105,6 +117,21 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
105
117
|
return downloadGenerations.get(id);
|
|
106
118
|
}
|
|
107
119
|
|
|
120
|
+
private static boolean usesExtendedDestinationRoots(String androidHttpEngine) {
|
|
121
|
+
if (androidHttpEngine == null || androidHttpEngine.isEmpty()) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
if ("downloadManager".equalsIgnoreCase(androidHttpEngine) || "download_manager".equalsIgnoreCase(androidHttpEngine)) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private boolean isCurrentOkHttpGeneration(String id, long expectedGeneration) {
|
|
131
|
+
Long current = generationFor(id);
|
|
132
|
+
return current != null && current.longValue() == expectedGeneration;
|
|
133
|
+
}
|
|
134
|
+
|
|
108
135
|
@PluginMethod
|
|
109
136
|
public void download(PluginCall call) {
|
|
110
137
|
String id = call.getString("id");
|
|
@@ -121,7 +148,71 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
121
148
|
return;
|
|
122
149
|
}
|
|
123
150
|
|
|
124
|
-
final
|
|
151
|
+
final boolean extendedDestinationRoots = usesExtendedDestinationRoots(call.getString("androidHttpEngine"));
|
|
152
|
+
|
|
153
|
+
final AndroidDownloadDestination.ResolvedDestination resolvedDestination;
|
|
154
|
+
try {
|
|
155
|
+
resolvedDestination = AndroidDownloadDestination.resolve(getContext(), destination, extendedDestinationRoots);
|
|
156
|
+
} catch (IllegalArgumentException e) {
|
|
157
|
+
call.reject(e.getMessage(), e);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
final AndroidHttpEngineSelector.Engine httpEngine;
|
|
162
|
+
try {
|
|
163
|
+
httpEngine = AndroidHttpEngineSelector.resolve(call.getString("androidHttpEngine"), resolvedDestination.preferOkHttpInAutoMode);
|
|
164
|
+
} catch (IllegalArgumentException e) {
|
|
165
|
+
call.reject(e.getMessage(), e);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (
|
|
170
|
+
httpEngine == AndroidHttpEngineSelector.Engine.OKHTTP &&
|
|
171
|
+
extendedDestinationRoots &&
|
|
172
|
+
destination.trim().startsWith("public-downloads/")
|
|
173
|
+
) {
|
|
174
|
+
call.reject("OkHttp cannot write to public-downloads/ paths. Use androidHttpEngine 'auto' or 'downloadManager'.");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
final String completionNotificationText = call.getString("downloadCompletedNotificationText");
|
|
179
|
+
|
|
180
|
+
if (httpEngine == AndroidHttpEngineSelector.Engine.OKHTTP) {
|
|
181
|
+
downloadWithOkHttp(
|
|
182
|
+
call,
|
|
183
|
+
id,
|
|
184
|
+
url,
|
|
185
|
+
resolvedDestination.file,
|
|
186
|
+
call.getObject("headers"),
|
|
187
|
+
call.getString("network"),
|
|
188
|
+
call.getString("notification"),
|
|
189
|
+
completionNotificationText
|
|
190
|
+
);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
downloadWithDownloadManager(
|
|
195
|
+
call,
|
|
196
|
+
id,
|
|
197
|
+
url,
|
|
198
|
+
resolvedDestination.file,
|
|
199
|
+
call.getObject("headers"),
|
|
200
|
+
call.getString("network"),
|
|
201
|
+
call.getString("notification"),
|
|
202
|
+
completionNotificationText
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private void downloadWithDownloadManager(
|
|
207
|
+
PluginCall call,
|
|
208
|
+
String id,
|
|
209
|
+
String url,
|
|
210
|
+
File destinationFile,
|
|
211
|
+
JSObject headers,
|
|
212
|
+
String network,
|
|
213
|
+
String notification,
|
|
214
|
+
String completionNotificationText
|
|
215
|
+
) {
|
|
125
216
|
final DownloadManager.Request request;
|
|
126
217
|
try {
|
|
127
218
|
request = new DownloadManager.Request(Uri.parse(url))
|
|
@@ -129,11 +220,9 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
129
220
|
.setAllowedOverMetered(true)
|
|
130
221
|
.setAllowedOverRoaming(true);
|
|
131
222
|
|
|
132
|
-
File destinationFile = new File(getContext().getExternalFilesDir(null), destination);
|
|
133
223
|
Uri destinationUri = Uri.fromFile(destinationFile);
|
|
134
224
|
request.setDestinationUri(destinationUri);
|
|
135
225
|
|
|
136
|
-
JSObject headers = call.getObject("headers");
|
|
137
226
|
if (headers != null) {
|
|
138
227
|
for (Iterator<String> it = headers.keys(); it.hasNext(); ) {
|
|
139
228
|
String key = it.next();
|
|
@@ -141,7 +230,6 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
141
230
|
}
|
|
142
231
|
}
|
|
143
232
|
|
|
144
|
-
String network = call.getString("network");
|
|
145
233
|
if ("wifi-only".equals(network)) {
|
|
146
234
|
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
|
|
147
235
|
} else {
|
|
@@ -158,7 +246,7 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
158
246
|
return;
|
|
159
247
|
}
|
|
160
248
|
final long downloadGeneration = nextGeneration(id);
|
|
161
|
-
completedNotificationStore.setText(id,
|
|
249
|
+
completedNotificationStore.setText(id, completionNotificationText, downloadGeneration);
|
|
162
250
|
runDownloadManagerWork(
|
|
163
251
|
call,
|
|
164
252
|
() -> {
|
|
@@ -205,7 +293,6 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
205
293
|
result.put("status", DownloadManager.STATUS_PENDING);
|
|
206
294
|
call.resolve(result);
|
|
207
295
|
|
|
208
|
-
// Start a periodic progress check
|
|
209
296
|
startProgressCheck(id, downloadId);
|
|
210
297
|
},
|
|
211
298
|
() -> {
|
|
@@ -215,6 +302,137 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
215
302
|
);
|
|
216
303
|
}
|
|
217
304
|
|
|
305
|
+
private void downloadWithOkHttp(
|
|
306
|
+
PluginCall call,
|
|
307
|
+
String id,
|
|
308
|
+
String url,
|
|
309
|
+
File destinationFile,
|
|
310
|
+
JSObject headers,
|
|
311
|
+
String network,
|
|
312
|
+
String notification,
|
|
313
|
+
String completionNotificationText
|
|
314
|
+
) {
|
|
315
|
+
if (notification == null) {
|
|
316
|
+
notification = "completed";
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
final long pendingToken = reservePendingDownload();
|
|
320
|
+
if (downloads.putIfAbsent(id, pendingToken) != null) {
|
|
321
|
+
call.reject("Download already exists");
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
okHttpDestinationFiles.put(id, destinationFile);
|
|
325
|
+
final long downloadGeneration = nextGeneration(id);
|
|
326
|
+
completedNotificationStore.setText(id, completionNotificationText, downloadGeneration);
|
|
327
|
+
|
|
328
|
+
final String notificationMode = notification;
|
|
329
|
+
final long storedGeneration = downloadGeneration;
|
|
330
|
+
runDownloadManagerWork(
|
|
331
|
+
call,
|
|
332
|
+
() -> {
|
|
333
|
+
if (!isTrackedDownload(id, pendingToken)) {
|
|
334
|
+
okHttpDestinationFiles.remove(id);
|
|
335
|
+
completedNotificationStore.remove(id, storedGeneration);
|
|
336
|
+
call.reject("Download was cancelled");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (!okHttpDownloadExecutor.isWifiSatisfied(network)) {
|
|
341
|
+
downloads.remove(id, pendingToken);
|
|
342
|
+
okHttpDestinationFiles.remove(id);
|
|
343
|
+
completedNotificationStore.remove(id, storedGeneration);
|
|
344
|
+
call.reject("Wi-Fi required for this download");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (!okHttpDownloadExecutor.beginTransfer(id)) {
|
|
349
|
+
downloads.remove(id, pendingToken);
|
|
350
|
+
okHttpDestinationFiles.remove(id);
|
|
351
|
+
completedNotificationStore.remove(id, storedGeneration);
|
|
352
|
+
call.reject("Download already exists");
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!downloads.replace(id, pendingToken, OkHttpDownloadExecutor.TRACKED_DOWNLOAD_ID)) {
|
|
357
|
+
okHttpDownloadExecutor.abandonTransfer(id);
|
|
358
|
+
okHttpDestinationFiles.remove(id);
|
|
359
|
+
completedNotificationStore.remove(id, storedGeneration);
|
|
360
|
+
call.reject("Download was cancelled");
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
JSObject result = new JSObject();
|
|
365
|
+
result.put("id", id);
|
|
366
|
+
result.put("status", DownloadManager.STATUS_PENDING);
|
|
367
|
+
call.resolve(result);
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
okHttpDownloadExecutor.start(
|
|
371
|
+
id,
|
|
372
|
+
url,
|
|
373
|
+
destinationFile,
|
|
374
|
+
headers,
|
|
375
|
+
network,
|
|
376
|
+
(downloadId, progress) ->
|
|
377
|
+
handler.post(() -> {
|
|
378
|
+
if (!isCurrentOkHttpDownload(downloadId) || !isCurrentOkHttpGeneration(downloadId, storedGeneration)) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
JSObject progressData = new JSObject();
|
|
382
|
+
progressData.put("id", downloadId);
|
|
383
|
+
progressData.put("progress", progress);
|
|
384
|
+
notifyListeners("downloadProgress", progressData);
|
|
385
|
+
}),
|
|
386
|
+
(downloadId, success, cancelled, errorMessage) ->
|
|
387
|
+
handler.post(() -> {
|
|
388
|
+
if (!isCurrentOkHttpDownload(downloadId) || !isCurrentOkHttpGeneration(downloadId, storedGeneration)) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (cancelled) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (success) {
|
|
395
|
+
JSObject completedData = new JSObject();
|
|
396
|
+
completedData.put("id", downloadId);
|
|
397
|
+
notifyListeners("downloadCompleted", completedData);
|
|
398
|
+
DownloadCompletionNotifier.notifyIfRequested(getContext(), downloadId, notificationMode, true);
|
|
399
|
+
String completionText = completedNotificationStore.takeText(downloadId, storedGeneration);
|
|
400
|
+
if (completionText != null) {
|
|
401
|
+
DownloadCompletedNotificationPresenter.showIfAllowed(getContext(), downloadId, completionText);
|
|
402
|
+
}
|
|
403
|
+
} else {
|
|
404
|
+
okHttpDestinationFiles.remove(downloadId, destinationFile);
|
|
405
|
+
completedNotificationStore.remove(downloadId, storedGeneration);
|
|
406
|
+
JSObject failedData = new JSObject();
|
|
407
|
+
failedData.put("id", downloadId);
|
|
408
|
+
failedData.put("error", errorMessage != null ? errorMessage : "Download failed");
|
|
409
|
+
notifyListeners("downloadFailed", failedData);
|
|
410
|
+
}
|
|
411
|
+
})
|
|
412
|
+
);
|
|
413
|
+
} catch (RuntimeException e) {
|
|
414
|
+
okHttpDownloadExecutor.abandonTransfer(id);
|
|
415
|
+
downloads.remove(id);
|
|
416
|
+
okHttpDestinationFiles.remove(id);
|
|
417
|
+
completedNotificationStore.remove(id, storedGeneration);
|
|
418
|
+
JSObject failedData = new JSObject();
|
|
419
|
+
failedData.put("id", id);
|
|
420
|
+
failedData.put("error", e.getMessage());
|
|
421
|
+
notifyListeners("downloadFailed", failedData);
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
() -> {
|
|
425
|
+
downloads.remove(id, pendingToken);
|
|
426
|
+
okHttpDestinationFiles.remove(id);
|
|
427
|
+
completedNotificationStore.remove(id, storedGeneration);
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
private boolean isOkHttpDownload(long trackedDownloadId) {
|
|
433
|
+
return trackedDownloadId == OkHttpDownloadExecutor.TRACKED_DOWNLOAD_ID;
|
|
434
|
+
}
|
|
435
|
+
|
|
218
436
|
private void startProgressCheck(final String id, final long downloadId) {
|
|
219
437
|
runProgressQuery(id, true, downloadId);
|
|
220
438
|
}
|
|
@@ -232,13 +450,13 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
232
450
|
return;
|
|
233
451
|
}
|
|
234
452
|
|
|
235
|
-
final Long capturedGeneration = generationFor(id);
|
|
236
|
-
|
|
237
453
|
ExecutorService executor = downloadManagerExecutor;
|
|
238
454
|
if (executor == null) {
|
|
239
455
|
return;
|
|
240
456
|
}
|
|
241
457
|
|
|
458
|
+
final Long capturedGeneration = generationFor(id);
|
|
459
|
+
|
|
242
460
|
try {
|
|
243
461
|
executor.execute(() -> {
|
|
244
462
|
ProgressSnapshot snapshot = queryProgressSnapshot(id, systemDownloadId);
|
|
@@ -362,6 +580,7 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
362
580
|
}
|
|
363
581
|
|
|
364
582
|
if (isPendingDownload(downloadId)) {
|
|
583
|
+
okHttpDestinationFiles.remove(id);
|
|
365
584
|
if (stoppedGeneration != null) {
|
|
366
585
|
completedNotificationStore.remove(id, stoppedGeneration);
|
|
367
586
|
}
|
|
@@ -369,6 +588,17 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
369
588
|
return;
|
|
370
589
|
}
|
|
371
590
|
|
|
591
|
+
if (isOkHttpDownload(downloadId)) {
|
|
592
|
+
File destinationFile = okHttpDestinationFiles.remove(id);
|
|
593
|
+
boolean cancelled = okHttpDownloadExecutor.cancel(id, destinationFile);
|
|
594
|
+
okHttpDownloadExecutor.clearCompleted(id);
|
|
595
|
+
if (stoppedGeneration != null) {
|
|
596
|
+
completedNotificationStore.remove(id, stoppedGeneration);
|
|
597
|
+
}
|
|
598
|
+
call.resolve(new JSObject().put("removed", cancelled));
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
372
602
|
final long systemDownloadId = downloadId;
|
|
373
603
|
final String stoppedId = id;
|
|
374
604
|
final long generation = stoppedGeneration != null ? stoppedGeneration.longValue() : 0L;
|
|
@@ -404,6 +634,25 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
404
634
|
return;
|
|
405
635
|
}
|
|
406
636
|
|
|
637
|
+
if (isOkHttpDownload(trackedDownloadId)) {
|
|
638
|
+
OkHttpDownloadExecutor.ActiveDownload active = okHttpDownloadExecutor.getActive(id);
|
|
639
|
+
JSObject result = new JSObject();
|
|
640
|
+
result.put("id", id);
|
|
641
|
+
if (active == null) {
|
|
642
|
+
call.reject("Download not found");
|
|
643
|
+
return;
|
|
644
|
+
} else {
|
|
645
|
+
result.put("status", mapOkHttpStateToDownloadManagerStatus(active.state));
|
|
646
|
+
result.put("bytesDownloaded", active.bytesWritten);
|
|
647
|
+
result.put("bytesTotal", active.bytesTotal);
|
|
648
|
+
if (active.state == OkHttpDownloadExecutor.State.ERROR && active.errorMessage != null) {
|
|
649
|
+
result.put("reasonText", active.errorMessage);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
call.resolve(result);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
407
656
|
final long downloadId = trackedDownloadId;
|
|
408
657
|
runDownloadManagerWork(call, () -> {
|
|
409
658
|
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
|
|
@@ -469,6 +718,22 @@ public class CapacitorDownloaderPlugin extends Plugin {
|
|
|
469
718
|
return result;
|
|
470
719
|
}
|
|
471
720
|
|
|
721
|
+
private int mapOkHttpStateToDownloadManagerStatus(OkHttpDownloadExecutor.State state) {
|
|
722
|
+
switch (state) {
|
|
723
|
+
case PENDING:
|
|
724
|
+
return DownloadManager.STATUS_PENDING;
|
|
725
|
+
case RUNNING:
|
|
726
|
+
return DownloadManager.STATUS_RUNNING;
|
|
727
|
+
case DONE:
|
|
728
|
+
return DownloadManager.STATUS_SUCCESSFUL;
|
|
729
|
+
case ERROR:
|
|
730
|
+
case CANCELLED:
|
|
731
|
+
return DownloadManager.STATUS_FAILED;
|
|
732
|
+
default:
|
|
733
|
+
return DownloadManager.STATUS_PENDING;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
472
737
|
private String getReasonText(int status, int reason) {
|
|
473
738
|
if (status == DownloadManager.STATUS_FAILED) {
|
|
474
739
|
switch (reason) {
|
package/android/src/main/java/ee/forgr/capacitor/plugin/downloader/DownloadCompletionNotifier.java
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
package ee.forgr.capacitor.plugin.downloader;
|
|
2
|
+
|
|
3
|
+
import android.Manifest;
|
|
4
|
+
import android.app.NotificationChannel;
|
|
5
|
+
import android.app.NotificationManager;
|
|
6
|
+
import android.content.Context;
|
|
7
|
+
import android.content.pm.PackageManager;
|
|
8
|
+
import android.os.Build;
|
|
9
|
+
import androidx.core.app.NotificationCompat;
|
|
10
|
+
import androidx.core.content.ContextCompat;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Optional completion-only notifications for OkHttp downloads (DownloadManager has its own).
|
|
14
|
+
*/
|
|
15
|
+
public final class DownloadCompletionNotifier {
|
|
16
|
+
|
|
17
|
+
private static final String CHANNEL_ID = "capgo_capacitor_downloader";
|
|
18
|
+
private static final int NOTIFICATION_ID_BASE = 0x44554c44; // "DOWNL"
|
|
19
|
+
|
|
20
|
+
private DownloadCompletionNotifier() {}
|
|
21
|
+
|
|
22
|
+
public static void notifyIfRequested(Context context, String downloadId, String notificationMode, boolean success) {
|
|
23
|
+
if (context == null) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!"completed".equals(notificationMode)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!canPostNotifications(context)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
35
|
+
if (notificationManager == null) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
ensureChannel(context, notificationManager);
|
|
40
|
+
|
|
41
|
+
String title = success ? "Download complete" : "Download failed";
|
|
42
|
+
String text = success ? "Finished: " + downloadId : "Failed: " + downloadId;
|
|
43
|
+
|
|
44
|
+
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
|
|
45
|
+
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
|
46
|
+
.setContentTitle(title)
|
|
47
|
+
.setContentText(text)
|
|
48
|
+
.setAutoCancel(true)
|
|
49
|
+
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
|
|
50
|
+
|
|
51
|
+
int notificationId = NOTIFICATION_ID_BASE ^ (downloadId != null ? downloadId.hashCode() : 0);
|
|
52
|
+
notificationManager.notify(notificationId, builder.build());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private static boolean canPostNotifications(Context context) {
|
|
56
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private static void ensureChannel(Context context, NotificationManager notificationManager) {
|
|
63
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Capacitor Downloader", NotificationManager.IMPORTANCE_DEFAULT);
|
|
67
|
+
channel.setDescription("Download completion notifications");
|
|
68
|
+
notificationManager.createNotificationChannel(channel);
|
|
69
|
+
}
|
|
70
|
+
}
|