@capgo/capacitor-updater 8.46.3 → 8.47.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 +178 -19
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +466 -30
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +239 -22
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +95 -3
- package/dist/docs.json +473 -0
- package/dist/esm/definitions.d.ts +241 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +4 -1
- package/dist/esm/web.js +30 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +30 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +30 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +354 -15
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +194 -8
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +2 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +47 -3
- package/package.json +1 -1
|
@@ -60,6 +60,7 @@ public class CapgoUpdater {
|
|
|
60
60
|
|
|
61
61
|
private static final String FALLBACK_VERSION = "pastVersion";
|
|
62
62
|
private static final String NEXT_VERSION = "nextVersion";
|
|
63
|
+
private static final String PREVIEW_FALLBACK_VERSION = "previewFallbackVersion";
|
|
63
64
|
private static final String bundleDirectory = "versions";
|
|
64
65
|
private static final String TEMP_UNZIP_PREFIX = "capgo_unzip_";
|
|
65
66
|
|
|
@@ -81,6 +82,7 @@ public class CapgoUpdater {
|
|
|
81
82
|
public String channelUrl = "";
|
|
82
83
|
public String defaultChannel = "";
|
|
83
84
|
public String appId = "";
|
|
85
|
+
public boolean previewSession = false;
|
|
84
86
|
public String publicKey = "";
|
|
85
87
|
public String deviceID = "";
|
|
86
88
|
public int timeout = 20000;
|
|
@@ -124,24 +126,32 @@ public class CapgoUpdater {
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
private boolean isEmulator() {
|
|
129
|
+
final String brand = String.valueOf(Build.BRAND);
|
|
130
|
+
final String device = String.valueOf(Build.DEVICE);
|
|
131
|
+
final String fingerprint = String.valueOf(Build.FINGERPRINT);
|
|
132
|
+
final String hardware = String.valueOf(Build.HARDWARE);
|
|
133
|
+
final String model = String.valueOf(Build.MODEL);
|
|
134
|
+
final String manufacturer = String.valueOf(Build.MANUFACTURER);
|
|
135
|
+
final String product = String.valueOf(Build.PRODUCT);
|
|
136
|
+
|
|
127
137
|
return (
|
|
128
|
-
(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
(brand.startsWith("generic") && device.startsWith("generic")) ||
|
|
139
|
+
fingerprint.startsWith("generic") ||
|
|
140
|
+
fingerprint.startsWith("unknown") ||
|
|
141
|
+
hardware.contains("goldfish") ||
|
|
142
|
+
hardware.contains("ranchu") ||
|
|
143
|
+
model.contains("google_sdk") ||
|
|
144
|
+
model.contains("Emulator") ||
|
|
145
|
+
model.contains("Android SDK built for x86") ||
|
|
146
|
+
manufacturer.contains("Genymotion") ||
|
|
147
|
+
product.contains("sdk_google") ||
|
|
148
|
+
product.contains("google_sdk") ||
|
|
149
|
+
product.contains("sdk") ||
|
|
150
|
+
product.contains("sdk_x86") ||
|
|
151
|
+
product.contains("sdk_gphone64_arm64") ||
|
|
152
|
+
product.contains("vbox86p") ||
|
|
153
|
+
product.contains("emulator") ||
|
|
154
|
+
product.contains("simulator")
|
|
145
155
|
);
|
|
146
156
|
}
|
|
147
157
|
|
|
@@ -345,6 +355,137 @@ public class CapgoUpdater {
|
|
|
345
355
|
}
|
|
346
356
|
}
|
|
347
357
|
|
|
358
|
+
private boolean verifyChecksum(final File file, final String expectedHash) {
|
|
359
|
+
if (expectedHash == null || expectedHash.isEmpty() || file == null || !file.exists()) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
final String actualHash = CryptoCipher.calcChecksum(file);
|
|
363
|
+
return expectedHash.equalsIgnoreCase(actualHash);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private String resolveManifestFileHash(final JSONObject entry, final String sessionKey) {
|
|
367
|
+
String fileHash = entry.optString("file_hash", "");
|
|
368
|
+
if (fileHash.isEmpty()) {
|
|
369
|
+
return "";
|
|
370
|
+
}
|
|
371
|
+
if (this.publicKey != null && !this.publicKey.isEmpty() && sessionKey != null && !sessionKey.isEmpty()) {
|
|
372
|
+
try {
|
|
373
|
+
fileHash = CryptoCipher.decryptChecksum(fileHash, this.publicKey);
|
|
374
|
+
} catch (Exception e) {
|
|
375
|
+
logger.error("Checksum decryption failed while checking missing manifest files");
|
|
376
|
+
logger.debug("File: " + entry.optString("file_name", "unknown") + ", Error: " + e.getMessage());
|
|
377
|
+
return "";
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return fileHash;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private boolean isManifestEntryAvailableLocally(final JSONObject entry, final String sessionKey) {
|
|
384
|
+
final String fileName = entry.optString("file_name", "");
|
|
385
|
+
final String fileHash = resolveManifestFileHash(entry, sessionKey);
|
|
386
|
+
if (fileName.isEmpty() || fileHash.isEmpty() || this.activity == null) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
final File builtinFile = new File(this.activity.getFilesDir(), "public/" + fileName);
|
|
391
|
+
if (verifyChecksum(builtinFile, fileHash)) {
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
final boolean isBrotli = fileName.endsWith(".br");
|
|
396
|
+
final String fileNameWithoutPath = new File(fileName).getName();
|
|
397
|
+
final String cacheBaseName = isBrotli ? fileNameWithoutPath.substring(0, fileNameWithoutPath.length() - 3) : fileNameWithoutPath;
|
|
398
|
+
final File cacheFolder = new File(this.activity.getCacheDir(), "capgo_downloads");
|
|
399
|
+
final File cacheFile = new File(cacheFolder, fileHash + "_" + cacheBaseName);
|
|
400
|
+
if (verifyChecksum(cacheFile, fileHash)) {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (isBrotli) {
|
|
405
|
+
final File legacyCacheFile = new File(cacheFolder, fileHash + "_" + fileNameWithoutPath);
|
|
406
|
+
return verifyChecksum(legacyCacheFile, fileHash);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
public JSONArray getMissingBundleFiles(final JSONArray manifest, final String sessionKey) throws JSONException {
|
|
413
|
+
final JSONArray missing = new JSONArray();
|
|
414
|
+
for (int i = 0; i < manifest.length(); i++) {
|
|
415
|
+
final JSONObject entry = manifest.getJSONObject(i);
|
|
416
|
+
if (!isManifestEntryAvailableLocally(entry, sessionKey)) {
|
|
417
|
+
missing.put(entry);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return missing;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
public JSONObject missingBundleFilesResult(final JSONArray manifest, final String sessionKey) throws JSONException {
|
|
424
|
+
final JSONArray missing = getMissingBundleFiles(manifest, sessionKey);
|
|
425
|
+
final JSONObject ret = new JSONObject();
|
|
426
|
+
ret.put("missing", missing);
|
|
427
|
+
ret.put("total", manifest.length());
|
|
428
|
+
ret.put("missingCount", missing.length());
|
|
429
|
+
ret.put("reusableCount", manifest.length() - missing.length());
|
|
430
|
+
return ret;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private String manifestSizeUrl(final String updateUrl) {
|
|
434
|
+
HttpUrl parsed = HttpUrl.parse(updateUrl);
|
|
435
|
+
if (parsed == null) {
|
|
436
|
+
return updateUrl;
|
|
437
|
+
}
|
|
438
|
+
return parsed.newBuilder().addPathSegment("manifest_size").query(null).build().toString();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private JSONObject unavailableBundleSizeResult(final JSONArray manifest, final String error) throws JSONException {
|
|
442
|
+
final JSONObject ret = new JSONObject();
|
|
443
|
+
final JSONArray files = new JSONArray();
|
|
444
|
+
for (int i = 0; i < manifest.length(); i++) {
|
|
445
|
+
final JSONObject entry = new JSONObject(manifest.getJSONObject(i).toString());
|
|
446
|
+
entry.put("error", error);
|
|
447
|
+
files.put(entry);
|
|
448
|
+
}
|
|
449
|
+
ret.put("totalSize", 0);
|
|
450
|
+
ret.put("knownFiles", 0);
|
|
451
|
+
ret.put("unknownFiles", manifest.length());
|
|
452
|
+
ret.put("files", files);
|
|
453
|
+
return ret;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
public JSONObject getBundleDownloadSize(final String updateUrl, final String version, final JSONArray manifest) throws JSONException {
|
|
457
|
+
if (manifest.length() == 0) {
|
|
458
|
+
final JSONObject ret = new JSONObject();
|
|
459
|
+
ret.put("totalSize", 0);
|
|
460
|
+
ret.put("knownFiles", 0);
|
|
461
|
+
ret.put("unknownFiles", 0);
|
|
462
|
+
ret.put("files", new JSONArray());
|
|
463
|
+
return ret;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
final JSONObject json = this.createInfoObject();
|
|
467
|
+
json.put("version", version != null ? version : "");
|
|
468
|
+
json.put("manifest", manifest);
|
|
469
|
+
|
|
470
|
+
Request request = new Request.Builder()
|
|
471
|
+
.url(manifestSizeUrl(updateUrl))
|
|
472
|
+
.post(RequestBody.create(json.toString(), MediaType.get("application/json; charset=utf-8")))
|
|
473
|
+
.build();
|
|
474
|
+
|
|
475
|
+
try (Response response = DownloadService.sharedClient.newCall(request).execute()) {
|
|
476
|
+
final ResponseBody responseBody = response.body();
|
|
477
|
+
final String responseData = responseBody != null ? responseBody.string() : "";
|
|
478
|
+
if (!response.isSuccessful() || responseData.isEmpty()) {
|
|
479
|
+
return unavailableBundleSizeResult(manifest, "response_error");
|
|
480
|
+
}
|
|
481
|
+
return new JSONObject(responseData);
|
|
482
|
+
} catch (IOException e) {
|
|
483
|
+
logger.error("Error getting bundle download size");
|
|
484
|
+
logger.debug("Error: " + e.getMessage());
|
|
485
|
+
return unavailableBundleSizeResult(manifest, "response_error");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
348
489
|
private void observeWorkProgress(Context context, String id, boolean setNext) {
|
|
349
490
|
if (!(context instanceof LifecycleOwner)) {
|
|
350
491
|
logger.error("Context is not a LifecycleOwner, cannot observe work progress");
|
|
@@ -941,6 +1082,17 @@ public class CapgoUpdater {
|
|
|
941
1082
|
logger.debug("Bundle ID: " + id);
|
|
942
1083
|
return false;
|
|
943
1084
|
}
|
|
1085
|
+
final BundleInfo previewFallback = this.getPreviewFallbackBundle();
|
|
1086
|
+
if (
|
|
1087
|
+
previewFallback != null &&
|
|
1088
|
+
!previewFallback.isDeleted() &&
|
|
1089
|
+
!previewFallback.isErrorStatus() &&
|
|
1090
|
+
previewFallback.getId().equals(id)
|
|
1091
|
+
) {
|
|
1092
|
+
logger.error("Cannot delete the preview fallback bundle");
|
|
1093
|
+
logger.debug("Bundle ID: " + id);
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
944
1096
|
final BundleInfo next = this.getNextBundle();
|
|
945
1097
|
if (next != null && !next.isDeleted() && !next.isErrorStatus() && next.getId().equals(id)) {
|
|
946
1098
|
logger.error("Cannot delete the next bundle");
|
|
@@ -1081,6 +1233,21 @@ public class CapgoUpdater {
|
|
|
1081
1233
|
return true;
|
|
1082
1234
|
}
|
|
1083
1235
|
|
|
1236
|
+
boolean stagePreviewFallbackReload(final BundleInfo bundle) {
|
|
1237
|
+
if (bundle == null || bundle.isErrorStatus()) {
|
|
1238
|
+
return false;
|
|
1239
|
+
}
|
|
1240
|
+
if (bundle.isBuiltin()) {
|
|
1241
|
+
this.setCurrentBundle(new File("public"));
|
|
1242
|
+
return true;
|
|
1243
|
+
}
|
|
1244
|
+
if (!this.bundleExists(bundle.getId())) {
|
|
1245
|
+
return false;
|
|
1246
|
+
}
|
|
1247
|
+
this.setCurrentBundle(this.getBundleDirectory(bundle.getId()));
|
|
1248
|
+
return true;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1084
1251
|
void finalizePendingReload(final BundleInfo bundle, final String previousBundleName) {
|
|
1085
1252
|
if (bundle == null || bundle.isBuiltin()) {
|
|
1086
1253
|
return;
|
|
@@ -1147,12 +1314,20 @@ public class CapgoUpdater {
|
|
|
1147
1314
|
public void setSuccess(final BundleInfo bundle, Boolean autoDeletePrevious) {
|
|
1148
1315
|
this.setBundleStatus(bundle.getId(), BundleStatus.SUCCESS);
|
|
1149
1316
|
final BundleInfo fallback = this.getFallbackBundle();
|
|
1317
|
+
final BundleInfo previewFallback = this.getPreviewFallbackBundle();
|
|
1318
|
+
final boolean fallbackIsPreviewFallback = previewFallback != null && previewFallback.getId().equals(fallback.getId());
|
|
1150
1319
|
logger.debug("Fallback bundle is: " + fallback);
|
|
1151
1320
|
logger.info("Version successfully loaded: " + bundle.getVersionName());
|
|
1152
1321
|
// Only attempt to delete when the fallback is a different bundle than the
|
|
1153
1322
|
// currently loaded one. Otherwise we spam logs with "Cannot delete <id>"
|
|
1154
1323
|
// because delete() protects the current bundle from removal.
|
|
1155
|
-
if (
|
|
1324
|
+
if (
|
|
1325
|
+
autoDeletePrevious &&
|
|
1326
|
+
!fallback.isBuiltin() &&
|
|
1327
|
+
fallback.getId() != null &&
|
|
1328
|
+
!fallback.getId().equals(bundle.getId()) &&
|
|
1329
|
+
!fallbackIsPreviewFallback
|
|
1330
|
+
) {
|
|
1156
1331
|
final Boolean res = this.delete(fallback.getId());
|
|
1157
1332
|
if (res) {
|
|
1158
1333
|
logger.info("Deleted previous bundle: " + fallback.getVersionName());
|
|
@@ -1175,10 +1350,14 @@ public class CapgoUpdater {
|
|
|
1175
1350
|
}
|
|
1176
1351
|
|
|
1177
1352
|
private JSONObject createInfoObject() throws JSONException {
|
|
1353
|
+
return this.createInfoObject(null);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
private JSONObject createInfoObject(final String appIdOverride) throws JSONException {
|
|
1178
1357
|
JSONObject json = new JSONObject();
|
|
1179
1358
|
json.put("platform", "android");
|
|
1180
1359
|
json.put("device_id", this.deviceID);
|
|
1181
|
-
json.put("app_id", this.appId);
|
|
1360
|
+
json.put("app_id", appIdOverride == null || appIdOverride.trim().isEmpty() ? this.appId : appIdOverride);
|
|
1182
1361
|
json.put("custom_id", this.customId);
|
|
1183
1362
|
json.put("version_build", this.versionBuild);
|
|
1184
1363
|
json.put("version_code", this.versionCode);
|
|
@@ -1204,7 +1383,7 @@ public class CapgoUpdater {
|
|
|
1204
1383
|
if (response.code() == 429) {
|
|
1205
1384
|
// Send a statistic about the rate limit BEFORE setting the flag
|
|
1206
1385
|
// Only send once to prevent infinite loop if the stat request itself gets rate limited
|
|
1207
|
-
if (!rateLimitExceeded && !rateLimitStatisticSent) {
|
|
1386
|
+
if (!this.previewSession && !rateLimitExceeded && !rateLimitStatisticSent) {
|
|
1208
1387
|
rateLimitStatisticSent = true;
|
|
1209
1388
|
sendRateLimitStatistic();
|
|
1210
1389
|
}
|
|
@@ -1362,9 +1541,13 @@ public class CapgoUpdater {
|
|
|
1362
1541
|
}
|
|
1363
1542
|
|
|
1364
1543
|
public void getLatest(final String updateUrl, final String channel, final Callback callback) {
|
|
1544
|
+
this.getLatest(updateUrl, channel, null, callback);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
public void getLatest(final String updateUrl, final String channel, final String appIdOverride, final Callback callback) {
|
|
1365
1548
|
JSONObject json;
|
|
1366
1549
|
try {
|
|
1367
|
-
json = this.createInfoObject();
|
|
1550
|
+
json = this.createInfoObject(appIdOverride);
|
|
1368
1551
|
if (channel != null && json != null) {
|
|
1369
1552
|
json.put("defaultChannel", channel);
|
|
1370
1553
|
}
|
|
@@ -1378,7 +1561,9 @@ public class CapgoUpdater {
|
|
|
1378
1561
|
return;
|
|
1379
1562
|
}
|
|
1380
1563
|
|
|
1381
|
-
logger
|
|
1564
|
+
if (logger != null) {
|
|
1565
|
+
logger.info("Auto-update parameters: " + json);
|
|
1566
|
+
}
|
|
1382
1567
|
|
|
1383
1568
|
makeJsonRequest(updateUrl, json, callback);
|
|
1384
1569
|
}
|
|
@@ -1749,6 +1934,13 @@ public class CapgoUpdater {
|
|
|
1749
1934
|
}
|
|
1750
1935
|
|
|
1751
1936
|
public void sendStats(final String action, final String versionName, final String oldVersionName, final Map<String, String> metadata) {
|
|
1937
|
+
if (this.previewSession) {
|
|
1938
|
+
if (logger != null) {
|
|
1939
|
+
logger.debug("Skipping sendStats during preview session.");
|
|
1940
|
+
}
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1752
1944
|
// Check if rate limit was exceeded
|
|
1753
1945
|
if (rateLimitExceeded) {
|
|
1754
1946
|
logger.debug("Skipping sendStats due to rate limit (429). Stats will resume after app restart.");
|
|
@@ -1971,6 +2163,31 @@ public class CapgoUpdater {
|
|
|
1971
2163
|
return this.getBundleInfo(id);
|
|
1972
2164
|
}
|
|
1973
2165
|
|
|
2166
|
+
public BundleInfo getPreviewFallbackBundle() {
|
|
2167
|
+
final String id = this.prefs.getString(PREVIEW_FALLBACK_VERSION, null);
|
|
2168
|
+
if (id == null) return null;
|
|
2169
|
+
final BundleInfo bundle = this.getBundleInfo(id);
|
|
2170
|
+
if (bundle.isErrorStatus() || (!bundle.isBuiltin() && !this.bundleExists(id))) {
|
|
2171
|
+
this.setPreviewFallbackBundle(null);
|
|
2172
|
+
return null;
|
|
2173
|
+
}
|
|
2174
|
+
return bundle;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
public boolean setPreviewFallbackBundle(final String fallback) {
|
|
2178
|
+
if (fallback == null) {
|
|
2179
|
+
this.editor.remove(PREVIEW_FALLBACK_VERSION);
|
|
2180
|
+
} else {
|
|
2181
|
+
final BundleInfo newBundle = this.getBundleInfo(fallback);
|
|
2182
|
+
if (newBundle.isErrorStatus() || (!newBundle.isBuiltin() && !this.bundleExists(fallback))) {
|
|
2183
|
+
return false;
|
|
2184
|
+
}
|
|
2185
|
+
this.editor.putString(PREVIEW_FALLBACK_VERSION, fallback);
|
|
2186
|
+
}
|
|
2187
|
+
this.editor.commit();
|
|
2188
|
+
return true;
|
|
2189
|
+
}
|
|
2190
|
+
|
|
1974
2191
|
public boolean setNextBundle(final String next) {
|
|
1975
2192
|
BundleInfo bundleToNotify = null;
|
|
1976
2193
|
if (next == null) {
|
|
@@ -66,8 +66,9 @@ public class ShakeMenu implements ShakeDetector.Listener {
|
|
|
66
66
|
|
|
67
67
|
isShowing = true;
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
if (plugin.hasActivePreviewSession()) {
|
|
70
|
+
showDefaultMenu();
|
|
71
|
+
} else if (plugin.shakeChannelSelectorEnabled) {
|
|
71
72
|
showChannelSelector();
|
|
72
73
|
} else {
|
|
73
74
|
showDefaultMenu();
|
|
@@ -75,6 +76,98 @@ public class ShakeMenu implements ShakeDetector.Listener {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
private void showDefaultMenu() {
|
|
79
|
+
activity.runOnUiThread(() -> {
|
|
80
|
+
try {
|
|
81
|
+
if (!plugin.hasActivePreviewSession()) {
|
|
82
|
+
showConfiguredDefaultMenu();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
String appName = activity.getPackageManager().getApplicationLabel(activity.getApplicationInfo()).toString();
|
|
86
|
+
String title = "Preview " + appName + " Menu";
|
|
87
|
+
String message = "Reload the current preview or leave the test app.";
|
|
88
|
+
String okButtonTitle = "Leave test app";
|
|
89
|
+
String reloadButtonTitle = "Reload app";
|
|
90
|
+
String cancelButtonTitle = "Close menu";
|
|
91
|
+
|
|
92
|
+
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
93
|
+
builder.setTitle(title);
|
|
94
|
+
builder.setMessage(message);
|
|
95
|
+
|
|
96
|
+
builder.setPositiveButton(okButtonTitle, null);
|
|
97
|
+
builder.setNeutralButton(reloadButtonTitle, null);
|
|
98
|
+
|
|
99
|
+
// Cancel button
|
|
100
|
+
builder.setNegativeButton(
|
|
101
|
+
cancelButtonTitle,
|
|
102
|
+
new DialogInterface.OnClickListener() {
|
|
103
|
+
public void onClick(DialogInterface dialog, int id) {
|
|
104
|
+
logger.info("Shake menu cancelled");
|
|
105
|
+
dialog.dismiss();
|
|
106
|
+
isShowing = false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
AlertDialog dialog = builder.create();
|
|
112
|
+
dialog.setOnDismissListener((dialogInterface) -> isShowing = false);
|
|
113
|
+
dialog.show();
|
|
114
|
+
dialog
|
|
115
|
+
.getButton(AlertDialog.BUTTON_POSITIVE)
|
|
116
|
+
.setOnClickListener((view) -> {
|
|
117
|
+
setPreviewMenuButtonsEnabled(dialog, false);
|
|
118
|
+
new Thread(() -> {
|
|
119
|
+
try {
|
|
120
|
+
if (!plugin.leavePreviewSessionFromShakeMenu()) {
|
|
121
|
+
activity.runOnUiThread(() -> showError("Could not leave the test app."));
|
|
122
|
+
}
|
|
123
|
+
} catch (Exception e) {
|
|
124
|
+
logger.error("Error leaving test app: " + e.getMessage());
|
|
125
|
+
activity.runOnUiThread(() -> showError("Error leaving test app: " + e.getMessage()));
|
|
126
|
+
} finally {
|
|
127
|
+
activity.runOnUiThread(() -> {
|
|
128
|
+
dialog.dismiss();
|
|
129
|
+
isShowing = false;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
.start();
|
|
134
|
+
});
|
|
135
|
+
dialog
|
|
136
|
+
.getButton(AlertDialog.BUTTON_NEUTRAL)
|
|
137
|
+
.setOnClickListener((view) -> {
|
|
138
|
+
setPreviewMenuButtonsEnabled(dialog, false);
|
|
139
|
+
new Thread(() -> {
|
|
140
|
+
try {
|
|
141
|
+
logger.info("Reloading webview");
|
|
142
|
+
if (!plugin.reloadPreviewSessionFromShakeMenu()) {
|
|
143
|
+
activity.runOnUiThread(() -> showError("Could not reload the test app."));
|
|
144
|
+
}
|
|
145
|
+
} catch (Exception e) {
|
|
146
|
+
logger.error("Error in Reload action: " + e.getMessage());
|
|
147
|
+
activity.runOnUiThread(() -> showError("Error reloading test app: " + e.getMessage()));
|
|
148
|
+
} finally {
|
|
149
|
+
activity.runOnUiThread(() -> {
|
|
150
|
+
dialog.dismiss();
|
|
151
|
+
isShowing = false;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
.start();
|
|
156
|
+
});
|
|
157
|
+
} catch (Exception e) {
|
|
158
|
+
logger.error("Error showing shake menu: " + e.getMessage());
|
|
159
|
+
isShowing = false;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private void setPreviewMenuButtonsEnabled(AlertDialog dialog, boolean enabled) {
|
|
165
|
+
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled);
|
|
166
|
+
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setEnabled(enabled);
|
|
167
|
+
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(enabled);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private void showConfiguredDefaultMenu() {
|
|
78
171
|
activity.runOnUiThread(() -> {
|
|
79
172
|
try {
|
|
80
173
|
String appName = activity.getPackageManager().getApplicationLabel(activity.getApplicationInfo()).toString();
|
|
@@ -120,7 +213,6 @@ public class ShakeMenu implements ShakeDetector.Listener {
|
|
|
120
213
|
bridge.setServerAssetPath(path);
|
|
121
214
|
}
|
|
122
215
|
|
|
123
|
-
// Try to delete the current bundle
|
|
124
216
|
try {
|
|
125
217
|
updater.delete(current.getId());
|
|
126
218
|
} catch (Exception err) {
|