@capgo/capacitor-updater 6.0.0 → 6.0.1

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.
@@ -6,6 +6,8 @@
6
6
 
7
7
  package ee.forgr.capacitor_updater;
8
8
 
9
+ import static android.content.Context.RECEIVER_NOT_EXPORTED;
10
+
9
11
  import android.app.Activity;
10
12
  import android.content.BroadcastReceiver;
11
13
  import android.content.Context;
@@ -21,7 +23,6 @@ import com.android.volley.DefaultRetryPolicy;
21
23
  import com.android.volley.NetworkResponse;
22
24
  import com.android.volley.Request;
23
25
  import com.android.volley.RequestQueue;
24
- import com.android.volley.Response;
25
26
  import com.android.volley.VolleyError;
26
27
  import com.android.volley.toolbox.HttpHeaderParser;
27
28
  import com.android.volley.toolbox.JsonObjectRequest;
@@ -46,6 +47,7 @@ import java.util.ArrayList;
46
47
  import java.util.Date;
47
48
  import java.util.Iterator;
48
49
  import java.util.List;
50
+ import java.util.Objects;
49
51
  import java.util.zip.CRC32;
50
52
  import java.util.zip.ZipEntry;
51
53
  import java.util.zip.ZipInputStream;
@@ -53,10 +55,6 @@ import javax.crypto.SecretKey;
53
55
  import org.json.JSONException;
54
56
  import org.json.JSONObject;
55
57
 
56
- interface Callback {
57
- void callback(JSObject jsoObject);
58
- }
59
-
60
58
  public class CapacitorUpdater {
61
59
 
62
60
  private static final String AB =
@@ -70,14 +68,13 @@ public class CapacitorUpdater {
70
68
  private static final String bundleDirectory = "versions";
71
69
 
72
70
  public static final String TAG = "Capacitor-updater";
73
- public static final int timeout = 20000;
74
-
75
71
  public SharedPreferences.Editor editor;
76
72
  public SharedPreferences prefs;
77
73
 
78
74
  public RequestQueue requestQueue;
79
75
 
80
76
  public File documentsDir;
77
+ public Boolean directUpdate = false;
81
78
  public Activity activity;
82
79
  public String PLUGIN_VERSION = "";
83
80
  public String versionBuild = "";
@@ -87,20 +84,19 @@ public class CapacitorUpdater {
87
84
  public String customId = "";
88
85
  public String statsUrl = "";
89
86
  public String channelUrl = "";
87
+ public String defaultChannel = "";
90
88
  public String appId = "";
91
89
  public String privateKey = "";
92
90
  public String deviceID = "";
91
+ public int timeout = 20000;
93
92
 
94
- private final FilenameFilter filter = new FilenameFilter() {
95
- @Override
96
- public boolean accept(final File f, final String name) {
97
- // ignore directories generated by mac os x
98
- return (
99
- !name.startsWith("__MACOSX") &&
100
- !name.startsWith(".") &&
101
- !name.startsWith(".DS_Store")
102
- );
103
- }
93
+ private final FilenameFilter filter = (f, name) -> {
94
+ // ignore directories generated by mac os x
95
+ return (
96
+ !name.startsWith("__MACOSX") &&
97
+ !name.startsWith(".") &&
98
+ !name.startsWith(".DS_Store")
99
+ );
104
100
  };
105
101
 
106
102
  private boolean isProd() {
@@ -109,9 +105,8 @@ public class CapacitorUpdater {
109
105
 
110
106
  private boolean isEmulator() {
111
107
  return (
112
- (
113
- Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")
114
- ) ||
108
+ (Build.BRAND.startsWith("generic") &&
109
+ Build.DEVICE.startsWith("generic")) ||
115
110
  Build.FINGERPRINT.startsWith("generic") ||
116
111
  Build.FINGERPRINT.startsWith("unknown") ||
117
112
  Build.HARDWARE.contains("goldfish") ||
@@ -139,29 +134,27 @@ public class CapacitorUpdater {
139
134
  return (percent * (max - min)) / 100 + min;
140
135
  }
141
136
 
142
- void notifyDownload(final String id, final int percent) {
143
- return;
144
- }
137
+ void notifyDownload(final String id, final int percent) {}
145
138
 
146
- void notifyListeners(final String id, final JSObject res) {
147
- return;
148
- }
139
+ void directUpdateFinish(final BundleInfo latest) {}
149
140
 
150
- private String randomString(final int len) {
151
- final StringBuilder sb = new StringBuilder(len);
152
- for (int i = 0; i < len; i++) sb.append(
153
- AB.charAt(rnd.nextInt(AB.length()))
154
- );
141
+ void notifyListeners(final String id, final JSObject res) {}
142
+
143
+ private String randomString() {
144
+ final StringBuilder sb = new StringBuilder(10);
145
+ for (int i = 0; i < 10; i++) sb.append(AB.charAt(rnd.nextInt(AB.length())));
155
146
  return sb.toString();
156
147
  }
157
148
 
158
149
  private File unzip(final String id, final File zipFile, final String dest)
159
150
  throws IOException {
160
151
  final File targetDirectory = new File(this.documentsDir, dest);
161
- final ZipInputStream zis = new ZipInputStream(
162
- new BufferedInputStream(new FileInputStream(zipFile))
163
- );
164
- try {
152
+ try (
153
+ final BufferedInputStream bis = new BufferedInputStream(
154
+ new FileInputStream(zipFile)
155
+ );
156
+ final ZipInputStream zis = new ZipInputStream(bis)
157
+ ) {
165
158
  int count;
166
159
  final int bufferSize = 8192;
167
160
  final byte[] buffer = new byte[bufferSize];
@@ -178,6 +171,7 @@ public class CapacitorUpdater {
178
171
  "unzip: Windows path is not supported, please use unix path as require by zip RFC: " +
179
172
  entry.getName()
180
173
  );
174
+ this.sendStats("windows_path_fail");
181
175
  }
182
176
  final File file = new File(targetDirectory, entry.getName());
183
177
  final String canonicalPath = file.getCanonicalPath();
@@ -185,6 +179,7 @@ public class CapacitorUpdater {
185
179
  final File dir = entry.isDirectory() ? file : file.getParentFile();
186
180
 
187
181
  if (!canonicalPath.startsWith(canonicalDir)) {
182
+ this.sendStats("canonical_path_fail");
188
183
  throw new FileNotFoundException(
189
184
  "SecurityException, Failed to ensure directory is the start path : " +
190
185
  canonicalDir +
@@ -193,7 +188,9 @@ public class CapacitorUpdater {
193
188
  );
194
189
  }
195
190
 
191
+ assert dir != null;
196
192
  if (!dir.isDirectory() && !dir.mkdirs()) {
193
+ this.sendStats("directory_path_fail");
197
194
  throw new FileNotFoundException(
198
195
  "Failed to ensure directory: " + dir.getAbsolutePath()
199
196
  );
@@ -220,12 +217,9 @@ public class CapacitorUpdater {
220
217
  lengthRead += entry.getCompressedSize();
221
218
  }
222
219
  return targetDirectory;
223
- } finally {
224
- try {
225
- zis.close();
226
- } catch (final IOException e) {
227
- Log.e(TAG, "Failed to close zip input stream", e);
228
- }
220
+ } catch (IOException e) {
221
+ this.sendStats("unzip_fail");
222
+ throw new IOException("Failed to unzip: " + zipFile.getPath());
229
223
  }
230
224
  }
231
225
 
@@ -237,7 +231,7 @@ public class CapacitorUpdater {
237
231
  );
238
232
  }
239
233
  final File destinationFile = new File(this.documentsDir, dest);
240
- destinationFile.getParentFile().mkdirs();
234
+ Objects.requireNonNull(destinationFile.getParentFile()).mkdirs();
241
235
  final String[] entries = sourceFile.list(this.filter);
242
236
  if (entries == null || entries.length == 0) {
243
237
  throw new IOException(
@@ -254,32 +248,41 @@ public class CapacitorUpdater {
254
248
  }
255
249
 
256
250
  public void onResume() {
257
- this.activity.registerReceiver(
258
- receiver,
259
- new IntentFilter(DownloadService.NOTIFICATION)
260
- );
251
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
252
+ this.activity.registerReceiver(
253
+ receiver,
254
+ new IntentFilter(DownloadService.NOTIFICATION),
255
+ RECEIVER_NOT_EXPORTED
256
+ );
257
+ } else {
258
+ this.activity.registerReceiver(
259
+ receiver,
260
+ new IntentFilter(DownloadService.NOTIFICATION)
261
+ );
262
+ }
261
263
  }
262
264
 
263
265
  public void onPause() {
264
266
  this.activity.unregisterReceiver(receiver);
265
267
  }
266
268
 
267
- private BroadcastReceiver receiver = new BroadcastReceiver() {
269
+ private final BroadcastReceiver receiver = new BroadcastReceiver() {
268
270
  @Override
269
271
  public void onReceive(Context context, Intent intent) {
270
272
  String action = intent.getAction();
271
273
  Bundle bundle = intent.getExtras();
272
274
  if (bundle != null) {
273
- if (action == DownloadService.PERCENTDOWNLOAD) {
275
+ if (Objects.equals(action, DownloadService.PERCENTDOWNLOAD)) {
274
276
  String id = bundle.getString(DownloadService.ID);
275
277
  int percent = bundle.getInt(DownloadService.PERCENT);
276
278
  CapacitorUpdater.this.notifyDownload(id, percent);
277
- } else if (action == DownloadService.NOTIFICATION) {
279
+ } else if (Objects.equals(action, DownloadService.NOTIFICATION)) {
278
280
  String id = bundle.getString(DownloadService.ID);
279
281
  String dest = bundle.getString(DownloadService.FILEDEST);
280
282
  String version = bundle.getString(DownloadService.VERSION);
281
283
  String sessionKey = bundle.getString(DownloadService.SESSIONKEY);
282
284
  String checksum = bundle.getString(DownloadService.CHECKSUM);
285
+ String error = bundle.getString(DownloadService.ERROR);
283
286
  Log.i(
284
287
  CapacitorUpdater.TAG,
285
288
  "res " +
@@ -291,7 +294,9 @@ public class CapacitorUpdater {
291
294
  " " +
292
295
  sessionKey +
293
296
  " " +
294
- checksum
297
+ checksum +
298
+ " " +
299
+ error
295
300
  );
296
301
  if (dest == null) {
297
302
  final JSObject ret = new JSObject();
@@ -299,11 +304,12 @@ public class CapacitorUpdater {
299
304
  "version",
300
305
  CapacitorUpdater.this.getCurrentBundle().getVersionName()
301
306
  );
307
+ if ("low_mem_fail".equals(error)) {
308
+ CapacitorUpdater.this.sendStats("low_mem_fail", version);
309
+ }
310
+ ret.put("error", "download_fail");
311
+ CapacitorUpdater.this.sendStats("download_fail", version);
302
312
  CapacitorUpdater.this.notifyListeners("downloadFailed", ret);
303
- CapacitorUpdater.this.sendStats(
304
- "download_fail",
305
- CapacitorUpdater.this.getCurrentBundle().getVersionName()
306
- );
307
313
  return;
308
314
  }
309
315
  CapacitorUpdater.this.finishDownload(
@@ -321,7 +327,7 @@ public class CapacitorUpdater {
321
327
  }
322
328
  };
323
329
 
324
- public void finishDownload(
330
+ public Boolean finishDownload(
325
331
  String id,
326
332
  String dest,
327
333
  String version,
@@ -335,7 +341,7 @@ public class CapacitorUpdater {
335
341
  final String checksum;
336
342
  checksum = this.getChecksum(downloaded);
337
343
  this.notifyDownload(id, 71);
338
- final File unzipped = this.unzip(id, downloaded, this.randomString(10));
344
+ final File unzipped = this.unzip(id, downloaded, this.randomString());
339
345
  downloaded.delete();
340
346
  this.notifyDownload(id, 91);
341
347
  final String idName = bundleDirectory + "/" + id;
@@ -357,9 +363,9 @@ public class CapacitorUpdater {
357
363
  ) {
358
364
  Log.e(
359
365
  CapacitorUpdater.TAG,
360
- "Error checksum " + next.getChecksum() + " " + checksum
366
+ "Error checksum " + checksumRes + " " + checksum
361
367
  );
362
- this.sendStats("checksum_fail", getCurrentBundle().getVersionName());
368
+ this.sendStats("checksum_fail");
363
369
  final Boolean res = this.delete(id);
364
370
  if (res) {
365
371
  Log.i(
@@ -373,7 +379,12 @@ public class CapacitorUpdater {
373
379
  ret.put("bundle", next.toJSON());
374
380
  CapacitorUpdater.this.notifyListeners("updateAvailable", ret);
375
381
  if (setNext) {
376
- this.setNextBundle(next.getId());
382
+ if (this.directUpdate) {
383
+ CapacitorUpdater.this.directUpdateFinish(next);
384
+ this.directUpdate = false;
385
+ } else {
386
+ this.setNextBundle(next.getId());
387
+ }
377
388
  }
378
389
  } catch (IOException e) {
379
390
  e.printStackTrace();
@@ -383,11 +394,10 @@ public class CapacitorUpdater {
383
394
  CapacitorUpdater.this.getCurrentBundle().getVersionName()
384
395
  );
385
396
  CapacitorUpdater.this.notifyListeners("downloadFailed", ret);
386
- CapacitorUpdater.this.sendStats(
387
- "download_fail",
388
- CapacitorUpdater.this.getCurrentBundle().getVersionName()
389
- );
397
+ CapacitorUpdater.this.sendStats("download_fail");
398
+ return false;
390
399
  }
400
+ return true;
391
401
  }
392
402
 
393
403
  private void downloadFileBackground(
@@ -412,20 +422,17 @@ public class CapacitorUpdater {
412
422
  this.activity.startService(intent);
413
423
  }
414
424
 
415
- private File downloadFile(
425
+ private void downloadFile(
416
426
  final String id,
417
427
  final String url,
418
428
  final String dest
419
429
  ) throws IOException {
420
430
  final URL u = new URL(url);
421
431
  final URLConnection connection = u.openConnection();
422
- final InputStream is = u.openStream();
423
- final DataInputStream dis = new DataInputStream(is);
424
432
 
425
433
  final File target = new File(this.documentsDir, dest);
426
- target.getParentFile().mkdirs();
434
+ Objects.requireNonNull(target.getParentFile()).mkdirs();
427
435
  target.createNewFile();
428
- final FileOutputStream fos = new FileOutputStream(target);
429
436
 
430
437
  final long totalLength = connection.getContentLength();
431
438
  final int bufferSize = 1024;
@@ -435,16 +442,25 @@ public class CapacitorUpdater {
435
442
  int bytesRead = bufferSize;
436
443
  int percent = 0;
437
444
  this.notifyDownload(id, 10);
438
- while ((length = dis.read(buffer)) > 0) {
439
- fos.write(buffer, 0, length);
440
- final int newPercent = (int) ((bytesRead / (float) totalLength) * 100);
441
- if (totalLength > 1 && newPercent != percent) {
442
- percent = newPercent;
443
- this.notifyDownload(id, this.calcTotalPercent(percent, 10, 70));
445
+ try (
446
+ final InputStream is = connection.getInputStream();
447
+ final DataInputStream dis = new DataInputStream(is);
448
+ final FileOutputStream fos = new FileOutputStream(target)
449
+ ) {
450
+ while ((length = dis.read(buffer)) > 0) {
451
+ fos.write(buffer, 0, length);
452
+ final int newPercent = (int) ((bytesRead / (float) totalLength) * 100);
453
+ if (totalLength > 1 && newPercent != percent) {
454
+ percent = newPercent;
455
+ this.notifyDownload(id, this.calcTotalPercent(percent, 10, 70));
456
+ }
457
+ bytesRead += length;
444
458
  }
445
- bytesRead += length;
459
+ } catch (OutOfMemoryError e) {
460
+ Log.e(TAG, "OutOfMemoryError while downloading file", e);
461
+ this.sendStats("low_mem_fail");
462
+ throw new IOException("OutOfMemoryError while downloading file");
446
463
  }
447
- return target;
448
464
  }
449
465
 
450
466
  private void deleteDirectory(final File file) throws IOException {
@@ -468,12 +484,15 @@ public class CapacitorUpdater {
468
484
  }
469
485
 
470
486
  private String getChecksum(File file) throws IOException {
471
- byte[] bytes = new byte[(int) file.length()];
487
+ final int BUFFER_SIZE = 1024 * 1024 * 5; // 5 MB buffer size
488
+ CRC32 crc = new CRC32();
472
489
  try (FileInputStream fis = new FileInputStream(file)) {
473
- fis.read(bytes);
490
+ byte[] buffer = new byte[BUFFER_SIZE];
491
+ int length;
492
+ while ((length = fis.read(buffer)) != -1) {
493
+ crc.update(buffer, 0, length);
494
+ }
474
495
  }
475
- CRC32 crc = new CRC32();
476
- crc.update(bytes);
477
496
  String enc = String.format("%08X", crc.getValue());
478
497
  return enc.toLowerCase();
479
498
  }
@@ -488,8 +507,10 @@ public class CapacitorUpdater {
488
507
  this.privateKey == null ||
489
508
  this.privateKey.isEmpty() ||
490
509
  ivSessionKey == null ||
491
- ivSessionKey.isEmpty()
510
+ ivSessionKey.isEmpty() ||
511
+ ivSessionKey.split(":").length != 2
492
512
  ) {
513
+ Log.i(TAG, "Cannot found privateKey or sessionKey");
493
514
  return;
494
515
  }
495
516
  try {
@@ -504,17 +525,24 @@ public class CapacitorUpdater {
504
525
  byte[] decryptedSessionKey = CryptoCipher.decryptRSA(sessionKey, pKey);
505
526
  SecretKey sKey = CryptoCipher.byteToSessionKey(decryptedSessionKey);
506
527
  byte[] content = new byte[(int) file.length()];
507
- BufferedInputStream bis = new BufferedInputStream(
508
- new FileInputStream(file)
509
- );
510
- DataInputStream dis = new DataInputStream(bis);
511
- dis.readFully(content);
512
- dis.close();
513
- byte[] decrypted = CryptoCipher.decryptAES(content, sKey, iv);
514
- // write the decrypted string to the file
515
- FileOutputStream fos = new FileOutputStream(file.getAbsolutePath());
516
- fos.write(decrypted);
517
- fos.close();
528
+
529
+ try (
530
+ final FileInputStream fis = new FileInputStream(file);
531
+ final BufferedInputStream bis = new BufferedInputStream(fis);
532
+ final DataInputStream dis = new DataInputStream(bis)
533
+ ) {
534
+ dis.readFully(content);
535
+ dis.close();
536
+ byte[] decrypted = CryptoCipher.decryptAES(content, sKey, iv);
537
+ // write the decrypted string to the file
538
+ try (
539
+ final FileOutputStream fos = new FileOutputStream(
540
+ file.getAbsolutePath()
541
+ )
542
+ ) {
543
+ fos.write(decrypted);
544
+ }
545
+ }
518
546
  } catch (GeneralSecurityException e) {
519
547
  Log.i(TAG, "decryptFile fail");
520
548
  this.sendStats("decrypt_fail", version);
@@ -529,7 +557,7 @@ public class CapacitorUpdater {
529
557
  final String sessionKey,
530
558
  final String checksum
531
559
  ) {
532
- final String id = this.randomString(10);
560
+ final String id = this.randomString();
533
561
  this.saveBundleInfo(
534
562
  id,
535
563
  new BundleInfo(
@@ -548,7 +576,7 @@ public class CapacitorUpdater {
548
576
  version,
549
577
  sessionKey,
550
578
  checksum,
551
- this.randomString(10)
579
+ this.randomString()
552
580
  );
553
581
  }
554
582
 
@@ -558,7 +586,7 @@ public class CapacitorUpdater {
558
586
  final String sessionKey,
559
587
  final String checksum
560
588
  ) throws IOException {
561
- final String id = this.randomString(10);
589
+ final String id = this.randomString();
562
590
  this.saveBundleInfo(
563
591
  id,
564
592
  new BundleInfo(
@@ -570,15 +598,18 @@ public class CapacitorUpdater {
570
598
  )
571
599
  );
572
600
  this.notifyDownload(id, 0);
573
- final String idName = bundleDirectory + "/" + id;
574
601
  this.notifyDownload(id, 5);
575
- final String dest = this.randomString(10);
576
- final File downloaded = this.downloadFile(id, url, dest);
577
- this.finishDownload(id, dest, version, sessionKey, checksum, false);
602
+ final String dest = this.randomString();
603
+ this.downloadFile(id, url, dest);
604
+ final Boolean finished =
605
+ this.finishDownload(id, dest, version, sessionKey, checksum, false);
606
+ final BundleStatus status = finished
607
+ ? BundleStatus.PENDING
608
+ : BundleStatus.ERROR;
578
609
  BundleInfo info = new BundleInfo(
579
610
  id,
580
611
  version,
581
- BundleStatus.PENDING,
612
+ status,
582
613
  new Date(System.currentTimeMillis()),
583
614
  checksum
584
615
  );
@@ -591,7 +622,7 @@ public class CapacitorUpdater {
591
622
  final File destHot = new File(this.documentsDir, bundleDirectory);
592
623
  Log.d(TAG, "list File : " + destHot.getPath());
593
624
  if (destHot.exists()) {
594
- for (final File i : destHot.listFiles()) {
625
+ for (final File i : Objects.requireNonNull(destHot.listFiles())) {
595
626
  final String id = i.getName();
596
627
  res.add(this.getBundleInfo(id));
597
628
  }
@@ -634,10 +665,12 @@ public class CapacitorUpdater {
634
665
  private boolean bundleExists(final String id) {
635
666
  final File bundle = this.getBundleDirectory(id);
636
667
  final BundleInfo bundleInfo = this.getBundleInfo(id);
637
- if (bundle == null || !bundle.exists() || bundleInfo.isDeleted()) {
638
- return false;
639
- }
640
- return new File(bundle.getPath(), "/index.html").exists();
668
+ return (
669
+ bundle.isDirectory() &&
670
+ bundle.exists() &&
671
+ new File(bundle.getPath(), "/index.html").exists() &&
672
+ !bundleInfo.isDeleted()
673
+ );
641
674
  }
642
675
 
643
676
  public Boolean set(final BundleInfo bundle) {
@@ -653,9 +686,10 @@ public class CapacitorUpdater {
653
686
  final File bundle = this.getBundleDirectory(id);
654
687
  Log.i(TAG, "Setting next active bundle: " + id);
655
688
  if (this.bundleExists(id)) {
689
+ var currentBundleName = this.getCurrentBundle().getVersionName();
656
690
  this.setCurrentBundle(bundle);
657
691
  this.setBundleStatus(id, BundleStatus.PENDING);
658
- this.sendStats("set", newBundle.getVersionName());
692
+ this.sendStats("set", newBundle.getVersionName(), currentBundleName);
659
693
  return true;
660
694
  }
661
695
  this.setBundleStatus(id, BundleStatus.ERROR);
@@ -663,6 +697,16 @@ public class CapacitorUpdater {
663
697
  return false;
664
698
  }
665
699
 
700
+ public void autoReset() {
701
+ final BundleInfo currentBundle = this.getCurrentBundle();
702
+ if (
703
+ !currentBundle.isBuiltin() && !this.bundleExists(currentBundle.getId())
704
+ ) {
705
+ Log.i(TAG, "Folder at bundle path does not exist. Triggering reset.");
706
+ this.reset();
707
+ }
708
+ }
709
+
666
710
  public void reset() {
667
711
  this.reset(false);
668
712
  }
@@ -701,11 +745,16 @@ public class CapacitorUpdater {
701
745
 
702
746
  public void reset(final boolean internal) {
703
747
  Log.d(CapacitorUpdater.TAG, "reset: " + internal);
748
+ var currentBundleName = this.getCurrentBundle().getVersionName();
704
749
  this.setCurrentBundle(new File("public"));
705
750
  this.setFallbackBundle(null);
706
751
  this.setNextBundle(null);
707
752
  if (!internal) {
708
- this.sendStats("reset", this.getCurrentBundle().getVersionName());
753
+ this.sendStats(
754
+ "reset",
755
+ this.getCurrentBundle().getVersionName(),
756
+ currentBundleName
757
+ );
709
758
  }
710
759
  }
711
760
 
@@ -722,9 +771,21 @@ public class CapacitorUpdater {
722
771
  json.put("plugin_version", this.PLUGIN_VERSION);
723
772
  json.put("is_emulator", this.isEmulator());
724
773
  json.put("is_prod", this.isProd());
774
+ json.put("defaultChannel", this.defaultChannel);
725
775
  return json;
726
776
  }
727
777
 
778
+ private JsonObjectRequest setRetryPolicy(JsonObjectRequest request) {
779
+ request.setRetryPolicy(
780
+ new DefaultRetryPolicy(
781
+ this.timeout,
782
+ DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
783
+ DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
784
+ )
785
+ );
786
+ return request;
787
+ }
788
+
728
789
  private JSObject createError(String message, VolleyError error) {
729
790
  NetworkResponse response = error.networkResponse;
730
791
  final JSObject retError = new JSObject();
@@ -737,24 +798,24 @@ public class CapacitorUpdater {
737
798
  );
738
799
  retError.put("message", message + ": " + json);
739
800
  } catch (UnsupportedEncodingException e) {
740
- retError.put("message", message + ": " + e.toString());
801
+ retError.put("message", message + ": " + e);
741
802
  }
742
803
  } else {
743
- retError.put("message", message + ": " + error.toString());
804
+ retError.put("message", message + ": " + error);
744
805
  }
745
806
  Log.e(TAG, message + ": " + retError);
746
807
  return retError;
747
808
  }
748
809
 
749
810
  public void getLatest(final String updateUrl, final Callback callback) {
750
- JSONObject json = null;
811
+ JSONObject json;
751
812
  try {
752
813
  json = this.createInfoObject();
753
814
  } catch (JSONException e) {
754
815
  Log.e(TAG, "Error getLatest JSONException", e);
755
816
  e.printStackTrace();
756
817
  final JSObject retError = new JSObject();
757
- retError.put("message", "Cannot get info: " + e.toString());
818
+ retError.put("message", "Cannot get info: " + e);
758
819
  retError.put("error", "json_error");
759
820
  callback.callback(retError);
760
821
  return;
@@ -766,56 +827,95 @@ public class CapacitorUpdater {
766
827
  Request.Method.POST,
767
828
  updateUrl,
768
829
  json,
769
- new Response.Listener<JSONObject>() {
770
- @Override
771
- public void onResponse(JSONObject res) {
772
- final JSObject ret = new JSObject();
773
- Iterator<String> keys = res.keys();
774
- while (keys.hasNext()) {
775
- String key = keys.next();
776
- if (res.has(key)) {
777
- try {
778
- if ("session_key".equals(key)) {
779
- ret.put("sessionKey", res.get(key));
780
- } else {
781
- ret.put(key, res.get(key));
782
- }
783
- } catch (JSONException e) {
784
- e.printStackTrace();
785
- final JSObject retError = new JSObject();
786
- retError.put("message", "Cannot set info: " + e.toString());
787
- retError.put("error", "response_error");
788
- callback.callback(retError);
830
+ res -> {
831
+ final JSObject ret = new JSObject();
832
+ Iterator<String> keys = res.keys();
833
+ while (keys.hasNext()) {
834
+ String key = keys.next();
835
+ if (res.has(key)) {
836
+ try {
837
+ if ("session_key".equals(key)) {
838
+ ret.put("sessionKey", res.get(key));
839
+ } else {
840
+ ret.put(key, res.get(key));
789
841
  }
842
+ } catch (JSONException e) {
843
+ e.printStackTrace();
844
+ final JSObject retError = new JSObject();
845
+ retError.put("message", "Cannot set info: " + e);
846
+ retError.put("error", "response_error");
847
+ callback.callback(retError);
790
848
  }
791
849
  }
792
- callback.callback(ret);
793
850
  }
851
+ callback.callback(ret);
794
852
  },
795
- new Response.ErrorListener() {
796
- @Override
797
- public void onErrorResponse(VolleyError error) {
798
- callback.callback(
799
- CapacitorUpdater.this.createError("Error get latest", error)
800
- );
801
- }
802
- }
853
+ error ->
854
+ callback.callback(
855
+ CapacitorUpdater.this.createError("Error get latest", error)
856
+ )
803
857
  );
804
- request.setRetryPolicy(
805
- new DefaultRetryPolicy(
806
- this.timeout,
807
- DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
808
- DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
809
- )
858
+ this.requestQueue.add(setRetryPolicy(request));
859
+ }
860
+
861
+ public void unsetChannel(final Callback callback) {
862
+ String channelUrl = this.channelUrl;
863
+ if (channelUrl == null || channelUrl.isEmpty()) {
864
+ Log.e(TAG, "Channel URL is not set");
865
+ final JSObject retError = new JSObject();
866
+ retError.put("message", "channelUrl missing");
867
+ retError.put("error", "missing_config");
868
+ callback.callback(retError);
869
+ return;
870
+ }
871
+ JSONObject json;
872
+ try {
873
+ json = this.createInfoObject();
874
+ } catch (JSONException e) {
875
+ Log.e(TAG, "Error unsetChannel JSONException", e);
876
+ e.printStackTrace();
877
+ final JSObject retError = new JSObject();
878
+ retError.put("message", "Cannot get info: " + e);
879
+ retError.put("error", "json_error");
880
+ callback.callback(retError);
881
+ return;
882
+ }
883
+ // Building a request
884
+ JsonObjectRequest request = new JsonObjectRequest(
885
+ Request.Method.DELETE,
886
+ channelUrl,
887
+ json,
888
+ res -> {
889
+ final JSObject ret = new JSObject();
890
+ Iterator<String> keys = res.keys();
891
+ while (keys.hasNext()) {
892
+ String key = keys.next();
893
+ if (res.has(key)) {
894
+ try {
895
+ ret.put(key, res.get(key));
896
+ } catch (JSONException e) {
897
+ e.printStackTrace();
898
+ final JSObject retError = new JSObject();
899
+ retError.put("message", "Cannot unset channel: " + e);
900
+ retError.put("error", "response_error");
901
+ callback.callback(ret);
902
+ }
903
+ }
904
+ }
905
+ Log.i(TAG, "Channel unset");
906
+ callback.callback(ret);
907
+ },
908
+ error ->
909
+ callback.callback(
910
+ CapacitorUpdater.this.createError("Error unset channel", error)
911
+ )
810
912
  );
811
- this.requestQueue.add(request);
913
+ this.requestQueue.add(setRetryPolicy(request));
812
914
  }
813
915
 
814
916
  public void setChannel(final String channel, final Callback callback) {
815
917
  String channelUrl = this.channelUrl;
816
- if (
817
- channelUrl == null || "".equals(channelUrl) || channelUrl.length() == 0
818
- ) {
918
+ if (channelUrl == null || channelUrl.isEmpty()) {
819
919
  Log.e(TAG, "Channel URL is not set");
820
920
  final JSObject retError = new JSObject();
821
921
  retError.put("message", "channelUrl missing");
@@ -823,7 +923,7 @@ public class CapacitorUpdater {
823
923
  callback.callback(retError);
824
924
  return;
825
925
  }
826
- JSONObject json = null;
926
+ JSONObject json;
827
927
  try {
828
928
  json = this.createInfoObject();
829
929
  json.put("channel", channel);
@@ -831,7 +931,7 @@ public class CapacitorUpdater {
831
931
  Log.e(TAG, "Error setChannel JSONException", e);
832
932
  e.printStackTrace();
833
933
  final JSObject retError = new JSObject();
834
- retError.put("message", "Cannot get info: " + e.toString());
934
+ retError.put("message", "Cannot get info: " + e);
835
935
  retError.put("error", "json_error");
836
936
  callback.callback(retError);
837
937
  return;
@@ -841,53 +941,37 @@ public class CapacitorUpdater {
841
941
  Request.Method.POST,
842
942
  channelUrl,
843
943
  json,
844
- new Response.Listener<JSONObject>() {
845
- @Override
846
- public void onResponse(JSONObject res) {
847
- final JSObject ret = new JSObject();
848
- Iterator<String> keys = res.keys();
849
- while (keys.hasNext()) {
850
- String key = keys.next();
851
- if (res.has(key)) {
852
- try {
853
- ret.put(key, res.get(key));
854
- } catch (JSONException e) {
855
- e.printStackTrace();
856
- final JSObject retError = new JSObject();
857
- retError.put("message", "Cannot set channel: " + e.toString());
858
- retError.put("error", "response_error");
859
- callback.callback(ret);
860
- }
944
+ res -> {
945
+ final JSObject ret = new JSObject();
946
+ Iterator<String> keys = res.keys();
947
+ while (keys.hasNext()) {
948
+ String key = keys.next();
949
+ if (res.has(key)) {
950
+ try {
951
+ ret.put(key, res.get(key));
952
+ } catch (JSONException e) {
953
+ e.printStackTrace();
954
+ final JSObject retError = new JSObject();
955
+ retError.put("message", "Cannot set channel: " + e);
956
+ retError.put("error", "response_error");
957
+ callback.callback(ret);
861
958
  }
862
959
  }
863
- Log.i(TAG, "Channel set to \"" + channel);
864
- callback.callback(ret);
865
960
  }
961
+ Log.i(TAG, "Channel set to \"" + channel);
962
+ callback.callback(ret);
866
963
  },
867
- new Response.ErrorListener() {
868
- @Override
869
- public void onErrorResponse(VolleyError error) {
870
- callback.callback(
871
- CapacitorUpdater.this.createError("Error set channel", error)
872
- );
873
- }
874
- }
875
- );
876
- request.setRetryPolicy(
877
- new DefaultRetryPolicy(
878
- this.timeout,
879
- DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
880
- DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
881
- )
964
+ error ->
965
+ callback.callback(
966
+ CapacitorUpdater.this.createError("Error set channel", error)
967
+ )
882
968
  );
883
- this.requestQueue.add(request);
969
+ this.requestQueue.add(setRetryPolicy(request));
884
970
  }
885
971
 
886
972
  public void getChannel(final Callback callback) {
887
973
  String channelUrl = this.channelUrl;
888
- if (
889
- channelUrl == null || "".equals(channelUrl) || channelUrl.length() == 0
890
- ) {
974
+ if (channelUrl == null || channelUrl.isEmpty()) {
891
975
  Log.e(TAG, "Channel URL is not set");
892
976
  final JSObject retError = new JSObject();
893
977
  retError.put("message", "Channel URL is not set");
@@ -895,14 +979,14 @@ public class CapacitorUpdater {
895
979
  callback.callback(retError);
896
980
  return;
897
981
  }
898
- JSONObject json = null;
982
+ JSONObject json;
899
983
  try {
900
984
  json = this.createInfoObject();
901
985
  } catch (JSONException e) {
902
986
  Log.e(TAG, "Error getChannel JSONException", e);
903
987
  e.printStackTrace();
904
988
  final JSObject retError = new JSObject();
905
- retError.put("message", "Cannot get info: " + e.toString());
989
+ retError.put("message", "Cannot get info: " + e);
906
990
  retError.put("error", "json_error");
907
991
  callback.callback(retError);
908
992
  return;
@@ -912,52 +996,52 @@ public class CapacitorUpdater {
912
996
  Request.Method.PUT,
913
997
  channelUrl,
914
998
  json,
915
- new Response.Listener<JSONObject>() {
916
- @Override
917
- public void onResponse(JSONObject res) {
918
- final JSObject ret = new JSObject();
919
- Iterator<String> keys = res.keys();
920
- while (keys.hasNext()) {
921
- String key = keys.next();
922
- if (res.has(key)) {
923
- try {
924
- ret.put(key, res.get(key));
925
- } catch (JSONException e) {
926
- e.printStackTrace();
927
- }
999
+ res -> {
1000
+ final JSObject ret = new JSObject();
1001
+ Iterator<String> keys = res.keys();
1002
+ while (keys.hasNext()) {
1003
+ String key = keys.next();
1004
+ if (res.has(key)) {
1005
+ try {
1006
+ ret.put(key, res.get(key));
1007
+ } catch (JSONException e) {
1008
+ e.printStackTrace();
928
1009
  }
929
1010
  }
930
- Log.i(TAG, "Channel get to \"" + ret);
931
- callback.callback(ret);
932
1011
  }
1012
+ Log.i(TAG, "Channel get to \"" + ret);
1013
+ callback.callback(ret);
933
1014
  },
934
- new Response.ErrorListener() {
935
- @Override
936
- public void onErrorResponse(VolleyError error) {
937
- callback.callback(
938
- CapacitorUpdater.this.createError("Error get channel", error)
939
- );
940
- }
941
- }
942
- );
943
- request.setRetryPolicy(
944
- new DefaultRetryPolicy(
945
- this.timeout,
946
- DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
947
- DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
948
- )
1015
+ error ->
1016
+ callback.callback(
1017
+ CapacitorUpdater.this.createError("Error get channel", error)
1018
+ )
949
1019
  );
950
- this.requestQueue.add(request);
1020
+ this.requestQueue.add(setRetryPolicy(request));
1021
+ }
1022
+
1023
+ public void sendStats(final String action) {
1024
+ this.sendStats(action, this.getCurrentBundle().getVersionName());
951
1025
  }
952
1026
 
953
1027
  public void sendStats(final String action, final String versionName) {
1028
+ this.sendStats(action, versionName, "");
1029
+ }
1030
+
1031
+ public void sendStats(
1032
+ final String action,
1033
+ final String versionName,
1034
+ final String oldVersionName
1035
+ ) {
954
1036
  String statsUrl = this.statsUrl;
955
- if (statsUrl == null || "".equals(statsUrl) || statsUrl.length() == 0) {
1037
+ if (statsUrl == null || statsUrl.isEmpty()) {
956
1038
  return;
957
1039
  }
958
- JSONObject json = null;
1040
+ JSONObject json;
959
1041
  try {
960
1042
  json = this.createInfoObject();
1043
+ json.put("version_name", versionName);
1044
+ json.put("old_version_name", oldVersionName);
961
1045
  json.put("action", action);
962
1046
  } catch (JSONException e) {
963
1047
  Log.e(TAG, "Error sendStats JSONException", e);
@@ -969,30 +1053,11 @@ public class CapacitorUpdater {
969
1053
  Request.Method.POST,
970
1054
  statsUrl,
971
1055
  json,
972
- new Response.Listener<JSONObject>() {
973
- @Override
974
- public void onResponse(JSONObject response) {
975
- Log.i(
976
- TAG,
977
- "Stats send for \"" + action + "\", version " + versionName
978
- );
979
- }
980
- },
981
- new Response.ErrorListener() {
982
- @Override
983
- public void onErrorResponse(VolleyError error) {
984
- CapacitorUpdater.this.createError("Error send stats", error);
985
- }
986
- }
1056
+ response ->
1057
+ Log.i(TAG, "Stats send for \"" + action + "\", version " + versionName),
1058
+ error -> CapacitorUpdater.this.createError("Error send stats", error)
987
1059
  );
988
- request.setRetryPolicy(
989
- new DefaultRetryPolicy(
990
- this.timeout,
991
- DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
992
- DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
993
- )
994
- );
995
- this.requestQueue.add(request);
1060
+ this.requestQueue.add(setRetryPolicy(request));
996
1061
  }
997
1062
 
998
1063
  public BundleInfo getBundleInfo(final String id) {
@@ -1000,7 +1065,6 @@ public class CapacitorUpdater {
1000
1065
  if (id != null) {
1001
1066
  trueId = id;
1002
1067
  }
1003
- Log.d(TAG, "Getting info for bundle [" + trueId + "]");
1004
1068
  BundleInfo result;
1005
1069
  if (BundleInfo.ID_BUILTIN.equals(trueId)) {
1006
1070
  result = new BundleInfo(trueId, null, BundleStatus.SUCCESS, "", "");
@@ -1033,7 +1097,7 @@ public class CapacitorUpdater {
1033
1097
  this.saveBundleInfo(id, null);
1034
1098
  }
1035
1099
 
1036
- private void saveBundleInfo(final String id, final BundleInfo info) {
1100
+ public void saveBundleInfo(final String id, final BundleInfo info) {
1037
1101
  if (
1038
1102
  id == null || (info != null && (info.isBuiltin() || info.isUnknown()))
1039
1103
  ) {
@@ -1052,14 +1116,6 @@ public class CapacitorUpdater {
1052
1116
  this.editor.commit();
1053
1117
  }
1054
1118
 
1055
- public void setVersionName(final String id, final String name) {
1056
- if (id != null) {
1057
- Log.d(TAG, "Setting name for bundle [" + id + "] to " + name);
1058
- BundleInfo info = this.getBundleInfo(id);
1059
- this.saveBundleInfo(id, info.setVersionName(name));
1060
- }
1061
- }
1062
-
1063
1119
  private void setBundleStatus(final String id, final BundleStatus status) {
1064
1120
  if (id != null && status != null) {
1065
1121
  BundleInfo info = this.getBundleInfo(id);
@@ -1083,7 +1139,7 @@ public class CapacitorUpdater {
1083
1139
 
1084
1140
  public String getCurrentBundlePath() {
1085
1141
  String path = this.prefs.getString(WebView.CAP_SERVER_PATH, "public");
1086
- if ("".equals(path.trim())) {
1142
+ if (path.trim().isEmpty()) {
1087
1143
  return "public";
1088
1144
  }
1089
1145
  return path;