@capgo/capacitor-updater 7.42.9 → 7.45.10
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/Package.swift +5 -2
- package/README.md +250 -77
- package/android/build.gradle +3 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +682 -213
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +222 -35
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +49 -13
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +38 -13
- package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +13 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +476 -2
- package/dist/docs.json +402 -10
- package/dist/esm/definitions.d.ts +183 -22
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +3 -2
- package/dist/esm/web.js +6 -4
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +6 -4
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +6 -4
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +653 -140
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +213 -50
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +37 -16
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +2 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +347 -2
- package/package.json +12 -8
|
@@ -22,6 +22,8 @@ import android.view.View;
|
|
|
22
22
|
import android.view.ViewGroup;
|
|
23
23
|
import android.widget.FrameLayout;
|
|
24
24
|
import android.widget.ProgressBar;
|
|
25
|
+
import androidx.core.content.pm.PackageInfoCompat;
|
|
26
|
+
import com.getcapacitor.Bridge;
|
|
25
27
|
import com.getcapacitor.CapConfig;
|
|
26
28
|
import com.getcapacitor.JSArray;
|
|
27
29
|
import com.getcapacitor.JSObject;
|
|
@@ -29,6 +31,7 @@ import com.getcapacitor.Plugin;
|
|
|
29
31
|
import com.getcapacitor.PluginCall;
|
|
30
32
|
import com.getcapacitor.PluginHandle;
|
|
31
33
|
import com.getcapacitor.PluginMethod;
|
|
34
|
+
import com.getcapacitor.PluginResult;
|
|
32
35
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
33
36
|
import com.getcapacitor.plugin.WebView;
|
|
34
37
|
import com.google.android.gms.tasks.Task;
|
|
@@ -47,7 +50,6 @@ import java.io.IOException;
|
|
|
47
50
|
import java.net.MalformedURLException;
|
|
48
51
|
import java.net.URL;
|
|
49
52
|
import java.util.ArrayList;
|
|
50
|
-
import java.util.Arrays;
|
|
51
53
|
import java.util.Date;
|
|
52
54
|
import java.util.HashSet;
|
|
53
55
|
import java.util.List;
|
|
@@ -56,7 +58,6 @@ import java.util.Objects;
|
|
|
56
58
|
import java.util.Set;
|
|
57
59
|
import java.util.Timer;
|
|
58
60
|
import java.util.TimerTask;
|
|
59
|
-
import java.util.UUID;
|
|
60
61
|
import java.util.concurrent.Phaser;
|
|
61
62
|
import java.util.concurrent.Semaphore;
|
|
62
63
|
import java.util.concurrent.TimeUnit;
|
|
@@ -83,8 +84,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
83
84
|
private static final String DEFAULT_CHANNEL_PREF_KEY = "CapacitorUpdater.defaultChannel";
|
|
84
85
|
private static final String[] BREAKING_EVENT_NAMES = { "breakingAvailable", "majorAvailable" };
|
|
85
86
|
private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
|
|
87
|
+
private static final String SPLASH_SCREEN_PLUGIN_ID = "SplashScreen";
|
|
88
|
+
private static final int SPLASH_SCREEN_RETRY_DELAY_MS = 100;
|
|
89
|
+
private static final int SPLASH_SCREEN_MAX_RETRIES = 20;
|
|
90
|
+
private static final long PENDING_BUNDLE_APP_READY_MIN_TIMEOUT_MS = 30000L;
|
|
86
91
|
|
|
87
|
-
private final String pluginVersion = "7.
|
|
92
|
+
private final String pluginVersion = "7.45.10";
|
|
88
93
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
89
94
|
|
|
90
95
|
private SharedPreferences.Editor editor;
|
|
@@ -108,21 +113,33 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
108
113
|
private Boolean autoSplashscreenLoader = false;
|
|
109
114
|
private Integer autoSplashscreenTimeout = 10000;
|
|
110
115
|
private Boolean autoSplashscreenTimedOut = false;
|
|
116
|
+
private int splashscreenInvocationToken = 0;
|
|
111
117
|
private String directUpdateMode = "false";
|
|
112
118
|
private Boolean wasRecentlyInstalledOrUpdated = false;
|
|
113
|
-
private
|
|
119
|
+
private volatile boolean onLaunchDirectUpdateUsed = false;
|
|
114
120
|
Boolean shakeMenuEnabled = false;
|
|
121
|
+
Boolean shakeChannelSelectorEnabled = false;
|
|
115
122
|
private Boolean allowManualBundleError = false;
|
|
116
|
-
|
|
123
|
+
Boolean allowSetDefaultChannel = true;
|
|
124
|
+
|
|
125
|
+
String getUpdateUrl() {
|
|
126
|
+
return this.updateUrl;
|
|
127
|
+
}
|
|
117
128
|
|
|
118
129
|
// Used for activity-based foreground/background detection on Android < 14
|
|
119
130
|
private Boolean isPreviousMainActivity = true;
|
|
120
131
|
|
|
121
132
|
private volatile Thread backgroundDownloadTask;
|
|
122
133
|
private volatile Thread appReadyCheck;
|
|
134
|
+
private volatile long downloadStartTimeMs = 0;
|
|
135
|
+
private static final long DOWNLOAD_TIMEOUT_MS = 3600000; // 1 hour timeout
|
|
123
136
|
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
private final Phaser semaphoreReady = new Phaser(0) {
|
|
138
|
+
@Override
|
|
139
|
+
protected boolean onAdvance(final int phase, final int registeredParties) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
126
143
|
|
|
127
144
|
// Lock to ensure cleanup completes before downloads start
|
|
128
145
|
private final Object cleanupLock = new Object();
|
|
@@ -138,6 +155,28 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
138
155
|
private FrameLayout splashscreenLoaderOverlay;
|
|
139
156
|
private Runnable splashscreenTimeoutRunnable;
|
|
140
157
|
|
|
158
|
+
private static final class FireAndForgetPluginCall extends PluginCall {
|
|
159
|
+
|
|
160
|
+
FireAndForgetPluginCall(final String methodName, final JSObject data) {
|
|
161
|
+
super(null, SPLASH_SCREEN_PLUGIN_ID, PluginCall.CALLBACK_ID_DANGLING, methodName, data);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@Override
|
|
165
|
+
public void successCallback(final PluginResult successResult) {}
|
|
166
|
+
|
|
167
|
+
@Override
|
|
168
|
+
public void resolve(final JSObject data) {}
|
|
169
|
+
|
|
170
|
+
@Override
|
|
171
|
+
public void resolve() {}
|
|
172
|
+
|
|
173
|
+
@Override
|
|
174
|
+
public void errorCallback(final String msg) {}
|
|
175
|
+
|
|
176
|
+
@Override
|
|
177
|
+
public void reject(final String msg, final String code, final Exception ex, final JSObject data) {}
|
|
178
|
+
}
|
|
179
|
+
|
|
141
180
|
// App lifecycle observer using ProcessLifecycleOwner for reliable foreground/background detection
|
|
142
181
|
private AppLifecycleObserver appLifecycleObserver;
|
|
143
182
|
|
|
@@ -147,6 +186,19 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
147
186
|
private static final int APP_UPDATE_REQUEST_CODE = 9001;
|
|
148
187
|
private InstallStateUpdatedListener installStateUpdatedListener;
|
|
149
188
|
|
|
189
|
+
private PackageInfo getCurrentPackageInfo() throws PackageManager.NameNotFoundException {
|
|
190
|
+
final PackageManager packageManager = this.getContext().getPackageManager();
|
|
191
|
+
final String packageName = this.getContext().getPackageName();
|
|
192
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
193
|
+
return packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0));
|
|
194
|
+
}
|
|
195
|
+
return packageManager.getPackageInfo(packageName, 0);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private String getVersionCode(final PackageInfo packageInfo) {
|
|
199
|
+
return Long.toString(PackageInfoCompat.getLongVersionCode(packageInfo));
|
|
200
|
+
}
|
|
201
|
+
|
|
150
202
|
private void notifyBreakingEvents(final String version) {
|
|
151
203
|
if (version == null || version.isEmpty()) {
|
|
152
204
|
return;
|
|
@@ -158,14 +210,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
158
210
|
}
|
|
159
211
|
}
|
|
160
212
|
|
|
161
|
-
private JSObject mapToJSObject(Map<String, Object> map) {
|
|
162
|
-
JSObject jsObject = new JSObject();
|
|
163
|
-
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
|
164
|
-
jsObject.put(entry.getKey(), entry.getValue());
|
|
165
|
-
}
|
|
166
|
-
return jsObject;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
213
|
private void persistLastFailedBundle(BundleInfo bundle) {
|
|
170
214
|
if (this.prefs == null) {
|
|
171
215
|
return;
|
|
@@ -233,31 +277,37 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
233
277
|
this.implementation = new CapgoUpdater(logger) {
|
|
234
278
|
@Override
|
|
235
279
|
public void notifyDownload(final String id, final int percent) {
|
|
236
|
-
activity
|
|
237
|
-
|
|
238
|
-
|
|
280
|
+
if (activity != null) {
|
|
281
|
+
activity.runOnUiThread(() -> {
|
|
282
|
+
CapacitorUpdaterPlugin.this.notifyDownload(id, percent);
|
|
283
|
+
});
|
|
284
|
+
} else {
|
|
285
|
+
logger.warn("notifyDownload: Activity is null, skipping notification");
|
|
286
|
+
}
|
|
239
287
|
}
|
|
240
288
|
|
|
241
289
|
@Override
|
|
242
290
|
public void directUpdateFinish(final BundleInfo latest) {
|
|
243
|
-
|
|
244
|
-
CapacitorUpdaterPlugin.this.directUpdateFinish(latest);
|
|
245
|
-
});
|
|
291
|
+
CapacitorUpdaterPlugin.this.scheduleDirectUpdateFinish(latest);
|
|
246
292
|
}
|
|
247
293
|
|
|
248
294
|
@Override
|
|
249
295
|
public void notifyListeners(final String id, final Map<String, Object> res) {
|
|
250
|
-
activity
|
|
251
|
-
|
|
252
|
-
|
|
296
|
+
if (activity != null) {
|
|
297
|
+
activity.runOnUiThread(() -> {
|
|
298
|
+
CapacitorUpdaterPlugin.this.notifyListeners(id, InternalUtils.mapToJSObject(res));
|
|
299
|
+
});
|
|
300
|
+
} else {
|
|
301
|
+
logger.warn("notifyListeners: Activity is null, skipping notification for event: " + id);
|
|
302
|
+
}
|
|
253
303
|
}
|
|
254
304
|
};
|
|
255
|
-
final PackageInfo pInfo = this.
|
|
305
|
+
final PackageInfo pInfo = this.getCurrentPackageInfo();
|
|
256
306
|
this.implementation.activity = this.getActivity();
|
|
257
307
|
this.implementation.versionBuild = this.getConfig().getString("version", pInfo.versionName);
|
|
258
308
|
this.implementation.CAP_SERVER_PATH = WebView.CAP_SERVER_PATH;
|
|
259
309
|
this.implementation.pluginVersion = this.pluginVersion;
|
|
260
|
-
this.implementation.versionCode =
|
|
310
|
+
this.implementation.versionCode = this.getVersionCode(pInfo);
|
|
261
311
|
// Removed unused OkHttpClient creation - using shared client in DownloadService instead
|
|
262
312
|
// Handle directUpdate configuration - support string values and backward compatibility
|
|
263
313
|
String directUpdateConfig = this.getConfig().getString("directUpdate", null);
|
|
@@ -299,7 +349,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
299
349
|
}
|
|
300
350
|
}
|
|
301
351
|
this.currentVersionNative = new Version(this.getConfig().getString("version", pInfo.versionName));
|
|
302
|
-
this.currentBuildVersion =
|
|
352
|
+
this.currentBuildVersion = this.getVersionCode(pInfo);
|
|
303
353
|
this.delayUpdateUtils = new DelayUpdateUtils(this.prefs, this.editor, this.currentVersionNative, logger);
|
|
304
354
|
} catch (final PackageManager.NameNotFoundException e) {
|
|
305
355
|
logger.error("Error instantiating implementation " + e.getMessage());
|
|
@@ -426,6 +476,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
426
476
|
this.autoSplashscreenTimeout = Math.max(0, splashscreenTimeoutValue);
|
|
427
477
|
this.implementation.timeout = this.getConfig().getInt("responseTimeout", 20) * 1000;
|
|
428
478
|
this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
|
|
479
|
+
this.shakeChannelSelectorEnabled = this.getConfig().getBoolean("allowShakeChannelSelector", false);
|
|
429
480
|
boolean resetWhenUpdate = this.getConfig().getBoolean("resetWhenUpdate", true);
|
|
430
481
|
|
|
431
482
|
// Check if app was recently installed/updated BEFORE cleanupObsoleteVersions updates LatestVersionNative
|
|
@@ -468,38 +519,86 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
468
519
|
}
|
|
469
520
|
}
|
|
470
521
|
|
|
471
|
-
private
|
|
522
|
+
private boolean semaphoreWait(final int phase, Number waitTime) {
|
|
472
523
|
try {
|
|
473
|
-
semaphoreReady.awaitAdvanceInterruptibly(
|
|
524
|
+
semaphoreReady.awaitAdvanceInterruptibly(phase, waitTime.longValue(), TimeUnit.MILLISECONDS);
|
|
474
525
|
logger.info("semaphoreReady count " + semaphoreReady.getPhase());
|
|
526
|
+
return true;
|
|
475
527
|
} catch (InterruptedException e) {
|
|
476
528
|
logger.info("semaphoreWait InterruptedException");
|
|
529
|
+
cleanupTimedOutSemaphoreWait(phase);
|
|
477
530
|
Thread.currentThread().interrupt(); // Restore interrupted status
|
|
531
|
+
return false;
|
|
478
532
|
} catch (TimeoutException e) {
|
|
479
533
|
logger.error("Semaphore timeout: " + e.getMessage());
|
|
480
|
-
|
|
534
|
+
cleanupTimedOutSemaphoreWait(phase);
|
|
535
|
+
return false;
|
|
481
536
|
}
|
|
482
537
|
}
|
|
483
538
|
|
|
484
|
-
private
|
|
539
|
+
private int semaphoreUp() {
|
|
485
540
|
logger.info("semaphoreUp");
|
|
486
|
-
semaphoreReady.register();
|
|
541
|
+
return semaphoreReady.register();
|
|
487
542
|
}
|
|
488
543
|
|
|
489
544
|
private void semaphoreDown() {
|
|
545
|
+
if (semaphoreReady.getRegisteredParties() == 0) {
|
|
546
|
+
logger.info("semaphoreDown skipped, no pending app ready wait");
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
490
549
|
logger.info("semaphoreDown");
|
|
491
550
|
logger.info("semaphoreDown count " + semaphoreReady.getPhase());
|
|
492
551
|
semaphoreReady.arriveAndDeregister();
|
|
493
552
|
}
|
|
494
553
|
|
|
554
|
+
private void cleanupTimedOutSemaphoreWait(final int phase) {
|
|
555
|
+
if (semaphoreReady.getPhase() != phase || semaphoreReady.getRegisteredParties() == 0) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
logger.info("Cleaning up stale app ready wait for phase " + phase);
|
|
559
|
+
semaphoreReady.arriveAndDeregister();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
protected long getMinimumPendingBundleAppReadyTimeoutMs() {
|
|
563
|
+
return PENDING_BUNDLE_APP_READY_MIN_TIMEOUT_MS;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private long resolveAppReadyCheckTimeoutMs() {
|
|
567
|
+
long configuredTimeoutMs = this.appReadyTimeout.longValue();
|
|
568
|
+
try {
|
|
569
|
+
if (this.implementation == null) {
|
|
570
|
+
return configuredTimeoutMs;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
final BundleInfo current = this.implementation.getCurrentBundle();
|
|
574
|
+
if (current == null || BundleStatus.SUCCESS == current.getStatus()) {
|
|
575
|
+
return configuredTimeoutMs;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return Math.max(configuredTimeoutMs, this.getMinimumPendingBundleAppReadyTimeoutMs());
|
|
579
|
+
} catch (final Exception e) {
|
|
580
|
+
logger.warn("Falling back to configured appReadyTimeout: " + e.getMessage());
|
|
581
|
+
return configuredTimeoutMs;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
495
585
|
private void sendReadyToJs(final BundleInfo current, final String msg) {
|
|
496
586
|
sendReadyToJs(current, msg, false);
|
|
497
587
|
}
|
|
498
588
|
|
|
589
|
+
private void notifyBundleSet(final BundleInfo bundle) {
|
|
590
|
+
if (bundle == null) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
final JSObject ret = new JSObject();
|
|
594
|
+
ret.put("bundle", InternalUtils.mapToJSObject(bundle.toJSONMap()));
|
|
595
|
+
this.notifyListeners("set", ret, true);
|
|
596
|
+
}
|
|
597
|
+
|
|
499
598
|
private void sendReadyToJs(final BundleInfo current, final String msg, final boolean isDirectUpdate) {
|
|
500
599
|
logger.info("sendReadyToJs: " + msg);
|
|
501
600
|
final JSObject ret = new JSObject();
|
|
502
|
-
ret.put("bundle", mapToJSObject(current.toJSONMap()));
|
|
601
|
+
ret.put("bundle", InternalUtils.mapToJSObject(current.toJSONMap()));
|
|
503
602
|
ret.put("status", msg);
|
|
504
603
|
|
|
505
604
|
// No need to wait for semaphore anymore since _reload() has already waited
|
|
@@ -523,47 +622,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
523
622
|
private void hideSplashscreenInternal() {
|
|
524
623
|
cancelSplashscreenTimeout();
|
|
525
624
|
removeSplashscreenLoader();
|
|
526
|
-
|
|
527
|
-
try {
|
|
528
|
-
if (getBridge() == null) {
|
|
529
|
-
logger.warn("Bridge not ready for hiding splashscreen with autoSplashscreen");
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// Try to call the SplashScreen plugin directly through the bridge
|
|
534
|
-
PluginHandle splashScreenPlugin = getBridge().getPlugin("SplashScreen");
|
|
535
|
-
if (splashScreenPlugin != null) {
|
|
536
|
-
try {
|
|
537
|
-
// Create a plugin call for the hide method using reflection to access private msgHandler
|
|
538
|
-
JSObject options = new JSObject();
|
|
539
|
-
java.lang.reflect.Field msgHandlerField = getBridge().getClass().getDeclaredField("msgHandler");
|
|
540
|
-
msgHandlerField.setAccessible(true);
|
|
541
|
-
Object msgHandler = msgHandlerField.get(getBridge());
|
|
542
|
-
|
|
543
|
-
PluginCall call = new PluginCall(
|
|
544
|
-
(com.getcapacitor.MessageHandler) msgHandler,
|
|
545
|
-
"SplashScreen",
|
|
546
|
-
"FAKE_CALLBACK_ID_HIDE",
|
|
547
|
-
"hide",
|
|
548
|
-
options
|
|
549
|
-
);
|
|
550
|
-
|
|
551
|
-
// Call the hide method directly
|
|
552
|
-
splashScreenPlugin.invoke("hide", call);
|
|
553
|
-
logger.info("Splashscreen hidden automatically via direct plugin call");
|
|
554
|
-
} catch (Exception e) {
|
|
555
|
-
logger.error("Failed to call SplashScreen hide method: " + e.getMessage());
|
|
556
|
-
}
|
|
557
|
-
} else {
|
|
558
|
-
logger.warn("autoSplashscreen: SplashScreen plugin not found. Install @capacitor/splash-screen plugin.");
|
|
559
|
-
}
|
|
560
|
-
} catch (Exception e) {
|
|
561
|
-
logger.error(
|
|
562
|
-
"Error hiding splashscreen with autoSplashscreen: " +
|
|
563
|
-
e.getMessage() +
|
|
564
|
-
". Make sure @capacitor/splash-screen plugin is installed and configured."
|
|
565
|
-
);
|
|
566
|
-
}
|
|
625
|
+
invokeSplashScreenPluginMethod("hide", new JSObject(), SPLASH_SCREEN_MAX_RETRIES, ++this.splashscreenInvocationToken);
|
|
567
626
|
}
|
|
568
627
|
|
|
569
628
|
private void showSplashscreen() {
|
|
@@ -578,37 +637,87 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
578
637
|
cancelSplashscreenTimeout();
|
|
579
638
|
this.autoSplashscreenTimedOut = false;
|
|
580
639
|
|
|
640
|
+
final JSObject options = new JSObject();
|
|
641
|
+
options.put("autoHide", false);
|
|
642
|
+
invokeSplashScreenPluginMethod("show", options, SPLASH_SCREEN_MAX_RETRIES, ++this.splashscreenInvocationToken);
|
|
643
|
+
|
|
644
|
+
addSplashscreenLoaderIfNeeded();
|
|
645
|
+
scheduleSplashscreenTimeout();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
private void invokeSplashScreenPluginMethod(
|
|
649
|
+
final String methodName,
|
|
650
|
+
final JSObject options,
|
|
651
|
+
final int retriesRemaining,
|
|
652
|
+
final int requestToken
|
|
653
|
+
) {
|
|
654
|
+
if (requestToken != this.splashscreenInvocationToken) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
581
658
|
try {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
(com.getcapacitor.MessageHandler) msgHandler,
|
|
594
|
-
"SplashScreen",
|
|
595
|
-
"FAKE_CALLBACK_ID_SHOW",
|
|
596
|
-
"show",
|
|
597
|
-
options
|
|
598
|
-
);
|
|
659
|
+
final Bridge bridge = getBridge();
|
|
660
|
+
if (bridge == null) {
|
|
661
|
+
retrySplashScreenInvocation(
|
|
662
|
+
methodName,
|
|
663
|
+
options,
|
|
664
|
+
retriesRemaining,
|
|
665
|
+
requestToken,
|
|
666
|
+
"Bridge not ready for " + ("show".equals(methodName) ? "showing" : "hiding") + " splashscreen"
|
|
667
|
+
);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
599
670
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
671
|
+
final PluginHandle splashScreenPlugin = bridge.getPlugin(SPLASH_SCREEN_PLUGIN_ID);
|
|
672
|
+
if (splashScreenPlugin == null) {
|
|
673
|
+
retrySplashScreenInvocation(
|
|
674
|
+
methodName,
|
|
675
|
+
options,
|
|
676
|
+
retriesRemaining,
|
|
677
|
+
requestToken,
|
|
678
|
+
"autoSplashscreen: SplashScreen plugin not found. Install @capacitor/splash-screen plugin."
|
|
679
|
+
);
|
|
680
|
+
return;
|
|
605
681
|
}
|
|
606
|
-
|
|
607
|
-
|
|
682
|
+
|
|
683
|
+
splashScreenPlugin.invoke(methodName, new FireAndForgetPluginCall(methodName, options));
|
|
684
|
+
logger.info("Splashscreen " + methodName + " invoked automatically");
|
|
685
|
+
} catch (final Exception e) {
|
|
686
|
+
retrySplashScreenInvocation(
|
|
687
|
+
methodName,
|
|
688
|
+
options,
|
|
689
|
+
retriesRemaining,
|
|
690
|
+
requestToken,
|
|
691
|
+
"Failed to call SplashScreen " + methodName + " method: " + e.getMessage()
|
|
692
|
+
);
|
|
608
693
|
}
|
|
694
|
+
}
|
|
609
695
|
|
|
610
|
-
|
|
611
|
-
|
|
696
|
+
private void retrySplashScreenInvocation(
|
|
697
|
+
final String methodName,
|
|
698
|
+
final JSObject options,
|
|
699
|
+
final int retriesRemaining,
|
|
700
|
+
final int requestToken,
|
|
701
|
+
final String message
|
|
702
|
+
) {
|
|
703
|
+
if (retriesRemaining > 0) {
|
|
704
|
+
logger.info(message + ". Retrying.");
|
|
705
|
+
this.mainHandler.postDelayed(
|
|
706
|
+
() -> invokeSplashScreenPluginMethod(methodName, options, retriesRemaining - 1, requestToken),
|
|
707
|
+
SPLASH_SCREEN_RETRY_DELAY_MS
|
|
708
|
+
);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if ("show".equals(methodName)) {
|
|
713
|
+
logger.warn(message);
|
|
714
|
+
} else {
|
|
715
|
+
logger.error(message);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
boolean isCurrentSplashscreenInvocationTokenForTesting(final int requestToken) {
|
|
720
|
+
return requestToken == this.splashscreenInvocationToken;
|
|
612
721
|
}
|
|
613
722
|
|
|
614
723
|
private void addSplashscreenLoaderIfNeeded() {
|
|
@@ -749,14 +858,74 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
749
858
|
return plannedDirectUpdate && !Boolean.TRUE.equals(this.autoSplashscreenTimedOut);
|
|
750
859
|
}
|
|
751
860
|
|
|
861
|
+
static boolean shouldConsumeOnLaunchDirectUpdate(final String directUpdateMode, final boolean plannedDirectUpdate) {
|
|
862
|
+
return plannedDirectUpdate && "onLaunch".equals(directUpdateMode);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
private void consumeOnLaunchDirectUpdateAttempt(final boolean plannedDirectUpdate) {
|
|
866
|
+
if (!shouldConsumeOnLaunchDirectUpdate(this.directUpdateMode, plannedDirectUpdate)) {
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
this.onLaunchDirectUpdateUsed = true;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
void configureDirectUpdateModeForTesting(final String directUpdateMode, final boolean onLaunchDirectUpdateUsed) {
|
|
874
|
+
this.directUpdateMode = directUpdateMode;
|
|
875
|
+
this.onLaunchDirectUpdateUsed = onLaunchDirectUpdateUsed;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
boolean shouldUseDirectUpdateForTesting() {
|
|
879
|
+
return this.shouldUseDirectUpdate();
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
boolean hasConsumedOnLaunchDirectUpdateForTesting() {
|
|
883
|
+
return this.onLaunchDirectUpdateUsed;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
boolean isVersionDownloadInProgress(final String version) {
|
|
887
|
+
return (
|
|
888
|
+
version != null &&
|
|
889
|
+
!version.isEmpty() &&
|
|
890
|
+
this.implementation != null &&
|
|
891
|
+
this.implementation.activity != null &&
|
|
892
|
+
DownloadWorkerManager.isVersionDownloading(this.implementation.activity, version)
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
void setLoggerForTesting(final Logger logger) {
|
|
897
|
+
this.logger = logger;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
void completeBackgroundTaskForTesting(final BundleInfo current, final boolean plannedDirectUpdate) {
|
|
901
|
+
this.endBackGroundTaskWithNotif("test", current.getVersionName(), current, false, plannedDirectUpdate);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
void scheduleDirectUpdateFinish(final BundleInfo latest) {
|
|
905
|
+
startNewThread(() -> {
|
|
906
|
+
try {
|
|
907
|
+
Activity currentActivity = this.getActivity();
|
|
908
|
+
if (currentActivity != null) {
|
|
909
|
+
this.implementation.activity = currentActivity;
|
|
910
|
+
} else {
|
|
911
|
+
logger.warn("directUpdateFinish: Activity is null, proceeding without refreshing the activity reference");
|
|
912
|
+
}
|
|
913
|
+
this.directUpdateFinish(latest);
|
|
914
|
+
} catch (final Exception e) {
|
|
915
|
+
logger.error("directUpdateFinish failed: " + e.getMessage());
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
|
|
752
920
|
private void directUpdateFinish(final BundleInfo latest) {
|
|
753
921
|
if ("onLaunch".equals(this.directUpdateMode)) {
|
|
754
922
|
this.onLaunchDirectUpdateUsed = true;
|
|
755
923
|
this.implementation.directUpdate = false;
|
|
756
924
|
}
|
|
757
|
-
CapacitorUpdaterPlugin.this.implementation.set(latest)
|
|
758
|
-
|
|
759
|
-
|
|
925
|
+
if (CapacitorUpdaterPlugin.this.implementation.set(latest) && CapacitorUpdaterPlugin.this._reload()) {
|
|
926
|
+
this.notifyBundleSet(latest);
|
|
927
|
+
sendReadyToJs(latest, "update installed", true);
|
|
928
|
+
}
|
|
760
929
|
}
|
|
761
930
|
|
|
762
931
|
private void cleanupObsoleteVersions() {
|
|
@@ -842,7 +1011,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
842
1011
|
final JSObject ret = new JSObject();
|
|
843
1012
|
ret.put("percent", percent);
|
|
844
1013
|
final BundleInfo bundleInfo = this.implementation.getBundleInfo(id);
|
|
845
|
-
ret.put("bundle", mapToJSObject(bundleInfo.toJSONMap()));
|
|
1014
|
+
ret.put("bundle", InternalUtils.mapToJSObject(bundleInfo.toJSONMap()));
|
|
846
1015
|
this.notifyListeners("download", ret);
|
|
847
1016
|
|
|
848
1017
|
if (percent == 100) {
|
|
@@ -994,7 +1163,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
994
1163
|
DEFAULT_CHANNEL_PREF_KEY,
|
|
995
1164
|
configDefaultChannel,
|
|
996
1165
|
(res) -> {
|
|
997
|
-
JSObject jsRes = mapToJSObject(res);
|
|
1166
|
+
JSObject jsRes = InternalUtils.mapToJSObject(res);
|
|
998
1167
|
if (jsRes.has("error")) {
|
|
999
1168
|
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
|
|
1000
1169
|
String errorCode = jsRes.getString("error");
|
|
@@ -1007,7 +1176,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1007
1176
|
} else {
|
|
1008
1177
|
if (CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() && Boolean.TRUE.equals(triggerAutoUpdate)) {
|
|
1009
1178
|
logger.info("Calling autoupdater after channel change!");
|
|
1010
|
-
|
|
1179
|
+
// Check if download is already in progress (with timeout protection)
|
|
1180
|
+
if (!this.isDownloadStuckOrTimedOut()) {
|
|
1181
|
+
backgroundDownload();
|
|
1182
|
+
} else {
|
|
1183
|
+
logger.info("Download already in progress, skipping duplicate download request");
|
|
1184
|
+
}
|
|
1011
1185
|
}
|
|
1012
1186
|
call.resolve(jsRes);
|
|
1013
1187
|
}
|
|
@@ -1042,7 +1216,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1042
1216
|
DEFAULT_CHANNEL_PREF_KEY,
|
|
1043
1217
|
CapacitorUpdaterPlugin.this.allowSetDefaultChannel,
|
|
1044
1218
|
(res) -> {
|
|
1045
|
-
JSObject jsRes = mapToJSObject(res);
|
|
1219
|
+
JSObject jsRes = InternalUtils.mapToJSObject(res);
|
|
1046
1220
|
if (jsRes.has("error")) {
|
|
1047
1221
|
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
|
|
1048
1222
|
String errorCode = jsRes.getString("error");
|
|
@@ -1066,7 +1240,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1066
1240
|
} else {
|
|
1067
1241
|
if (CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() && Boolean.TRUE.equals(triggerAutoUpdate)) {
|
|
1068
1242
|
logger.info("Calling autoupdater after channel change!");
|
|
1069
|
-
|
|
1243
|
+
// Check if download is already in progress (with timeout protection)
|
|
1244
|
+
if (!this.isDownloadStuckOrTimedOut()) {
|
|
1245
|
+
backgroundDownload();
|
|
1246
|
+
} else {
|
|
1247
|
+
logger.info("Download already in progress, skipping duplicate download request");
|
|
1248
|
+
}
|
|
1070
1249
|
}
|
|
1071
1250
|
call.resolve(jsRes);
|
|
1072
1251
|
}
|
|
@@ -1085,7 +1264,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1085
1264
|
logger.info("getChannel");
|
|
1086
1265
|
startNewThread(() ->
|
|
1087
1266
|
CapacitorUpdaterPlugin.this.implementation.getChannel((res) -> {
|
|
1088
|
-
JSObject jsRes = mapToJSObject(res);
|
|
1267
|
+
JSObject jsRes = InternalUtils.mapToJSObject(res);
|
|
1089
1268
|
if (jsRes.has("error")) {
|
|
1090
1269
|
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
|
|
1091
1270
|
String errorCode = jsRes.getString("error");
|
|
@@ -1112,7 +1291,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1112
1291
|
logger.info("listChannels");
|
|
1113
1292
|
startNewThread(() ->
|
|
1114
1293
|
CapacitorUpdaterPlugin.this.implementation.listChannels((res) -> {
|
|
1115
|
-
JSObject jsRes = mapToJSObject(res);
|
|
1294
|
+
JSObject jsRes = InternalUtils.mapToJSObject(res);
|
|
1116
1295
|
if (jsRes.has("error")) {
|
|
1117
1296
|
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
|
|
1118
1297
|
String errorCode = jsRes.getString("error");
|
|
@@ -1157,12 +1336,13 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1157
1336
|
final BundleInfo downloaded;
|
|
1158
1337
|
if (manifest != null) {
|
|
1159
1338
|
// For manifest downloads, we need to handle this asynchronously
|
|
1160
|
-
//
|
|
1161
|
-
|
|
1339
|
+
// to avoid automatically scheduling/applying the downloaded bundle.
|
|
1340
|
+
// Manual download must not schedule/apply the bundle automatically.
|
|
1341
|
+
CapacitorUpdaterPlugin.this.implementation.downloadBackground(url, version, sessionKey, checksum, manifest, false);
|
|
1162
1342
|
// Return immediately with a pending status - the actual result will come via listeners
|
|
1163
1343
|
final String id = CapacitorUpdaterPlugin.this.implementation.randomString();
|
|
1164
1344
|
downloaded = new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), "");
|
|
1165
|
-
call.resolve(mapToJSObject(downloaded.toJSONMap()));
|
|
1345
|
+
call.resolve(InternalUtils.mapToJSObject(downloaded.toJSONMap()));
|
|
1166
1346
|
return;
|
|
1167
1347
|
} else {
|
|
1168
1348
|
downloaded = CapacitorUpdaterPlugin.this.implementation.download(url, version, sessionKey, checksum);
|
|
@@ -1170,7 +1350,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1170
1350
|
if (downloaded.isErrorStatus()) {
|
|
1171
1351
|
throw new RuntimeException("Download failed: " + downloaded.getStatus());
|
|
1172
1352
|
} else {
|
|
1173
|
-
call.resolve(mapToJSObject(downloaded.toJSONMap()));
|
|
1353
|
+
call.resolve(InternalUtils.mapToJSObject(downloaded.toJSONMap()));
|
|
1174
1354
|
}
|
|
1175
1355
|
} catch (final Exception e) {
|
|
1176
1356
|
logger.error("Failed to download from: " + url + " " + e.getMessage());
|
|
@@ -1207,12 +1387,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1207
1387
|
this.bridge.getWebView().post(() -> this.bridge.getWebView().evaluateJavascript(script, null));
|
|
1208
1388
|
}
|
|
1209
1389
|
|
|
1210
|
-
|
|
1390
|
+
private void applyCurrentBundleToBridge() {
|
|
1211
1391
|
final String path = this.implementation.getCurrentBundlePath();
|
|
1392
|
+
final boolean usingBuiltin = this.implementation.isUsingBuiltin();
|
|
1212
1393
|
if (this.keepUrlPathAfterReload) {
|
|
1213
1394
|
this.syncKeepUrlPathFlag(true);
|
|
1214
1395
|
}
|
|
1215
|
-
this.semaphoreUp();
|
|
1216
1396
|
logger.info("Reloading: " + path);
|
|
1217
1397
|
|
|
1218
1398
|
AtomicReference<URL> url = new AtomicReference<>();
|
|
@@ -1257,7 +1437,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1257
1437
|
}
|
|
1258
1438
|
|
|
1259
1439
|
if (url.get() != null) {
|
|
1260
|
-
if (
|
|
1440
|
+
if (usingBuiltin) {
|
|
1261
1441
|
this.bridge.getLocalServer().hostAssets(path);
|
|
1262
1442
|
} else {
|
|
1263
1443
|
this.bridge.getLocalServer().hostFiles(path);
|
|
@@ -1277,14 +1457,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1277
1457
|
} catch (MalformedURLException e) {
|
|
1278
1458
|
logger.error("Cannot get finalUrl from capacitor bridge " + e.getMessage());
|
|
1279
1459
|
|
|
1280
|
-
if (
|
|
1460
|
+
if (usingBuiltin) {
|
|
1281
1461
|
this.bridge.setServerAssetPath(path);
|
|
1282
1462
|
} else {
|
|
1283
1463
|
this.bridge.setServerBasePath(path);
|
|
1284
1464
|
}
|
|
1285
1465
|
}
|
|
1286
1466
|
} else {
|
|
1287
|
-
if (
|
|
1467
|
+
if (usingBuiltin) {
|
|
1288
1468
|
this.bridge.setServerAssetPath(path);
|
|
1289
1469
|
} else {
|
|
1290
1470
|
this.bridge.setServerBasePath(path);
|
|
@@ -1300,34 +1480,75 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1300
1480
|
});
|
|
1301
1481
|
}
|
|
1302
1482
|
}
|
|
1483
|
+
}
|
|
1303
1484
|
|
|
1304
|
-
|
|
1305
|
-
this.notifyListeners("appReloaded", new JSObject());
|
|
1306
|
-
|
|
1307
|
-
// Wait for the reload to complete (until notifyAppReady is called)
|
|
1485
|
+
protected void restoreLiveBundleStateAfterFailedReload() {
|
|
1308
1486
|
try {
|
|
1309
|
-
this.
|
|
1310
|
-
} catch (Exception e) {
|
|
1311
|
-
logger.
|
|
1312
|
-
return false;
|
|
1487
|
+
this.applyCurrentBundleToBridge();
|
|
1488
|
+
} catch (final Exception e) {
|
|
1489
|
+
logger.warn("Failed to restore live bundle after rejected reload: " + e.getMessage());
|
|
1313
1490
|
}
|
|
1491
|
+
}
|
|
1314
1492
|
|
|
1315
|
-
|
|
1493
|
+
protected boolean _reload() {
|
|
1494
|
+
final int phase = this.semaphoreUp();
|
|
1495
|
+
this.applyCurrentBundleToBridge();
|
|
1496
|
+
|
|
1497
|
+
final long waitTimeMs = this.resolveAppReadyCheckTimeoutMs();
|
|
1498
|
+
this.checkAppReady(waitTimeMs);
|
|
1499
|
+
this.notifyListeners("appReloaded", new JSObject());
|
|
1500
|
+
|
|
1501
|
+
// Wait for the reload to complete (until notifyAppReady is called)
|
|
1502
|
+
return this.semaphoreWait(phase, waitTimeMs);
|
|
1316
1503
|
}
|
|
1317
1504
|
|
|
1318
1505
|
@PluginMethod
|
|
1319
1506
|
public void reload(final PluginCall call) {
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1507
|
+
startNewThread(() -> {
|
|
1508
|
+
try {
|
|
1509
|
+
final BundleInfo current = this.implementation.getCurrentBundle();
|
|
1510
|
+
final BundleInfo next = this.implementation.getNextBundle();
|
|
1511
|
+
|
|
1512
|
+
if (next != null && !next.isErrorStatus() && !next.getId().equals(current.getId())) {
|
|
1513
|
+
final CapgoUpdater.ResetState previousState = this.implementation.captureResetState();
|
|
1514
|
+
final String previousBundleName = this.implementation.getCurrentBundle().getVersionName();
|
|
1515
|
+
logger.info("Applying pending bundle before reload: " + next.getVersionName());
|
|
1516
|
+
final boolean didApplyPendingBundle;
|
|
1517
|
+
if (next.isBuiltin()) {
|
|
1518
|
+
this.implementation.prepareResetStateForTransition();
|
|
1519
|
+
didApplyPendingBundle = true;
|
|
1520
|
+
} else {
|
|
1521
|
+
didApplyPendingBundle = this.implementation.stagePendingReload(next);
|
|
1522
|
+
}
|
|
1523
|
+
if (didApplyPendingBundle && this._reload()) {
|
|
1524
|
+
if (next.isBuiltin()) {
|
|
1525
|
+
this.implementation.finalizeResetTransition(previousBundleName, false);
|
|
1526
|
+
} else {
|
|
1527
|
+
this.implementation.finalizePendingReload(next, previousBundleName);
|
|
1528
|
+
}
|
|
1529
|
+
this.notifyBundleSet(next);
|
|
1530
|
+
this.implementation.setNextBundle(null);
|
|
1531
|
+
call.resolve();
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
this.implementation.restoreResetState(previousState);
|
|
1535
|
+
this.restoreLiveBundleStateAfterFailedReload();
|
|
1536
|
+
logger.error("Reload failed after applying pending bundle: " + next.getVersionName());
|
|
1537
|
+
call.reject("Reload failed after applying pending bundle: " + next.getVersionName());
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
if (this._reload()) {
|
|
1542
|
+
call.resolve();
|
|
1543
|
+
} else {
|
|
1544
|
+
logger.error("Reload failed");
|
|
1545
|
+
call.reject("Reload failed");
|
|
1546
|
+
}
|
|
1547
|
+
} catch (final Exception e) {
|
|
1548
|
+
logger.error("Could not reload " + e.getMessage());
|
|
1549
|
+
call.reject("Could not reload", e);
|
|
1326
1550
|
}
|
|
1327
|
-
}
|
|
1328
|
-
logger.error("Could not reload " + e.getMessage());
|
|
1329
|
-
call.reject("Could not reload", e);
|
|
1330
|
-
}
|
|
1551
|
+
});
|
|
1331
1552
|
}
|
|
1332
1553
|
|
|
1333
1554
|
@PluginMethod
|
|
@@ -1344,7 +1565,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1344
1565
|
logger.error("Set next id failed. Bundle " + id + " does not exist.");
|
|
1345
1566
|
call.reject("Set next id failed. Bundle " + id + " does not exist.");
|
|
1346
1567
|
} else {
|
|
1347
|
-
call.resolve(mapToJSObject(this.implementation.getBundleInfo(id).toJSONMap()));
|
|
1568
|
+
call.resolve(InternalUtils.mapToJSObject(this.implementation.getBundleInfo(id).toJSONMap()));
|
|
1348
1569
|
}
|
|
1349
1570
|
} catch (final Exception e) {
|
|
1350
1571
|
logger.error("Could not set next id " + id + " " + e.getMessage());
|
|
@@ -1365,9 +1586,13 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1365
1586
|
if (!this.implementation.set(id)) {
|
|
1366
1587
|
logger.info("No such bundle " + id);
|
|
1367
1588
|
call.reject("Update failed, id " + id + " does not exist.");
|
|
1589
|
+
} else if (!this._reload()) {
|
|
1590
|
+
logger.error("Reload failed after setting bundle " + id);
|
|
1591
|
+
call.reject("Reload failed after setting bundle " + id);
|
|
1368
1592
|
} else {
|
|
1369
1593
|
logger.info("Bundle successfully set to " + id);
|
|
1370
|
-
this.
|
|
1594
|
+
this.notifyBundleSet(this.implementation.getBundleInfo(id));
|
|
1595
|
+
call.resolve();
|
|
1371
1596
|
}
|
|
1372
1597
|
} catch (final Exception e) {
|
|
1373
1598
|
logger.error("Could not set id " + id + " " + e.getMessage());
|
|
@@ -1428,7 +1653,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1428
1653
|
}
|
|
1429
1654
|
this.implementation.setError(bundle);
|
|
1430
1655
|
final JSObject ret = new JSObject();
|
|
1431
|
-
ret.put("bundle", mapToJSObject(this.implementation.getBundleInfo(id).toJSONMap()));
|
|
1656
|
+
ret.put("bundle", InternalUtils.mapToJSObject(this.implementation.getBundleInfo(id).toJSONMap()));
|
|
1432
1657
|
call.resolve(ret);
|
|
1433
1658
|
} catch (final Exception e) {
|
|
1434
1659
|
logger.error("Could not set bundle error for id " + id + " " + e.getMessage());
|
|
@@ -1443,7 +1668,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1443
1668
|
final JSObject ret = new JSObject();
|
|
1444
1669
|
final JSArray values = new JSArray();
|
|
1445
1670
|
for (final BundleInfo bundle : res) {
|
|
1446
|
-
values.put(mapToJSObject(bundle.toJSONMap()));
|
|
1671
|
+
values.put(InternalUtils.mapToJSObject(bundle.toJSONMap()));
|
|
1447
1672
|
}
|
|
1448
1673
|
ret.put("bundles", values);
|
|
1449
1674
|
call.resolve(ret);
|
|
@@ -1458,12 +1683,22 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1458
1683
|
final String channel = call.getString("channel");
|
|
1459
1684
|
startNewThread(() ->
|
|
1460
1685
|
CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, channel, (res) -> {
|
|
1461
|
-
JSObject jsRes = mapToJSObject(res);
|
|
1462
|
-
if (jsRes.has("error")) {
|
|
1463
|
-
String error = jsRes.getString("error");
|
|
1686
|
+
JSObject jsRes = InternalUtils.mapToJSObject(res);
|
|
1687
|
+
if (jsRes.has("error") || jsRes.has("kind")) {
|
|
1688
|
+
String error = jsRes.has("error") ? jsRes.getString("error") : "";
|
|
1464
1689
|
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : "server did not provide a message";
|
|
1465
|
-
|
|
1466
|
-
|
|
1690
|
+
String kind = CapacitorUpdaterPlugin.this.getUpdateResponseKind(jsRes.has("kind") ? jsRes.getString("kind") : null);
|
|
1691
|
+
jsRes.put("kind", kind);
|
|
1692
|
+
if ("failed".equals(kind)) {
|
|
1693
|
+
logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
|
|
1694
|
+
call.reject(error.isEmpty() ? errorMessage : error);
|
|
1695
|
+
} else {
|
|
1696
|
+
if (!jsRes.has("version") || jsRes.getString("version").isEmpty()) {
|
|
1697
|
+
jsRes.put("version", CapacitorUpdaterPlugin.this.implementation.getCurrentBundle().getVersionName());
|
|
1698
|
+
}
|
|
1699
|
+
logger.info("getLatest returned " + kind + ": " + errorMessage);
|
|
1700
|
+
call.resolve(jsRes);
|
|
1701
|
+
}
|
|
1467
1702
|
return;
|
|
1468
1703
|
} else if (jsRes.has("message")) {
|
|
1469
1704
|
call.reject(jsRes.getString("message"));
|
|
@@ -1475,24 +1710,83 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1475
1710
|
);
|
|
1476
1711
|
}
|
|
1477
1712
|
|
|
1478
|
-
private boolean _reset(final Boolean toLastSuccessful) {
|
|
1713
|
+
private boolean _reset(final Boolean toLastSuccessful, final Boolean usePendingBundle) {
|
|
1714
|
+
return this.performReset(toLastSuccessful, usePendingBundle, false);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
private boolean performReset(final Boolean toLastSuccessful, final Boolean usePendingBundle, final boolean internal) {
|
|
1479
1718
|
final BundleInfo fallback = this.implementation.getFallbackBundle();
|
|
1480
|
-
this.implementation.
|
|
1719
|
+
final BundleInfo pending = this.implementation.getNextBundle();
|
|
1720
|
+
final CapgoUpdater.ResetState previousState = this.implementation.captureResetState();
|
|
1721
|
+
final String previousBundleName = this.implementation.getCurrentBundle().getVersionName();
|
|
1722
|
+
|
|
1723
|
+
if (Boolean.TRUE.equals(usePendingBundle)) {
|
|
1724
|
+
if (pending == null || pending.isErrorStatus()) {
|
|
1725
|
+
logger.error("No pending bundle available to reset to");
|
|
1726
|
+
return false;
|
|
1727
|
+
}
|
|
1728
|
+
if (!this.implementation.canSet(pending)) {
|
|
1729
|
+
logger.error("Pending bundle is not installable");
|
|
1730
|
+
return false;
|
|
1731
|
+
}
|
|
1732
|
+
this.implementation.prepareResetStateForTransition();
|
|
1733
|
+
logger.info("Resetting to pending bundle: " + pending.getVersionName());
|
|
1734
|
+
final boolean didApplyPendingBundle;
|
|
1735
|
+
if (pending.isBuiltin()) {
|
|
1736
|
+
didApplyPendingBundle = true;
|
|
1737
|
+
} else {
|
|
1738
|
+
didApplyPendingBundle = this.implementation.set(pending);
|
|
1739
|
+
}
|
|
1740
|
+
if (didApplyPendingBundle && this._reload()) {
|
|
1741
|
+
this.implementation.finalizeResetTransition(previousBundleName, internal);
|
|
1742
|
+
this.notifyBundleSet(pending);
|
|
1743
|
+
this.implementation.setNextBundle(null);
|
|
1744
|
+
return true;
|
|
1745
|
+
}
|
|
1746
|
+
this.implementation.restoreResetState(previousState);
|
|
1747
|
+
this.restoreLiveBundleStateAfterFailedReload();
|
|
1748
|
+
return false;
|
|
1749
|
+
}
|
|
1481
1750
|
|
|
1482
|
-
if (toLastSuccessful && !fallback.isBuiltin()) {
|
|
1483
|
-
|
|
1484
|
-
|
|
1751
|
+
if (Boolean.TRUE.equals(toLastSuccessful) && !fallback.isBuiltin()) {
|
|
1752
|
+
if (this.implementation.canSet(fallback)) {
|
|
1753
|
+
this.implementation.prepareResetStateForTransition();
|
|
1754
|
+
logger.info("Resetting to: " + fallback);
|
|
1755
|
+
if (this.implementation.set(fallback) && this._reload()) {
|
|
1756
|
+
this.implementation.finalizeResetTransition(previousBundleName, internal);
|
|
1757
|
+
this.notifyBundleSet(fallback);
|
|
1758
|
+
return true;
|
|
1759
|
+
}
|
|
1760
|
+
if (!internal) {
|
|
1761
|
+
this.implementation.restoreResetState(previousState);
|
|
1762
|
+
this.restoreLiveBundleStateAfterFailedReload();
|
|
1763
|
+
return false;
|
|
1764
|
+
}
|
|
1765
|
+
logger.warn("Fallback reload failed during internal reset, resetting to native instead");
|
|
1766
|
+
} else {
|
|
1767
|
+
logger.warn("Fallback bundle is not installable, resetting to native instead");
|
|
1768
|
+
}
|
|
1485
1769
|
}
|
|
1486
1770
|
|
|
1771
|
+
this.implementation.prepareResetStateForTransition();
|
|
1487
1772
|
logger.info("Resetting to native.");
|
|
1488
|
-
|
|
1773
|
+
if (this._reload()) {
|
|
1774
|
+
this.implementation.finalizeResetTransition(previousBundleName, internal);
|
|
1775
|
+
return true;
|
|
1776
|
+
}
|
|
1777
|
+
if (!internal) {
|
|
1778
|
+
this.implementation.restoreResetState(previousState);
|
|
1779
|
+
this.restoreLiveBundleStateAfterFailedReload();
|
|
1780
|
+
}
|
|
1781
|
+
return false;
|
|
1489
1782
|
}
|
|
1490
1783
|
|
|
1491
1784
|
@PluginMethod
|
|
1492
1785
|
public void reset(final PluginCall call) {
|
|
1493
1786
|
try {
|
|
1494
1787
|
final Boolean toLastSuccessful = call.getBoolean("toLastSuccessful", false);
|
|
1495
|
-
|
|
1788
|
+
final Boolean usePendingBundle = call.getBoolean("usePendingBundle", false);
|
|
1789
|
+
if (this._reset(toLastSuccessful, usePendingBundle)) {
|
|
1496
1790
|
call.resolve();
|
|
1497
1791
|
return;
|
|
1498
1792
|
}
|
|
@@ -1510,7 +1804,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1510
1804
|
try {
|
|
1511
1805
|
final JSObject ret = new JSObject();
|
|
1512
1806
|
final BundleInfo bundle = this.implementation.getCurrentBundle();
|
|
1513
|
-
ret.put("bundle", mapToJSObject(bundle.toJSONMap()));
|
|
1807
|
+
ret.put("bundle", InternalUtils.mapToJSObject(bundle.toJSONMap()));
|
|
1514
1808
|
ret.put("native", this.currentVersionNative);
|
|
1515
1809
|
call.resolve(ret);
|
|
1516
1810
|
} catch (final Exception e) {
|
|
@@ -1528,7 +1822,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1528
1822
|
return;
|
|
1529
1823
|
}
|
|
1530
1824
|
|
|
1531
|
-
call.resolve(mapToJSObject(bundle.toJSONMap()));
|
|
1825
|
+
call.resolve(InternalUtils.mapToJSObject(bundle.toJSONMap()));
|
|
1532
1826
|
} catch (final Exception e) {
|
|
1533
1827
|
logger.error("Could not get next bundle " + e.getMessage());
|
|
1534
1828
|
call.reject("Could not get next bundle", e);
|
|
@@ -1547,7 +1841,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1547
1841
|
this.persistLastFailedBundle(null);
|
|
1548
1842
|
|
|
1549
1843
|
final JSObject ret = new JSObject();
|
|
1550
|
-
ret.put("bundle", mapToJSObject(bundle.toJSONMap()));
|
|
1844
|
+
ret.put("bundle", InternalUtils.mapToJSObject(bundle.toJSONMap()));
|
|
1551
1845
|
call.resolve(ret);
|
|
1552
1846
|
} catch (final Exception e) {
|
|
1553
1847
|
logger.error("Could not get failed update " + e.getMessage());
|
|
@@ -1566,19 +1860,45 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1566
1860
|
public void run() {
|
|
1567
1861
|
try {
|
|
1568
1862
|
CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, null, (res) -> {
|
|
1569
|
-
JSObject jsRes = mapToJSObject(res);
|
|
1570
|
-
if (jsRes.has("error")) {
|
|
1571
|
-
|
|
1863
|
+
JSObject jsRes = InternalUtils.mapToJSObject(res);
|
|
1864
|
+
if (jsRes.has("error") || jsRes.has("kind")) {
|
|
1865
|
+
final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
1866
|
+
String error = jsRes.has("error") ? jsRes.getString("error") : "";
|
|
1572
1867
|
String errorMessage = jsRes.has("message")
|
|
1573
1868
|
? jsRes.getString("message")
|
|
1574
1869
|
: "server did not provide a message";
|
|
1575
|
-
|
|
1870
|
+
int statusCode = jsRes.has("statusCode") ? jsRes.optInt("statusCode", 0) : 0;
|
|
1871
|
+
String kind = CapacitorUpdaterPlugin.this.getUpdateResponseKind(
|
|
1872
|
+
jsRes.has("kind") ? jsRes.getString("kind") : null
|
|
1873
|
+
);
|
|
1874
|
+
String latestVersion = jsRes.has("version") ? jsRes.getString("version") : current.getVersionName();
|
|
1875
|
+
CapacitorUpdaterPlugin.this.notifyUpdateCheckResult(
|
|
1876
|
+
kind,
|
|
1877
|
+
error,
|
|
1878
|
+
errorMessage,
|
|
1879
|
+
statusCode,
|
|
1880
|
+
latestVersion,
|
|
1881
|
+
current
|
|
1882
|
+
);
|
|
1883
|
+
|
|
1884
|
+
if ("failed".equals(kind)) {
|
|
1885
|
+
logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
|
|
1886
|
+
} else if ("blocked".equals(kind)) {
|
|
1887
|
+
logger.info("Update check blocked with error: " + error);
|
|
1888
|
+
} else {
|
|
1889
|
+
logger.info("No new version available");
|
|
1890
|
+
}
|
|
1576
1891
|
} else if (jsRes.has("version")) {
|
|
1577
1892
|
String newVersion = jsRes.getString("version");
|
|
1578
1893
|
String currentVersion = String.valueOf(CapacitorUpdaterPlugin.this.implementation.getCurrentBundle());
|
|
1579
1894
|
if (!Objects.equals(newVersion, currentVersion)) {
|
|
1580
1895
|
logger.info("New version found: " + newVersion);
|
|
1581
|
-
|
|
1896
|
+
// Check if download is already in progress (with timeout protection)
|
|
1897
|
+
if (!CapacitorUpdaterPlugin.this.isDownloadStuckOrTimedOut()) {
|
|
1898
|
+
CapacitorUpdaterPlugin.this.backgroundDownload();
|
|
1899
|
+
} else {
|
|
1900
|
+
logger.info("Download already in progress, skipping duplicate download request");
|
|
1901
|
+
}
|
|
1582
1902
|
}
|
|
1583
1903
|
}
|
|
1584
1904
|
});
|
|
@@ -1603,7 +1923,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1603
1923
|
this.semaphoreDown();
|
|
1604
1924
|
logger.info("semaphoreReady countDown done");
|
|
1605
1925
|
final JSObject ret = new JSObject();
|
|
1606
|
-
ret.put("bundle", mapToJSObject(bundle.toJSONMap()));
|
|
1926
|
+
ret.put("bundle", InternalUtils.mapToJSObject(bundle.toJSONMap()));
|
|
1607
1927
|
call.resolve(ret);
|
|
1608
1928
|
} catch (final Exception e) {
|
|
1609
1929
|
logger.error("Failed to notify app ready state. [Error calling 'notifyAppReady()'] " + e.getMessage());
|
|
@@ -1689,11 +2009,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1689
2009
|
}
|
|
1690
2010
|
|
|
1691
2011
|
private void checkAppReady() {
|
|
2012
|
+
this.checkAppReady(this.resolveAppReadyCheckTimeoutMs());
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
private void checkAppReady(final long waitTimeMs) {
|
|
1692
2016
|
try {
|
|
1693
2017
|
if (this.appReadyCheck != null) {
|
|
1694
2018
|
this.appReadyCheck.interrupt();
|
|
1695
2019
|
}
|
|
1696
|
-
this.appReadyCheck = startNewThread(new DeferredNotifyAppReadyCheck());
|
|
2020
|
+
this.appReadyCheck = startNewThread(new DeferredNotifyAppReadyCheck(waitTimeMs));
|
|
1697
2021
|
} catch (final Exception e) {
|
|
1698
2022
|
logger.error("Failed to start " + DeferredNotifyAppReadyCheck.class.getName() + " " + e.getMessage());
|
|
1699
2023
|
}
|
|
@@ -1708,6 +2032,31 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1708
2032
|
}
|
|
1709
2033
|
}
|
|
1710
2034
|
|
|
2035
|
+
private String getUpdateResponseKind(final String kind) {
|
|
2036
|
+
if ("up_to_date".equals(kind) || "blocked".equals(kind) || "failed".equals(kind)) {
|
|
2037
|
+
return kind;
|
|
2038
|
+
}
|
|
2039
|
+
return "failed";
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
private void notifyUpdateCheckResult(
|
|
2043
|
+
final String kind,
|
|
2044
|
+
final String error,
|
|
2045
|
+
final String message,
|
|
2046
|
+
final int statusCode,
|
|
2047
|
+
final String version,
|
|
2048
|
+
final BundleInfo current
|
|
2049
|
+
) {
|
|
2050
|
+
JSObject ret = new JSObject();
|
|
2051
|
+
ret.put("kind", kind);
|
|
2052
|
+
ret.put("error", error);
|
|
2053
|
+
ret.put("message", message);
|
|
2054
|
+
ret.put("statusCode", statusCode);
|
|
2055
|
+
ret.put("version", version);
|
|
2056
|
+
ret.put("bundle", InternalUtils.mapToJSObject(current.toJSONMap()));
|
|
2057
|
+
this.notifyListeners("updateCheckResult", ret);
|
|
2058
|
+
}
|
|
2059
|
+
|
|
1711
2060
|
private void ensureBridgeSet() {
|
|
1712
2061
|
if (this.bridge != null && this.bridge.getWebView() != null) {
|
|
1713
2062
|
logger.setBridge(this.bridge);
|
|
@@ -1745,11 +2094,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1745
2094
|
String latestVersionName,
|
|
1746
2095
|
BundleInfo current,
|
|
1747
2096
|
Boolean error,
|
|
1748
|
-
Boolean
|
|
2097
|
+
Boolean plannedDirectUpdate,
|
|
1749
2098
|
String failureAction,
|
|
1750
2099
|
String failureEvent,
|
|
1751
2100
|
boolean shouldSendStats
|
|
1752
2101
|
) {
|
|
2102
|
+
this.consumeOnLaunchDirectUpdateAttempt(Boolean.TRUE.equals(plannedDirectUpdate));
|
|
1753
2103
|
if (error) {
|
|
1754
2104
|
logger.info(
|
|
1755
2105
|
"endBackGroundTaskWithNotif error: " +
|
|
@@ -1767,54 +2117,86 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1767
2117
|
this.notifyListeners(failureEvent, ret);
|
|
1768
2118
|
}
|
|
1769
2119
|
final JSObject ret = new JSObject();
|
|
1770
|
-
ret.put("bundle", mapToJSObject(current.toJSONMap()));
|
|
2120
|
+
ret.put("bundle", InternalUtils.mapToJSObject(current.toJSONMap()));
|
|
1771
2121
|
this.notifyListeners("noNeedUpdate", ret);
|
|
1772
|
-
this.sendReadyToJs(current, msg,
|
|
2122
|
+
this.sendReadyToJs(current, msg, plannedDirectUpdate);
|
|
1773
2123
|
this.backgroundDownloadTask = null;
|
|
2124
|
+
this.downloadStartTimeMs = 0;
|
|
1774
2125
|
logger.info("endBackGroundTaskWithNotif " + msg);
|
|
1775
2126
|
}
|
|
1776
2127
|
|
|
2128
|
+
private boolean isDownloadStuckOrTimedOut() {
|
|
2129
|
+
if (this.backgroundDownloadTask == null || !this.backgroundDownloadTask.isAlive()) {
|
|
2130
|
+
return false;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
// Check if download has timed out
|
|
2134
|
+
if (this.downloadStartTimeMs > 0) {
|
|
2135
|
+
long elapsed = System.currentTimeMillis() - this.downloadStartTimeMs;
|
|
2136
|
+
if (elapsed > DOWNLOAD_TIMEOUT_MS) {
|
|
2137
|
+
logger.warn(
|
|
2138
|
+
"Download has been in progress for " +
|
|
2139
|
+
elapsed +
|
|
2140
|
+
" ms, exceeding timeout of " +
|
|
2141
|
+
DOWNLOAD_TIMEOUT_MS +
|
|
2142
|
+
" ms. Clearing stuck state."
|
|
2143
|
+
);
|
|
2144
|
+
this.backgroundDownloadTask = null;
|
|
2145
|
+
this.downloadStartTimeMs = 0;
|
|
2146
|
+
return false; // Now it's not stuck anymore, caller can proceed
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
return true;
|
|
2151
|
+
}
|
|
2152
|
+
|
|
1777
2153
|
private Thread backgroundDownload() {
|
|
1778
2154
|
final boolean plannedDirectUpdate = this.shouldUseDirectUpdate();
|
|
1779
2155
|
final boolean initialDirectUpdateAllowed = this.isDirectUpdateCurrentlyAllowed(plannedDirectUpdate);
|
|
1780
|
-
this.implementation.directUpdate = initialDirectUpdateAllowed;
|
|
1781
2156
|
final String messageUpdate = initialDirectUpdateAllowed
|
|
1782
2157
|
? "Update will occur now."
|
|
1783
2158
|
: "Update will occur next time app moves to background.";
|
|
1784
|
-
|
|
2159
|
+
Thread newTask = startNewThread(() -> {
|
|
1785
2160
|
// Wait for cleanup to complete before starting download
|
|
1786
2161
|
waitForCleanupIfNeeded();
|
|
1787
2162
|
logger.info("Check for update via: " + CapacitorUpdaterPlugin.this.updateUrl);
|
|
1788
2163
|
try {
|
|
1789
2164
|
CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, null, (res) -> {
|
|
1790
|
-
JSObject jsRes = mapToJSObject(res);
|
|
2165
|
+
JSObject jsRes = InternalUtils.mapToJSObject(res);
|
|
1791
2166
|
final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
1792
2167
|
|
|
1793
2168
|
// Handle network errors and other failures first
|
|
1794
|
-
if (jsRes.has("error")) {
|
|
1795
|
-
String error = jsRes.getString("error");
|
|
2169
|
+
if (jsRes.has("error") || jsRes.has("kind")) {
|
|
2170
|
+
String error = jsRes.has("error") ? jsRes.getString("error") : "";
|
|
1796
2171
|
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : "server did not provide a message";
|
|
1797
2172
|
int statusCode = jsRes.has("statusCode") ? jsRes.optInt("statusCode", 0) : 0;
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
logger.error(
|
|
1801
|
-
"getLatest failed with error: " + error + ", message: " + errorMessage + ", statusCode: " + statusCode
|
|
1802
|
-
);
|
|
2173
|
+
String kind = CapacitorUpdaterPlugin.this.getUpdateResponseKind(jsRes.has("kind") ? jsRes.getString("kind") : null);
|
|
1803
2174
|
String latestVersion = jsRes.has("version") ? jsRes.getString("version") : current.getVersionName();
|
|
2175
|
+
CapacitorUpdaterPlugin.this.notifyUpdateCheckResult(kind, error, errorMessage, statusCode, latestVersion, current);
|
|
2176
|
+
|
|
2177
|
+
if ("up_to_date".equals(kind)) {
|
|
2178
|
+
logger.info("No new version available");
|
|
2179
|
+
} else if ("blocked".equals(kind)) {
|
|
2180
|
+
logger.info("Update check blocked with error: " + error);
|
|
2181
|
+
} else {
|
|
2182
|
+
logger.error(
|
|
2183
|
+
"getLatest failed with error: " + error + ", message: " + errorMessage + ", statusCode: " + statusCode
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
1804
2186
|
|
|
2187
|
+
boolean isFailure = "failed".equals(kind);
|
|
1805
2188
|
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1806
2189
|
errorMessage,
|
|
1807
2190
|
latestVersion,
|
|
1808
2191
|
current,
|
|
1809
|
-
|
|
2192
|
+
isFailure,
|
|
1810
2193
|
plannedDirectUpdate,
|
|
1811
2194
|
"download_fail",
|
|
1812
2195
|
"downloadFailed",
|
|
1813
|
-
|
|
2196
|
+
isFailure
|
|
1814
2197
|
);
|
|
1815
2198
|
return;
|
|
1816
2199
|
}
|
|
1817
|
-
|
|
1818
2200
|
try {
|
|
1819
2201
|
final String latestVersionName = jsRes.getString("version");
|
|
1820
2202
|
|
|
@@ -1825,7 +2207,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1825
2207
|
);
|
|
1826
2208
|
if (directUpdateAllowedNow) {
|
|
1827
2209
|
logger.info("Direct update to builtin version");
|
|
1828
|
-
this._reset(false);
|
|
2210
|
+
this._reset(false, false);
|
|
1829
2211
|
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1830
2212
|
"Updated to builtin version",
|
|
1831
2213
|
latestVersionName,
|
|
@@ -1845,7 +2227,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1845
2227
|
"Next update will be to builtin version",
|
|
1846
2228
|
latestVersionName,
|
|
1847
2229
|
current,
|
|
1848
|
-
false
|
|
2230
|
+
false,
|
|
2231
|
+
plannedDirectUpdate
|
|
1849
2232
|
);
|
|
1850
2233
|
}
|
|
1851
2234
|
return;
|
|
@@ -1869,7 +2252,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1869
2252
|
final BundleInfo latest = CapacitorUpdaterPlugin.this.implementation.getBundleInfoByName(latestVersionName);
|
|
1870
2253
|
if (latest != null) {
|
|
1871
2254
|
final JSObject ret = new JSObject();
|
|
1872
|
-
ret.put("bundle", mapToJSObject(latest.toJSONMap()));
|
|
2255
|
+
ret.put("bundle", InternalUtils.mapToJSObject(latest.toJSONMap()));
|
|
1873
2256
|
if (latest.isErrorStatus()) {
|
|
1874
2257
|
logger.error("Latest bundle already exists, and is in error state. Aborting update.");
|
|
1875
2258
|
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
@@ -1881,7 +2264,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1881
2264
|
);
|
|
1882
2265
|
return;
|
|
1883
2266
|
}
|
|
1884
|
-
if (latest.isDownloaded()) {
|
|
2267
|
+
if (latest.isDownloaded() && BundleStatus.DOWNLOADING != latest.getStatus()) {
|
|
1885
2268
|
logger.info("Latest bundle already exists and download is NOT required. " + messageUpdate);
|
|
1886
2269
|
final boolean directUpdateAllowedNow = CapacitorUpdaterPlugin.this.isDirectUpdateCurrentlyAllowed(
|
|
1887
2270
|
plannedDirectUpdate
|
|
@@ -1902,15 +2285,26 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1902
2285
|
);
|
|
1903
2286
|
return;
|
|
1904
2287
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
2288
|
+
if (
|
|
2289
|
+
CapacitorUpdaterPlugin.this.implementation.set(latest) && CapacitorUpdaterPlugin.this._reload()
|
|
2290
|
+
) {
|
|
2291
|
+
CapacitorUpdaterPlugin.this.notifyBundleSet(latest);
|
|
2292
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
2293
|
+
"Update installed",
|
|
2294
|
+
latestVersionName,
|
|
2295
|
+
latest,
|
|
2296
|
+
false,
|
|
2297
|
+
true
|
|
2298
|
+
);
|
|
2299
|
+
} else {
|
|
2300
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
2301
|
+
"Update install failed",
|
|
2302
|
+
latestVersionName,
|
|
2303
|
+
latest,
|
|
2304
|
+
true,
|
|
2305
|
+
true
|
|
2306
|
+
);
|
|
2307
|
+
}
|
|
1914
2308
|
} else {
|
|
1915
2309
|
if (plannedDirectUpdate && !directUpdateAllowedNow) {
|
|
1916
2310
|
logger.info(
|
|
@@ -1923,7 +2317,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1923
2317
|
"update downloaded, will install next background",
|
|
1924
2318
|
latestVersionName,
|
|
1925
2319
|
latest,
|
|
1926
|
-
false
|
|
2320
|
+
false,
|
|
2321
|
+
plannedDirectUpdate
|
|
1927
2322
|
);
|
|
1928
2323
|
}
|
|
1929
2324
|
return;
|
|
@@ -1940,6 +2335,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1940
2335
|
}
|
|
1941
2336
|
}
|
|
1942
2337
|
}
|
|
2338
|
+
final boolean retryingInFlightDownload =
|
|
2339
|
+
latest != null &&
|
|
2340
|
+
BundleStatus.DOWNLOADING == latest.getStatus() &&
|
|
2341
|
+
CapacitorUpdaterPlugin.this.isVersionDownloadInProgress(latest.getVersionName());
|
|
2342
|
+
CapacitorUpdaterPlugin.this.consumeOnLaunchDirectUpdateAttempt(plannedDirectUpdate);
|
|
2343
|
+
CapacitorUpdaterPlugin.this.implementation.directUpdate = retryingInFlightDownload
|
|
2344
|
+
? Boolean.TRUE.equals(CapacitorUpdaterPlugin.this.implementation.directUpdate) || initialDirectUpdateAllowed
|
|
2345
|
+
: initialDirectUpdateAllowed;
|
|
1943
2346
|
startNewThread(() -> {
|
|
1944
2347
|
try {
|
|
1945
2348
|
logger.info(
|
|
@@ -1988,7 +2391,13 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1988
2391
|
});
|
|
1989
2392
|
} else {
|
|
1990
2393
|
logger.info("No need to update, " + current.getId() + " is the latest bundle.");
|
|
1991
|
-
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
2394
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
2395
|
+
"No need to update",
|
|
2396
|
+
latestVersionName,
|
|
2397
|
+
current,
|
|
2398
|
+
false,
|
|
2399
|
+
plannedDirectUpdate
|
|
2400
|
+
);
|
|
1992
2401
|
}
|
|
1993
2402
|
} catch (final Exception e) {
|
|
1994
2403
|
logger.error("error in update check " + e.getMessage());
|
|
@@ -2013,6 +2422,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2013
2422
|
);
|
|
2014
2423
|
}
|
|
2015
2424
|
});
|
|
2425
|
+
this.backgroundDownloadTask = newTask;
|
|
2426
|
+
this.downloadStartTimeMs = System.currentTimeMillis();
|
|
2427
|
+
return newTask;
|
|
2016
2428
|
}
|
|
2017
2429
|
|
|
2018
2430
|
private void installNext() {
|
|
@@ -2029,15 +2441,18 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2029
2441
|
if (next != null && !next.isErrorStatus() && !next.getId().equals(current.getId())) {
|
|
2030
2442
|
// There is a next bundle waiting for activation
|
|
2031
2443
|
logger.debug("Next bundle is: " + next.getVersionName());
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2444
|
+
startNewThread(() -> {
|
|
2445
|
+
if (this.implementation.set(next) && this._reload()) {
|
|
2446
|
+
logger.info("Updated to bundle: " + next.getVersionName());
|
|
2447
|
+
this.notifyBundleSet(next);
|
|
2448
|
+
this.implementation.setNextBundle(null);
|
|
2449
|
+
} else {
|
|
2450
|
+
logger.error("Update to bundle: " + next.getVersionName() + " Failed!");
|
|
2451
|
+
}
|
|
2452
|
+
});
|
|
2038
2453
|
}
|
|
2039
2454
|
} catch (final Exception e) {
|
|
2040
|
-
logger.error("Error during
|
|
2455
|
+
logger.error("Error during installNext " + e);
|
|
2041
2456
|
}
|
|
2042
2457
|
}
|
|
2043
2458
|
|
|
@@ -2055,12 +2470,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2055
2470
|
logger.error("notifyAppReady was not called, roll back current bundle: " + current.getId());
|
|
2056
2471
|
logger.info("Did you forget to call 'notifyAppReady()' in your Capacitor App code?");
|
|
2057
2472
|
final JSObject ret = new JSObject();
|
|
2058
|
-
ret.put("bundle", mapToJSObject(current.toJSONMap()));
|
|
2473
|
+
ret.put("bundle", InternalUtils.mapToJSObject(current.toJSONMap()));
|
|
2059
2474
|
this.persistLastFailedBundle(current);
|
|
2060
2475
|
this.notifyListeners("updateFailed", ret);
|
|
2061
2476
|
this.implementation.sendStats("update_fail", current.getVersionName());
|
|
2062
2477
|
this.implementation.setError(current);
|
|
2063
|
-
this.
|
|
2478
|
+
this.performReset(true, false, true);
|
|
2064
2479
|
if (CapacitorUpdaterPlugin.this.autoDeleteFailed && !current.isBuiltin()) {
|
|
2065
2480
|
logger.info("Deleting failing bundle: " + current.getVersionName());
|
|
2066
2481
|
try {
|
|
@@ -2079,11 +2494,17 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2079
2494
|
|
|
2080
2495
|
private class DeferredNotifyAppReadyCheck implements Runnable {
|
|
2081
2496
|
|
|
2497
|
+
private final long waitTimeMs;
|
|
2498
|
+
|
|
2499
|
+
DeferredNotifyAppReadyCheck(final long waitTimeMs) {
|
|
2500
|
+
this.waitTimeMs = waitTimeMs;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2082
2503
|
@Override
|
|
2083
2504
|
public void run() {
|
|
2084
2505
|
try {
|
|
2085
|
-
logger.info("Wait for " +
|
|
2086
|
-
Thread.sleep(
|
|
2506
|
+
logger.info("Wait for " + this.waitTimeMs + "ms, then check for notifyAppReady");
|
|
2507
|
+
Thread.sleep(this.waitTimeMs);
|
|
2087
2508
|
CapacitorUpdaterPlugin.this.checkRevert();
|
|
2088
2509
|
CapacitorUpdaterPlugin.this.appReadyCheck = null;
|
|
2089
2510
|
} catch (final InterruptedException e) {
|
|
@@ -2093,16 +2514,26 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2093
2514
|
}
|
|
2094
2515
|
|
|
2095
2516
|
public void appMovedToForeground() {
|
|
2517
|
+
// Ensure activity reference is up-to-date before proceeding
|
|
2518
|
+
// This is critical for callbacks that may be invoked during background operations
|
|
2519
|
+
try {
|
|
2520
|
+
Activity currentActivity = this.getActivity();
|
|
2521
|
+
if (currentActivity != null) {
|
|
2522
|
+
CapacitorUpdaterPlugin.this.implementation.activity = currentActivity;
|
|
2523
|
+
} else {
|
|
2524
|
+
logger.warn("appMovedToForeground: Activity is null, operations may be limited");
|
|
2525
|
+
}
|
|
2526
|
+
} catch (Exception e) {
|
|
2527
|
+
logger.error("appMovedToForeground: Failed to update activity reference: " + e.getMessage());
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2096
2530
|
final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
2097
2531
|
CapacitorUpdaterPlugin.this.implementation.sendStats("app_moved_to_foreground", current.getVersionName());
|
|
2098
2532
|
this.delayUpdateUtils.checkCancelDelay(DelayUpdateUtils.CancelDelaySource.FOREGROUND);
|
|
2099
2533
|
this.delayUpdateUtils.unsetBackgroundTimestamp();
|
|
2100
2534
|
|
|
2101
|
-
if (
|
|
2102
|
-
|
|
2103
|
-
(this.backgroundDownloadTask == null || !this.backgroundDownloadTask.isAlive())
|
|
2104
|
-
) {
|
|
2105
|
-
this.backgroundDownloadTask = this.backgroundDownload();
|
|
2535
|
+
if (CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() && !this.isDownloadStuckOrTimedOut()) {
|
|
2536
|
+
this.backgroundDownload();
|
|
2106
2537
|
} else {
|
|
2107
2538
|
final CapConfig config = CapConfig.loadDefault(this.getActivity());
|
|
2108
2539
|
String serverUrl = config.getServerUrl();
|
|
@@ -2119,6 +2550,18 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2119
2550
|
// Reset timeout flag at start of each background cycle
|
|
2120
2551
|
this.autoSplashscreenTimedOut = false;
|
|
2121
2552
|
|
|
2553
|
+
// Ensure activity reference is up-to-date before proceeding
|
|
2554
|
+
try {
|
|
2555
|
+
Activity currentActivity = this.getActivity();
|
|
2556
|
+
if (currentActivity != null) {
|
|
2557
|
+
CapacitorUpdaterPlugin.this.implementation.activity = currentActivity;
|
|
2558
|
+
} else {
|
|
2559
|
+
logger.warn("appMovedToBackground: Activity is null, operations may be limited");
|
|
2560
|
+
}
|
|
2561
|
+
} catch (Exception e) {
|
|
2562
|
+
logger.error("appMovedToBackground: Failed to update activity reference: " + e.getMessage());
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2122
2565
|
final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
2123
2566
|
|
|
2124
2567
|
// Show splashscreen FIRST, before any other background work to ensure launcher shows it
|
|
@@ -2307,6 +2750,32 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2307
2750
|
}
|
|
2308
2751
|
}
|
|
2309
2752
|
|
|
2753
|
+
@PluginMethod
|
|
2754
|
+
public void setShakeChannelSelector(final PluginCall call) {
|
|
2755
|
+
final Boolean enabled = call.getBoolean("enabled");
|
|
2756
|
+
if (enabled == null) {
|
|
2757
|
+
logger.error("setShakeChannelSelector called without enabled parameter");
|
|
2758
|
+
call.reject("setShakeChannelSelector called without enabled parameter");
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
this.shakeChannelSelectorEnabled = enabled;
|
|
2763
|
+
logger.info("Shake channel selector " + (enabled ? "enabled" : "disabled"));
|
|
2764
|
+
call.resolve();
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
@PluginMethod
|
|
2768
|
+
public void isShakeChannelSelectorEnabled(final PluginCall call) {
|
|
2769
|
+
try {
|
|
2770
|
+
final JSObject ret = new JSObject();
|
|
2771
|
+
ret.put("enabled", this.shakeChannelSelectorEnabled);
|
|
2772
|
+
call.resolve(ret);
|
|
2773
|
+
} catch (final Exception e) {
|
|
2774
|
+
logger.error("Could not get shake channel selector status " + e.getMessage());
|
|
2775
|
+
call.reject("Could not get shake channel selector status", e);
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2310
2779
|
@PluginMethod
|
|
2311
2780
|
public void getAppId(final PluginCall call) {
|
|
2312
2781
|
try {
|
|
@@ -2388,9 +2857,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2388
2857
|
|
|
2389
2858
|
JSObject result = new JSObject();
|
|
2390
2859
|
try {
|
|
2391
|
-
PackageInfo pInfo =
|
|
2860
|
+
PackageInfo pInfo = getCurrentPackageInfo();
|
|
2392
2861
|
result.put("currentVersionName", pInfo.versionName);
|
|
2393
|
-
result.put("currentVersionCode",
|
|
2862
|
+
result.put("currentVersionCode", getVersionCode(pInfo));
|
|
2394
2863
|
} catch (PackageManager.NameNotFoundException e) {
|
|
2395
2864
|
result.put("currentVersionName", "0.0.0");
|
|
2396
2865
|
result.put("currentVersionCode", "0");
|