@capgo/capacitor-updater 8.0.0 → 8.1.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/CapgoCapacitorUpdater.podspec +7 -5
- package/Package.swift +37 -0
- package/README.md +1461 -231
- package/android/build.gradle +29 -12
- package/android/proguard-rules.pro +45 -0
- package/android/src/main/AndroidManifest.xml +0 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +223 -195
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +23 -23
- package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +13 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +2159 -1234
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +1507 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +330 -121
- package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +28 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +43 -49
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUntilNext.java +4 -4
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +260 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +221 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +808 -117
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +156 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +32 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/Logger.java +338 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +72 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +169 -0
- package/dist/docs.json +2187 -625
- package/dist/esm/definitions.d.ts +1286 -249
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/history.d.ts +1 -0
- package/dist/esm/history.js +283 -0
- package/dist/esm/history.js.map +1 -0
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +5 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +36 -41
- package/dist/esm/web.js +94 -35
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +376 -35
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +376 -35
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +69 -0
- package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +55 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleInfo.swift +37 -10
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleStatus.swift +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +1605 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +1526 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +267 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +220 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +311 -0
- package/ios/Sources/CapacitorUpdaterPlugin/Logger.swift +310 -0
- package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +274 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +112 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/UserDefaultsExtension.swift +0 -2
- package/package.json +41 -35
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +0 -1130
- package/ios/Plugin/CapacitorUpdater.swift +0 -858
- package/ios/Plugin/CapacitorUpdaterPlugin.h +0 -10
- package/ios/Plugin/CapacitorUpdaterPlugin.m +0 -27
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +0 -675
- package/ios/Plugin/CryptoCipher.swift +0 -240
- /package/{LICENCE → LICENSE} +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayCondition.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayUntilNext.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/Info.plist +0 -0
|
@@ -8,1300 +8,2225 @@ package ee.forgr.capacitor_updater;
|
|
|
8
8
|
|
|
9
9
|
import android.app.Activity;
|
|
10
10
|
import android.app.ActivityManager;
|
|
11
|
-
import android.app.Application;
|
|
12
11
|
import android.content.Context;
|
|
13
12
|
import android.content.SharedPreferences;
|
|
14
13
|
import android.content.pm.PackageInfo;
|
|
15
14
|
import android.content.pm.PackageManager;
|
|
15
|
+
import android.graphics.Color;
|
|
16
16
|
import android.os.Build;
|
|
17
|
-
import android.os.
|
|
18
|
-
import android.
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import
|
|
17
|
+
import android.os.Handler;
|
|
18
|
+
import android.os.Looper;
|
|
19
|
+
import android.view.Gravity;
|
|
20
|
+
import android.view.View;
|
|
21
|
+
import android.view.ViewGroup;
|
|
22
|
+
import android.widget.FrameLayout;
|
|
23
|
+
import android.widget.ProgressBar;
|
|
22
24
|
import com.getcapacitor.CapConfig;
|
|
23
25
|
import com.getcapacitor.JSArray;
|
|
24
26
|
import com.getcapacitor.JSObject;
|
|
25
27
|
import com.getcapacitor.Plugin;
|
|
26
28
|
import com.getcapacitor.PluginCall;
|
|
29
|
+
import com.getcapacitor.PluginHandle;
|
|
27
30
|
import com.getcapacitor.PluginMethod;
|
|
28
31
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
29
32
|
import com.getcapacitor.plugin.WebView;
|
|
30
|
-
import com.google.gson.Gson;
|
|
31
|
-
import com.google.gson.reflect.TypeToken;
|
|
32
33
|
import io.github.g00fy2.versioncompare.Version;
|
|
33
34
|
import java.io.IOException;
|
|
34
|
-
import java.lang.reflect.Type;
|
|
35
35
|
import java.net.MalformedURLException;
|
|
36
36
|
import java.net.URL;
|
|
37
|
-
import java.text.SimpleDateFormat;
|
|
38
37
|
import java.util.ArrayList;
|
|
38
|
+
import java.util.Arrays;
|
|
39
39
|
import java.util.Date;
|
|
40
|
-
import java.util.
|
|
40
|
+
import java.util.HashSet;
|
|
41
41
|
import java.util.List;
|
|
42
|
+
import java.util.Map;
|
|
43
|
+
import java.util.Objects;
|
|
44
|
+
import java.util.Set;
|
|
45
|
+
import java.util.Timer;
|
|
46
|
+
import java.util.TimerTask;
|
|
42
47
|
import java.util.UUID;
|
|
48
|
+
import java.util.concurrent.Phaser;
|
|
49
|
+
import java.util.concurrent.Semaphore;
|
|
50
|
+
import java.util.concurrent.TimeUnit;
|
|
51
|
+
import java.util.concurrent.TimeoutException;
|
|
52
|
+
import java.util.concurrent.atomic.AtomicReference;
|
|
53
|
+
// Removed OkHttpClient and Protocol imports - using shared client in DownloadService instead
|
|
54
|
+
import org.json.JSONArray;
|
|
43
55
|
import org.json.JSONException;
|
|
56
|
+
import org.json.JSONObject;
|
|
44
57
|
|
|
45
58
|
@CapacitorPlugin(name = "CapacitorUpdater")
|
|
46
|
-
public class CapacitorUpdaterPlugin
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"https://
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
59
|
+
public class CapacitorUpdaterPlugin extends Plugin {
|
|
60
|
+
|
|
61
|
+
private final Logger logger = new Logger("CapgoUpdater");
|
|
62
|
+
|
|
63
|
+
private static final String updateUrlDefault = "https://plugin.capgo.app/updates";
|
|
64
|
+
private static final String statsUrlDefault = "https://plugin.capgo.app/stats";
|
|
65
|
+
private static final String channelUrlDefault = "https://plugin.capgo.app/channel_self";
|
|
66
|
+
private static final String KEEP_URL_FLAG_KEY = "__capgo_keep_url_path_after_reload";
|
|
67
|
+
private static final String CUSTOM_ID_PREF_KEY = "CapacitorUpdater.customId";
|
|
68
|
+
private static final String UPDATE_URL_PREF_KEY = "CapacitorUpdater.updateUrl";
|
|
69
|
+
private static final String STATS_URL_PREF_KEY = "CapacitorUpdater.statsUrl";
|
|
70
|
+
private static final String CHANNEL_URL_PREF_KEY = "CapacitorUpdater.channelUrl";
|
|
71
|
+
private static final String DEFAULT_CHANNEL_PREF_KEY = "CapacitorUpdater.defaultChannel";
|
|
72
|
+
private static final String[] BREAKING_EVENT_NAMES = { "breakingAvailable", "majorAvailable" };
|
|
73
|
+
private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
|
|
74
|
+
|
|
75
|
+
private final String pluginVersion = "8.0.0";
|
|
76
|
+
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
77
|
+
|
|
78
|
+
private SharedPreferences.Editor editor;
|
|
79
|
+
private SharedPreferences prefs;
|
|
80
|
+
protected CapgoUpdater implementation;
|
|
81
|
+
private Boolean persistCustomId = false;
|
|
82
|
+
private Boolean persistModifyUrl = false;
|
|
83
|
+
|
|
84
|
+
private Integer appReadyTimeout = 10000;
|
|
85
|
+
private Integer periodCheckDelay = 0;
|
|
86
|
+
private Boolean autoDeleteFailed = true;
|
|
87
|
+
private Boolean autoDeletePrevious = true;
|
|
88
|
+
private Boolean autoUpdate = false;
|
|
89
|
+
private String updateUrl = "";
|
|
90
|
+
private Version currentVersionNative;
|
|
91
|
+
private String currentBuildVersion;
|
|
92
|
+
private Thread backgroundTask;
|
|
93
|
+
private Boolean taskRunning = false;
|
|
94
|
+
private Boolean keepUrlPathAfterReload = false;
|
|
95
|
+
private Boolean autoSplashscreen = false;
|
|
96
|
+
private Boolean autoSplashscreenLoader = false;
|
|
97
|
+
private Integer autoSplashscreenTimeout = 10000;
|
|
98
|
+
private Boolean autoSplashscreenTimedOut = false;
|
|
99
|
+
private String directUpdateMode = "false";
|
|
100
|
+
private Boolean wasRecentlyInstalledOrUpdated = false;
|
|
101
|
+
private Boolean onLaunchDirectUpdateUsed = false;
|
|
102
|
+
Boolean shakeMenuEnabled = false;
|
|
103
|
+
private Boolean allowManualBundleError = false;
|
|
104
|
+
private Boolean allowSetDefaultChannel = true;
|
|
105
|
+
|
|
106
|
+
private Boolean isPreviousMainActivity = true;
|
|
107
|
+
|
|
108
|
+
private volatile Thread backgroundDownloadTask;
|
|
109
|
+
private volatile Thread appReadyCheck;
|
|
110
|
+
|
|
111
|
+
// private static final CountDownLatch semaphoreReady = new CountDownLatch(1);
|
|
112
|
+
private static final Phaser semaphoreReady = new Phaser(1);
|
|
113
|
+
|
|
114
|
+
private int lastNotifiedStatPercent = 0;
|
|
115
|
+
|
|
116
|
+
private DelayUpdateUtils delayUpdateUtils;
|
|
117
|
+
|
|
118
|
+
private ShakeMenu shakeMenu;
|
|
119
|
+
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
120
|
+
private FrameLayout splashscreenLoaderOverlay;
|
|
121
|
+
private Runnable splashscreenTimeoutRunnable;
|
|
122
|
+
|
|
123
|
+
private void notifyBreakingEvents(final String version) {
|
|
124
|
+
if (version == null || version.isEmpty()) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
for (final String eventName : BREAKING_EVENT_NAMES) {
|
|
128
|
+
final JSObject payload = new JSObject();
|
|
129
|
+
payload.put("version", version);
|
|
130
|
+
CapacitorUpdaterPlugin.this.notifyListeners(eventName, payload);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private JSObject mapToJSObject(Map<String, Object> map) {
|
|
135
|
+
JSObject jsObject = new JSObject();
|
|
136
|
+
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
|
137
|
+
jsObject.put(entry.getKey(), entry.getValue());
|
|
138
|
+
}
|
|
139
|
+
return jsObject;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private void persistLastFailedBundle(BundleInfo bundle) {
|
|
143
|
+
if (this.prefs == null) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
final SharedPreferences.Editor localEditor = this.prefs.edit();
|
|
147
|
+
if (bundle == null) {
|
|
148
|
+
localEditor.remove(LAST_FAILED_BUNDLE_PREF_KEY);
|
|
149
|
+
} else {
|
|
150
|
+
final JSONObject json = new JSONObject(bundle.toJSONMap());
|
|
151
|
+
localEditor.putString(LAST_FAILED_BUNDLE_PREF_KEY, json.toString());
|
|
152
|
+
}
|
|
153
|
+
localEditor.apply();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private BundleInfo readLastFailedBundle() {
|
|
157
|
+
if (this.prefs == null) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
final String raw = this.prefs.getString(LAST_FAILED_BUNDLE_PREF_KEY, null);
|
|
161
|
+
if (raw == null || raw.trim().isEmpty()) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
return BundleInfo.fromJSON(raw);
|
|
166
|
+
} catch (final JSONException e) {
|
|
167
|
+
logger.error("Failed to parse failed bundle info: " + e.getMessage());
|
|
168
|
+
this.persistLastFailedBundle(null);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public Thread startNewThread(final Runnable function, Number waitTime) {
|
|
174
|
+
Thread bgTask = new Thread(() -> {
|
|
175
|
+
try {
|
|
176
|
+
if (waitTime.longValue() > 0) {
|
|
177
|
+
Thread.sleep(waitTime.longValue());
|
|
178
|
+
}
|
|
179
|
+
function.run();
|
|
180
|
+
} catch (Exception e) {
|
|
181
|
+
e.printStackTrace();
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
bgTask.start();
|
|
185
|
+
return bgTask;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public Thread startNewThread(final Runnable function) {
|
|
189
|
+
return startNewThread(function, 0);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@Override
|
|
193
|
+
public void load() {
|
|
194
|
+
super.load();
|
|
195
|
+
this.prefs = this.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
|
|
196
|
+
this.editor = this.prefs.edit();
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
this.implementation = new CapgoUpdater(logger) {
|
|
200
|
+
@Override
|
|
201
|
+
public void notifyDownload(final String id, final int percent) {
|
|
202
|
+
activity.runOnUiThread(() -> {
|
|
203
|
+
CapacitorUpdaterPlugin.this.notifyDownload(id, percent);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@Override
|
|
208
|
+
public void directUpdateFinish(final BundleInfo latest) {
|
|
209
|
+
activity.runOnUiThread(() -> {
|
|
210
|
+
CapacitorUpdaterPlugin.this.directUpdateFinish(latest);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@Override
|
|
215
|
+
public void notifyListeners(final String id, final Map<String, Object> res) {
|
|
216
|
+
activity.runOnUiThread(() -> {
|
|
217
|
+
CapacitorUpdaterPlugin.this.notifyListeners(id, CapacitorUpdaterPlugin.this.mapToJSObject(res));
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
final PackageInfo pInfo = this.getContext().getPackageManager().getPackageInfo(this.getContext().getPackageName(), 0);
|
|
222
|
+
this.implementation.activity = this.getActivity();
|
|
223
|
+
this.implementation.versionBuild = this.getConfig().getString("version", pInfo.versionName);
|
|
224
|
+
this.implementation.CAP_SERVER_PATH = WebView.CAP_SERVER_PATH;
|
|
225
|
+
this.implementation.pluginVersion = this.pluginVersion;
|
|
226
|
+
this.implementation.versionCode = Integer.toString(pInfo.versionCode);
|
|
227
|
+
// Removed unused OkHttpClient creation - using shared client in DownloadService instead
|
|
228
|
+
// Handle directUpdate configuration - support string values and backward compatibility
|
|
229
|
+
String directUpdateConfig = this.getConfig().getString("directUpdate", null);
|
|
230
|
+
if (directUpdateConfig != null) {
|
|
231
|
+
// Handle backward compatibility for boolean true
|
|
232
|
+
if (directUpdateConfig.equals("true")) {
|
|
233
|
+
this.directUpdateMode = "always";
|
|
234
|
+
this.implementation.directUpdate = true;
|
|
235
|
+
} else {
|
|
236
|
+
this.directUpdateMode = directUpdateConfig;
|
|
237
|
+
this.implementation.directUpdate =
|
|
238
|
+
directUpdateConfig.equals("always") ||
|
|
239
|
+
directUpdateConfig.equals("atInstall") ||
|
|
240
|
+
directUpdateConfig.equals("onLaunch");
|
|
241
|
+
// Validate directUpdate value
|
|
242
|
+
if (
|
|
243
|
+
!directUpdateConfig.equals("false") &&
|
|
244
|
+
!directUpdateConfig.equals("always") &&
|
|
245
|
+
!directUpdateConfig.equals("atInstall") &&
|
|
246
|
+
!directUpdateConfig.equals("onLaunch")
|
|
247
|
+
) {
|
|
248
|
+
logger.error(
|
|
249
|
+
"Invalid directUpdate value: \"" +
|
|
250
|
+
directUpdateConfig +
|
|
251
|
+
"\". Supported values are: \"false\", \"true\", \"always\", \"atInstall\", \"onLaunch\". Defaulting to \"false\"."
|
|
252
|
+
);
|
|
253
|
+
this.directUpdateMode = "false";
|
|
254
|
+
this.implementation.directUpdate = false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
Boolean directUpdateBool = this.getConfig().getBoolean("directUpdate", false);
|
|
259
|
+
if (directUpdateBool) {
|
|
260
|
+
this.directUpdateMode = "always"; // backward compatibility: true = always
|
|
261
|
+
this.implementation.directUpdate = true;
|
|
262
|
+
} else {
|
|
263
|
+
this.directUpdateMode = "false";
|
|
264
|
+
this.implementation.directUpdate = false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
this.currentVersionNative = new Version(this.getConfig().getString("version", pInfo.versionName));
|
|
268
|
+
this.currentBuildVersion = Integer.toString(pInfo.versionCode);
|
|
269
|
+
this.delayUpdateUtils = new DelayUpdateUtils(this.prefs, this.editor, this.currentVersionNative, logger);
|
|
270
|
+
} catch (final PackageManager.NameNotFoundException e) {
|
|
271
|
+
logger.error("Error instantiating implementation " + e.getMessage());
|
|
272
|
+
return;
|
|
273
|
+
} catch (final Exception e) {
|
|
274
|
+
logger.error("Error getting current native app version " + e.getMessage());
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
boolean disableJSLogging = this.getConfig().getBoolean("disableJSLogging", false);
|
|
279
|
+
// Set the bridge in the Logger when webView is available
|
|
280
|
+
if (this.bridge != null && this.bridge.getWebView() != null && !disableJSLogging) {
|
|
281
|
+
logger.setBridge(this.bridge);
|
|
282
|
+
logger.info("WebView set successfully for logging");
|
|
283
|
+
} else {
|
|
284
|
+
logger.info("WebView not ready yet, will be set later");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Set logger for shared classes
|
|
288
|
+
CryptoCipher.setLogger(logger);
|
|
289
|
+
DownloadService.setLogger(logger);
|
|
290
|
+
DownloadWorkerManager.setLogger(logger);
|
|
291
|
+
|
|
292
|
+
final CapConfig config = CapConfig.loadDefault(this.getActivity());
|
|
293
|
+
this.implementation.appId = InternalUtils.getPackageName(getContext().getPackageManager(), getContext().getPackageName());
|
|
294
|
+
this.implementation.appId = config.getString("appId", this.implementation.appId);
|
|
295
|
+
this.implementation.appId = this.getConfig().getString("appId", this.implementation.appId);
|
|
296
|
+
if (this.implementation.appId == null || this.implementation.appId.isEmpty()) {
|
|
297
|
+
// crash the app on purpose it should not happen
|
|
298
|
+
throw new RuntimeException(
|
|
299
|
+
"appId is missing in capacitor.config.json or plugin config, and cannot be retrieved from the native app, please add it globally or in the plugin config"
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
logger.info("appId: " + implementation.appId);
|
|
303
|
+
|
|
304
|
+
this.persistCustomId = this.getConfig().getBoolean("persistCustomId", false);
|
|
305
|
+
this.persistModifyUrl = this.getConfig().getBoolean("persistModifyUrl", false);
|
|
306
|
+
this.allowSetDefaultChannel = this.getConfig().getBoolean("allowSetDefaultChannel", true);
|
|
307
|
+
this.implementation.publicKey = this.getConfig().getString("publicKey", "");
|
|
308
|
+
this.implementation.statsUrl = this.getConfig().getString("statsUrl", statsUrlDefault);
|
|
309
|
+
this.implementation.channelUrl = this.getConfig().getString("channelUrl", channelUrlDefault);
|
|
310
|
+
if (Boolean.TRUE.equals(this.persistModifyUrl)) {
|
|
311
|
+
if (this.prefs.contains(STATS_URL_PREF_KEY)) {
|
|
312
|
+
final String storedStatsUrl = this.prefs.getString(STATS_URL_PREF_KEY, this.implementation.statsUrl);
|
|
313
|
+
if (storedStatsUrl != null) {
|
|
314
|
+
this.implementation.statsUrl = storedStatsUrl;
|
|
315
|
+
logger.info("Loaded persisted statsUrl");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (this.prefs.contains(CHANNEL_URL_PREF_KEY)) {
|
|
319
|
+
final String storedChannelUrl = this.prefs.getString(CHANNEL_URL_PREF_KEY, this.implementation.channelUrl);
|
|
320
|
+
if (storedChannelUrl != null) {
|
|
321
|
+
this.implementation.channelUrl = storedChannelUrl;
|
|
322
|
+
logger.info("Loaded persisted channelUrl");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Load defaultChannel: first try from persistent storage (set via setChannel), then fall back to config
|
|
328
|
+
if (this.prefs.contains(DEFAULT_CHANNEL_PREF_KEY)) {
|
|
329
|
+
final String storedDefaultChannel = this.prefs.getString(DEFAULT_CHANNEL_PREF_KEY, "");
|
|
330
|
+
if (storedDefaultChannel != null && !storedDefaultChannel.isEmpty()) {
|
|
331
|
+
this.implementation.defaultChannel = storedDefaultChannel;
|
|
332
|
+
logger.info("Loaded persisted defaultChannel from setChannel()");
|
|
333
|
+
} else {
|
|
334
|
+
this.implementation.defaultChannel = this.getConfig().getString("defaultChannel", "");
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
this.implementation.defaultChannel = this.getConfig().getString("defaultChannel", "");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
int userValue = this.getConfig().getInt("periodCheckDelay", 0);
|
|
341
|
+
|
|
342
|
+
if (userValue >= 0 && userValue <= 600) {
|
|
343
|
+
this.periodCheckDelay = 600 * 1000;
|
|
344
|
+
} else if (userValue > 600) {
|
|
345
|
+
this.periodCheckDelay = userValue * 1000;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
this.implementation.documentsDir = this.getContext().getFilesDir();
|
|
349
|
+
this.implementation.prefs = this.prefs;
|
|
350
|
+
this.implementation.editor = this.editor;
|
|
351
|
+
this.implementation.versionOs = Build.VERSION.RELEASE;
|
|
352
|
+
// Use DeviceIdHelper to get or create device ID that persists across reinstalls
|
|
353
|
+
this.implementation.deviceID = DeviceIdHelper.getOrCreateDeviceId(this.getContext(), this.prefs);
|
|
354
|
+
|
|
355
|
+
// Update User-Agent for shared OkHttpClient with OS version
|
|
356
|
+
DownloadService.updateUserAgent(this.implementation.appId, this.pluginVersion, this.implementation.versionOs);
|
|
357
|
+
|
|
358
|
+
if (Boolean.TRUE.equals(this.persistCustomId)) {
|
|
359
|
+
final String storedCustomId = this.prefs.getString(CUSTOM_ID_PREF_KEY, "");
|
|
360
|
+
if (storedCustomId != null && !storedCustomId.isEmpty()) {
|
|
361
|
+
this.implementation.customId = storedCustomId;
|
|
362
|
+
logger.info("Loaded persisted customId");
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
logger.info("init for device " + this.implementation.deviceID);
|
|
366
|
+
logger.info("version native " + this.currentVersionNative.getOriginalString());
|
|
367
|
+
this.autoDeleteFailed = this.getConfig().getBoolean("autoDeleteFailed", true);
|
|
368
|
+
this.autoDeletePrevious = this.getConfig().getBoolean("autoDeletePrevious", true);
|
|
369
|
+
this.updateUrl = this.getConfig().getString("updateUrl", updateUrlDefault);
|
|
370
|
+
if (Boolean.TRUE.equals(this.persistModifyUrl)) {
|
|
371
|
+
if (this.prefs.contains(UPDATE_URL_PREF_KEY)) {
|
|
372
|
+
final String storedUpdateUrl = this.prefs.getString(UPDATE_URL_PREF_KEY, this.updateUrl);
|
|
373
|
+
if (storedUpdateUrl != null) {
|
|
374
|
+
this.updateUrl = storedUpdateUrl;
|
|
375
|
+
logger.info("Loaded persisted updateUrl");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
this.autoUpdate = this.getConfig().getBoolean("autoUpdate", true);
|
|
380
|
+
this.appReadyTimeout = this.getConfig().getInt("appReadyTimeout", 10000);
|
|
381
|
+
this.keepUrlPathAfterReload = this.getConfig().getBoolean("keepUrlPathAfterReload", false);
|
|
382
|
+
this.syncKeepUrlPathFlag(this.keepUrlPathAfterReload);
|
|
383
|
+
this.allowManualBundleError = this.getConfig().getBoolean("allowManualBundleError", false);
|
|
384
|
+
this.autoSplashscreen = this.getConfig().getBoolean("autoSplashscreen", false);
|
|
385
|
+
this.autoSplashscreenLoader = this.getConfig().getBoolean("autoSplashscreenLoader", false);
|
|
386
|
+
int splashscreenTimeoutValue = this.getConfig().getInt("autoSplashscreenTimeout", 10000);
|
|
387
|
+
this.autoSplashscreenTimeout = Math.max(0, splashscreenTimeoutValue);
|
|
388
|
+
this.implementation.timeout = this.getConfig().getInt("responseTimeout", 20) * 1000;
|
|
389
|
+
this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
|
|
390
|
+
boolean resetWhenUpdate = this.getConfig().getBoolean("resetWhenUpdate", true);
|
|
391
|
+
|
|
392
|
+
// Check if app was recently installed/updated BEFORE cleanupObsoleteVersions updates LatestVersionNative
|
|
393
|
+
this.wasRecentlyInstalledOrUpdated = this.checkIfRecentlyInstalledOrUpdated();
|
|
394
|
+
|
|
395
|
+
this.implementation.autoReset();
|
|
396
|
+
if (resetWhenUpdate) {
|
|
397
|
+
this.cleanupObsoleteVersions();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Check for 'kill' delay condition on app launch
|
|
401
|
+
// This handles cases where the app was killed by the system (onDestroy is not reliable)
|
|
402
|
+
this.delayUpdateUtils.checkCancelDelay(DelayUpdateUtils.CancelDelaySource.KILLED);
|
|
403
|
+
|
|
404
|
+
this.checkForUpdateAfterDelay();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private void semaphoreWait(Number waitTime) {
|
|
408
|
+
try {
|
|
409
|
+
semaphoreReady.awaitAdvanceInterruptibly(semaphoreReady.getPhase(), waitTime.longValue(), TimeUnit.SECONDS);
|
|
410
|
+
logger.info("semaphoreReady count " + semaphoreReady.getPhase());
|
|
411
|
+
} catch (InterruptedException e) {
|
|
412
|
+
logger.info("semaphoreWait InterruptedException");
|
|
413
|
+
Thread.currentThread().interrupt(); // Restore interrupted status
|
|
414
|
+
} catch (TimeoutException e) {
|
|
415
|
+
logger.error("Semaphore timeout: " + e.getMessage());
|
|
416
|
+
// Don't throw runtime exception, just log and continue
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private void semaphoreUp() {
|
|
421
|
+
logger.info("semaphoreUp");
|
|
422
|
+
semaphoreReady.register();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
private void semaphoreDown() {
|
|
426
|
+
logger.info("semaphoreDown");
|
|
427
|
+
logger.info("semaphoreDown count " + semaphoreReady.getPhase());
|
|
428
|
+
semaphoreReady.arriveAndDeregister();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private void sendReadyToJs(final BundleInfo current, final String msg) {
|
|
432
|
+
sendReadyToJs(current, msg, false);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private void sendReadyToJs(final BundleInfo current, final String msg, final boolean isDirectUpdate) {
|
|
436
|
+
logger.info("sendReadyToJs: " + msg);
|
|
437
|
+
final JSObject ret = new JSObject();
|
|
438
|
+
ret.put("bundle", mapToJSObject(current.toJSONMap()));
|
|
439
|
+
ret.put("status", msg);
|
|
440
|
+
|
|
441
|
+
// No need to wait for semaphore anymore since _reload() has already waited
|
|
442
|
+
this.notifyListeners("appReady", ret, true);
|
|
443
|
+
|
|
444
|
+
// Auto hide splashscreen if enabled
|
|
445
|
+
// We show it on background when conditions are met, so we should hide it on foreground regardless of update outcome
|
|
446
|
+
if (this.autoSplashscreen) {
|
|
447
|
+
this.hideSplashscreen();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private void hideSplashscreen() {
|
|
452
|
+
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
453
|
+
hideSplashscreenInternal();
|
|
454
|
+
} else {
|
|
455
|
+
this.mainHandler.post(this::hideSplashscreenInternal);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
private void hideSplashscreenInternal() {
|
|
460
|
+
cancelSplashscreenTimeout();
|
|
461
|
+
removeSplashscreenLoader();
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
if (getBridge() == null) {
|
|
465
|
+
logger.warn("Bridge not ready for hiding splashscreen with autoSplashscreen");
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Try to call the SplashScreen plugin directly through the bridge
|
|
470
|
+
PluginHandle splashScreenPlugin = getBridge().getPlugin("SplashScreen");
|
|
471
|
+
if (splashScreenPlugin != null) {
|
|
472
|
+
try {
|
|
473
|
+
// Create a plugin call for the hide method using reflection to access private msgHandler
|
|
474
|
+
JSObject options = new JSObject();
|
|
475
|
+
java.lang.reflect.Field msgHandlerField = getBridge().getClass().getDeclaredField("msgHandler");
|
|
476
|
+
msgHandlerField.setAccessible(true);
|
|
477
|
+
Object msgHandler = msgHandlerField.get(getBridge());
|
|
478
|
+
|
|
479
|
+
PluginCall call = new PluginCall(
|
|
480
|
+
(com.getcapacitor.MessageHandler) msgHandler,
|
|
481
|
+
"SplashScreen",
|
|
482
|
+
"FAKE_CALLBACK_ID_HIDE",
|
|
483
|
+
"hide",
|
|
484
|
+
options
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
// Call the hide method directly
|
|
488
|
+
splashScreenPlugin.invoke("hide", call);
|
|
489
|
+
logger.info("Splashscreen hidden automatically via direct plugin call");
|
|
490
|
+
} catch (Exception e) {
|
|
491
|
+
logger.error("Failed to call SplashScreen hide method: " + e.getMessage());
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
logger.warn("autoSplashscreen: SplashScreen plugin not found. Install @capacitor/splash-screen plugin.");
|
|
495
|
+
}
|
|
496
|
+
} catch (Exception e) {
|
|
497
|
+
logger.error(
|
|
498
|
+
"Error hiding splashscreen with autoSplashscreen: " +
|
|
499
|
+
e.getMessage() +
|
|
500
|
+
". Make sure @capacitor/splash-screen plugin is installed and configured."
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
private void showSplashscreen() {
|
|
506
|
+
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
507
|
+
showSplashscreenNow();
|
|
508
|
+
} else {
|
|
509
|
+
this.mainHandler.post(this::showSplashscreenNow);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private void showSplashscreenNow() {
|
|
514
|
+
cancelSplashscreenTimeout();
|
|
515
|
+
this.autoSplashscreenTimedOut = false;
|
|
516
|
+
|
|
517
|
+
try {
|
|
518
|
+
if (getBridge() == null) {
|
|
519
|
+
logger.warn("Bridge not ready for showing splashscreen with autoSplashscreen");
|
|
520
|
+
} else {
|
|
521
|
+
PluginHandle splashScreenPlugin = getBridge().getPlugin("SplashScreen");
|
|
522
|
+
if (splashScreenPlugin != null) {
|
|
523
|
+
JSObject options = new JSObject();
|
|
524
|
+
java.lang.reflect.Field msgHandlerField = getBridge().getClass().getDeclaredField("msgHandler");
|
|
525
|
+
msgHandlerField.setAccessible(true);
|
|
526
|
+
Object msgHandler = msgHandlerField.get(getBridge());
|
|
527
|
+
|
|
528
|
+
PluginCall call = new PluginCall(
|
|
529
|
+
(com.getcapacitor.MessageHandler) msgHandler,
|
|
530
|
+
"SplashScreen",
|
|
531
|
+
"FAKE_CALLBACK_ID_SHOW",
|
|
532
|
+
"show",
|
|
533
|
+
options
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
splashScreenPlugin.invoke("show", call);
|
|
537
|
+
logger.info("Splashscreen shown synchronously to prevent flash");
|
|
538
|
+
} else {
|
|
539
|
+
logger.warn("autoSplashscreen: SplashScreen plugin not found");
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
} catch (Exception e) {
|
|
543
|
+
logger.error("Failed to show splashscreen synchronously: " + e.getMessage());
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
addSplashscreenLoaderIfNeeded();
|
|
547
|
+
scheduleSplashscreenTimeout();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private void addSplashscreenLoaderIfNeeded() {
|
|
551
|
+
if (!Boolean.TRUE.equals(this.autoSplashscreenLoader)) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
Runnable addLoader = () -> {
|
|
556
|
+
if (this.splashscreenLoaderOverlay != null) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
Activity activity = getActivity();
|
|
561
|
+
if (activity == null) {
|
|
562
|
+
logger.warn("autoSplashscreen: Activity not available for loader overlay");
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
ProgressBar progressBar = new ProgressBar(activity);
|
|
567
|
+
progressBar.setIndeterminate(true);
|
|
568
|
+
|
|
569
|
+
FrameLayout overlay = new FrameLayout(activity);
|
|
570
|
+
overlay.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
571
|
+
overlay.setClickable(false);
|
|
572
|
+
overlay.setFocusable(false);
|
|
573
|
+
overlay.setBackgroundColor(Color.TRANSPARENT);
|
|
574
|
+
overlay.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
|
575
|
+
|
|
576
|
+
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
|
577
|
+
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
578
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
579
|
+
);
|
|
580
|
+
params.gravity = Gravity.CENTER;
|
|
581
|
+
overlay.addView(progressBar, params);
|
|
582
|
+
|
|
583
|
+
ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
|
|
584
|
+
decorView.addView(overlay);
|
|
585
|
+
|
|
586
|
+
this.splashscreenLoaderOverlay = overlay;
|
|
103
587
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
588
|
+
|
|
589
|
+
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
590
|
+
addLoader.run();
|
|
591
|
+
} else {
|
|
592
|
+
this.mainHandler.post(addLoader);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
private void removeSplashscreenLoader() {
|
|
597
|
+
Runnable removeLoader = () -> {
|
|
598
|
+
if (this.splashscreenLoaderOverlay != null) {
|
|
599
|
+
ViewGroup parent = (ViewGroup) this.splashscreenLoaderOverlay.getParent();
|
|
600
|
+
if (parent != null) {
|
|
601
|
+
parent.removeView(this.splashscreenLoaderOverlay);
|
|
602
|
+
}
|
|
603
|
+
this.splashscreenLoaderOverlay = null;
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
608
|
+
removeLoader.run();
|
|
609
|
+
} else {
|
|
610
|
+
this.mainHandler.post(removeLoader);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
private void scheduleSplashscreenTimeout() {
|
|
615
|
+
if (this.autoSplashscreenTimeout == null || this.autoSplashscreenTimeout <= 0) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
cancelSplashscreenTimeout();
|
|
620
|
+
|
|
621
|
+
this.splashscreenTimeoutRunnable = () -> {
|
|
622
|
+
logger.info("autoSplashscreen timeout reached, hiding splashscreen");
|
|
623
|
+
this.autoSplashscreenTimedOut = true;
|
|
624
|
+
this.implementation.directUpdate = false;
|
|
625
|
+
hideSplashscreen();
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
this.mainHandler.postDelayed(this.splashscreenTimeoutRunnable, this.autoSplashscreenTimeout);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
private void cancelSplashscreenTimeout() {
|
|
632
|
+
if (this.splashscreenTimeoutRunnable != null) {
|
|
633
|
+
this.mainHandler.removeCallbacks(this.splashscreenTimeoutRunnable);
|
|
634
|
+
this.splashscreenTimeoutRunnable = null;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
private boolean checkIfRecentlyInstalledOrUpdated() {
|
|
639
|
+
String currentVersion = this.currentBuildVersion;
|
|
640
|
+
String lastKnownVersion = this.prefs.getString("LatestNativeBuildVersion", "");
|
|
641
|
+
|
|
642
|
+
if (lastKnownVersion.isEmpty()) {
|
|
643
|
+
// First time running, consider it as recently installed
|
|
644
|
+
return true;
|
|
645
|
+
} else if (!lastKnownVersion.equals(currentVersion)) {
|
|
646
|
+
// Version changed, consider it as recently updated
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
private boolean shouldUseDirectUpdate() {
|
|
654
|
+
if (Boolean.TRUE.equals(this.autoSplashscreenTimedOut)) {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
switch (this.directUpdateMode) {
|
|
658
|
+
case "false":
|
|
659
|
+
return false;
|
|
660
|
+
case "always":
|
|
661
|
+
return true;
|
|
662
|
+
case "atInstall":
|
|
663
|
+
if (this.wasRecentlyInstalledOrUpdated) {
|
|
664
|
+
// Reset the flag after first use to prevent subsequent foreground events from using direct update
|
|
665
|
+
this.wasRecentlyInstalledOrUpdated = false;
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
return false;
|
|
669
|
+
case "onLaunch":
|
|
670
|
+
if (!this.onLaunchDirectUpdateUsed) {
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
return false;
|
|
674
|
+
default:
|
|
675
|
+
logger.error(
|
|
676
|
+
"Invalid directUpdateMode: \"" +
|
|
677
|
+
this.directUpdateMode +
|
|
678
|
+
"\". Supported values are: \"false\", \"always\", \"atInstall\", \"onLaunch\". Defaulting to \"false\" behavior."
|
|
679
|
+
);
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
private boolean isDirectUpdateCurrentlyAllowed(final boolean plannedDirectUpdate) {
|
|
685
|
+
return plannedDirectUpdate && !Boolean.TRUE.equals(this.autoSplashscreenTimedOut);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private void directUpdateFinish(final BundleInfo latest) {
|
|
689
|
+
if ("onLaunch".equals(this.directUpdateMode)) {
|
|
690
|
+
this.onLaunchDirectUpdateUsed = true;
|
|
691
|
+
this.implementation.directUpdate = false;
|
|
692
|
+
}
|
|
693
|
+
CapacitorUpdaterPlugin.this.implementation.set(latest);
|
|
694
|
+
CapacitorUpdaterPlugin.this._reload();
|
|
695
|
+
sendReadyToJs(latest, "update installed", true);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
private void cleanupObsoleteVersions() {
|
|
699
|
+
startNewThread(() -> {
|
|
187
700
|
try {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
701
|
+
final String previous = this.prefs.getString("LatestNativeBuildVersion", "");
|
|
702
|
+
if (!"".equals(previous) && !Objects.equals(this.currentBuildVersion, previous)) {
|
|
703
|
+
logger.info("New native build version detected: " + this.currentBuildVersion);
|
|
704
|
+
this.implementation.reset(true);
|
|
705
|
+
final List<BundleInfo> installed = this.implementation.list(false);
|
|
706
|
+
for (final BundleInfo bundle : installed) {
|
|
707
|
+
try {
|
|
708
|
+
logger.info("Deleting obsolete bundle: " + bundle.getId());
|
|
709
|
+
this.implementation.delete(bundle.getId());
|
|
710
|
+
} catch (final Exception e) {
|
|
711
|
+
logger.error("Failed to delete: " + bundle.getId() + " " + e.getMessage());
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
final List<BundleInfo> storedBundles = this.implementation.list(true);
|
|
715
|
+
final Set<String> allowedIds = new HashSet<>();
|
|
716
|
+
for (final BundleInfo info : storedBundles) {
|
|
717
|
+
if (info != null && info.getId() != null && !info.getId().isEmpty()) {
|
|
718
|
+
allowedIds.add(info.getId());
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
this.implementation.cleanupDownloadDirectories(allowedIds);
|
|
722
|
+
this.implementation.cleanupDeltaCache();
|
|
723
|
+
}
|
|
724
|
+
this.editor.putString("LatestNativeBuildVersion", this.currentBuildVersion);
|
|
725
|
+
this.editor.apply();
|
|
726
|
+
} catch (Exception e) {
|
|
727
|
+
logger.error("Error during cleanupObsoleteVersions: " + e.getMessage());
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
public void notifyDownload(final String id, final int percent) {
|
|
733
|
+
try {
|
|
734
|
+
final JSObject ret = new JSObject();
|
|
735
|
+
ret.put("percent", percent);
|
|
736
|
+
final BundleInfo bundleInfo = this.implementation.getBundleInfo(id);
|
|
737
|
+
ret.put("bundle", mapToJSObject(bundleInfo.toJSONMap()));
|
|
738
|
+
this.notifyListeners("download", ret);
|
|
739
|
+
|
|
740
|
+
if (percent == 100) {
|
|
741
|
+
final JSObject retDownloadComplete = new JSObject(ret, new String[] { "bundle" });
|
|
742
|
+
this.notifyListeners("downloadComplete", retDownloadComplete);
|
|
743
|
+
this.implementation.sendStats("download_complete", bundleInfo.getVersionName());
|
|
744
|
+
lastNotifiedStatPercent = 100;
|
|
745
|
+
} else {
|
|
746
|
+
int currentStatPercent = (percent / 10) * 10; // Round down to nearest 10
|
|
747
|
+
if (currentStatPercent > lastNotifiedStatPercent) {
|
|
748
|
+
this.implementation.sendStats("download_" + currentStatPercent, bundleInfo.getVersionName());
|
|
749
|
+
lastNotifiedStatPercent = currentStatPercent;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
} catch (final Exception e) {
|
|
753
|
+
logger.error("Could not notify listeners " + e.getMessage());
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
@PluginMethod
|
|
758
|
+
public void setUpdateUrl(final PluginCall call) {
|
|
759
|
+
if (!this.getConfig().getBoolean("allowModifyUrl", false)) {
|
|
760
|
+
logger.error("setUpdateUrl not allowed set allowModifyUrl in your config to true to allow it");
|
|
761
|
+
call.reject("setUpdateUrl not allowed");
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
final String url = call.getString("url");
|
|
765
|
+
if (url == null) {
|
|
766
|
+
logger.error("setUpdateUrl called without url");
|
|
767
|
+
call.reject("setUpdateUrl called without url");
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
this.updateUrl = url;
|
|
771
|
+
if (Boolean.TRUE.equals(this.persistModifyUrl)) {
|
|
772
|
+
this.editor.putString(UPDATE_URL_PREF_KEY, url);
|
|
773
|
+
this.editor.apply();
|
|
774
|
+
}
|
|
775
|
+
call.resolve();
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
@PluginMethod
|
|
779
|
+
public void setStatsUrl(final PluginCall call) {
|
|
780
|
+
if (!this.getConfig().getBoolean("allowModifyUrl", false)) {
|
|
781
|
+
logger.error("setStatsUrl not allowed set allowModifyUrl in your config to true to allow it");
|
|
782
|
+
call.reject("setStatsUrl not allowed");
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
final String url = call.getString("url");
|
|
786
|
+
if (url == null) {
|
|
787
|
+
logger.error("setStatsUrl called without url");
|
|
788
|
+
call.reject("setStatsUrl called without url");
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
this.implementation.statsUrl = url;
|
|
792
|
+
if (Boolean.TRUE.equals(this.persistModifyUrl)) {
|
|
793
|
+
this.editor.putString(STATS_URL_PREF_KEY, url);
|
|
794
|
+
this.editor.apply();
|
|
795
|
+
}
|
|
796
|
+
call.resolve();
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
@PluginMethod
|
|
800
|
+
public void setChannelUrl(final PluginCall call) {
|
|
801
|
+
if (!this.getConfig().getBoolean("allowModifyUrl", false)) {
|
|
802
|
+
logger.error("setChannelUrl not allowed set allowModifyUrl in your config to true to allow it");
|
|
803
|
+
call.reject("setChannelUrl not allowed");
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
final String url = call.getString("url");
|
|
807
|
+
if (url == null) {
|
|
808
|
+
logger.error("setChannelUrl called without url");
|
|
809
|
+
call.reject("setChannelUrl called without url");
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
this.implementation.channelUrl = url;
|
|
813
|
+
if (Boolean.TRUE.equals(this.persistModifyUrl)) {
|
|
814
|
+
this.editor.putString(CHANNEL_URL_PREF_KEY, url);
|
|
815
|
+
this.editor.apply();
|
|
816
|
+
}
|
|
817
|
+
call.resolve();
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
@PluginMethod
|
|
821
|
+
public void getBuiltinVersion(final PluginCall call) {
|
|
822
|
+
try {
|
|
823
|
+
final JSObject ret = new JSObject();
|
|
824
|
+
ret.put("version", this.implementation.versionBuild);
|
|
825
|
+
call.resolve(ret);
|
|
826
|
+
} catch (final Exception e) {
|
|
827
|
+
logger.error("Could not get version " + e.getMessage());
|
|
828
|
+
call.reject("Could not get version", e);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
@PluginMethod
|
|
833
|
+
public void getDeviceId(final PluginCall call) {
|
|
834
|
+
try {
|
|
835
|
+
final JSObject ret = new JSObject();
|
|
836
|
+
ret.put("deviceId", this.implementation.deviceID);
|
|
837
|
+
call.resolve(ret);
|
|
838
|
+
} catch (final Exception e) {
|
|
839
|
+
logger.error("Could not get device id " + e.getMessage());
|
|
840
|
+
call.reject("Could not get device id", e);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
@PluginMethod
|
|
845
|
+
public void setCustomId(final PluginCall call) {
|
|
846
|
+
final String customId = call.getString("customId");
|
|
847
|
+
if (customId == null) {
|
|
848
|
+
logger.error("setCustomId called without customId");
|
|
849
|
+
call.reject("setCustomId called without customId");
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
this.implementation.customId = customId;
|
|
853
|
+
if (Boolean.TRUE.equals(this.persistCustomId)) {
|
|
854
|
+
if (customId.isEmpty()) {
|
|
855
|
+
this.editor.remove(CUSTOM_ID_PREF_KEY);
|
|
856
|
+
} else {
|
|
857
|
+
this.editor.putString(CUSTOM_ID_PREF_KEY, customId);
|
|
858
|
+
}
|
|
859
|
+
this.editor.apply();
|
|
860
|
+
}
|
|
861
|
+
call.resolve();
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
@PluginMethod
|
|
865
|
+
public void getPluginVersion(final PluginCall call) {
|
|
866
|
+
try {
|
|
867
|
+
final JSObject ret = new JSObject();
|
|
868
|
+
ret.put("version", this.pluginVersion);
|
|
869
|
+
call.resolve(ret);
|
|
870
|
+
} catch (final Exception e) {
|
|
871
|
+
logger.error("Could not get plugin version " + e.getMessage());
|
|
872
|
+
call.reject("Could not get plugin version", e);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
@PluginMethod
|
|
877
|
+
public void unsetChannel(final PluginCall call) {
|
|
878
|
+
final Boolean triggerAutoUpdate = call.getBoolean("triggerAutoUpdate", false);
|
|
879
|
+
|
|
880
|
+
try {
|
|
881
|
+
logger.info("unsetChannel triggerAutoUpdate: " + triggerAutoUpdate);
|
|
882
|
+
startNewThread(() -> {
|
|
883
|
+
String configDefaultChannel = CapacitorUpdaterPlugin.this.getConfig().getString("defaultChannel", "");
|
|
884
|
+
CapacitorUpdaterPlugin.this.implementation.unsetChannel(
|
|
885
|
+
CapacitorUpdaterPlugin.this.editor,
|
|
886
|
+
DEFAULT_CHANNEL_PREF_KEY,
|
|
887
|
+
configDefaultChannel,
|
|
888
|
+
(res) -> {
|
|
889
|
+
JSObject jsRes = mapToJSObject(res);
|
|
890
|
+
if (jsRes.has("error")) {
|
|
891
|
+
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
|
|
892
|
+
String errorCode = jsRes.getString("error");
|
|
893
|
+
|
|
894
|
+
JSObject errorObj = new JSObject();
|
|
895
|
+
errorObj.put("message", errorMessage);
|
|
896
|
+
errorObj.put("error", errorCode);
|
|
897
|
+
|
|
898
|
+
call.reject(errorMessage, "UNSETCHANNEL_FAILED", null, errorObj);
|
|
899
|
+
} else {
|
|
900
|
+
if (CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() && Boolean.TRUE.equals(triggerAutoUpdate)) {
|
|
901
|
+
logger.info("Calling autoupdater after channel change!");
|
|
902
|
+
backgroundDownload();
|
|
903
|
+
}
|
|
904
|
+
call.resolve(jsRes);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
});
|
|
909
|
+
} catch (final Exception e) {
|
|
910
|
+
logger.error("Failed to unsetChannel: " + e.getMessage());
|
|
911
|
+
call.reject("Failed to unsetChannel: ", e);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
@PluginMethod
|
|
916
|
+
public void setChannel(final PluginCall call) {
|
|
917
|
+
final String channel = call.getString("channel");
|
|
918
|
+
final Boolean triggerAutoUpdate = call.getBoolean("triggerAutoUpdate", false);
|
|
919
|
+
|
|
920
|
+
if (channel == null) {
|
|
921
|
+
logger.error("setChannel called without channel");
|
|
922
|
+
JSObject errorObj = new JSObject();
|
|
923
|
+
errorObj.put("message", "setChannel called without channel");
|
|
924
|
+
errorObj.put("error", "missing_parameter");
|
|
925
|
+
call.reject("setChannel called without channel", "SETCHANNEL_INVALID_PARAMS", null, errorObj);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
try {
|
|
929
|
+
logger.info("setChannel " + channel + " triggerAutoUpdate: " + triggerAutoUpdate);
|
|
930
|
+
startNewThread(() ->
|
|
931
|
+
CapacitorUpdaterPlugin.this.implementation.setChannel(
|
|
932
|
+
channel,
|
|
933
|
+
CapacitorUpdaterPlugin.this.editor,
|
|
934
|
+
DEFAULT_CHANNEL_PREF_KEY,
|
|
935
|
+
CapacitorUpdaterPlugin.this.allowSetDefaultChannel,
|
|
936
|
+
(res) -> {
|
|
937
|
+
JSObject jsRes = mapToJSObject(res);
|
|
938
|
+
if (jsRes.has("error")) {
|
|
939
|
+
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
|
|
940
|
+
String errorCode = jsRes.getString("error");
|
|
941
|
+
|
|
942
|
+
// Fire channelPrivate event if channel doesn't allow self-assignment
|
|
943
|
+
if (
|
|
944
|
+
errorCode.contains("cannot_update_via_private_channel") ||
|
|
945
|
+
errorCode.contains("channel_self_set_not_allowed")
|
|
946
|
+
) {
|
|
947
|
+
JSObject eventData = new JSObject();
|
|
948
|
+
eventData.put("channel", channel);
|
|
949
|
+
eventData.put("message", errorMessage);
|
|
950
|
+
notifyListeners("channelPrivate", eventData);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
JSObject errorObj = new JSObject();
|
|
954
|
+
errorObj.put("message", errorMessage);
|
|
955
|
+
errorObj.put("error", errorCode);
|
|
956
|
+
|
|
957
|
+
call.reject(errorMessage, "SETCHANNEL_FAILED", null, errorObj);
|
|
958
|
+
} else {
|
|
959
|
+
if (CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() && Boolean.TRUE.equals(triggerAutoUpdate)) {
|
|
960
|
+
logger.info("Calling autoupdater after channel change!");
|
|
961
|
+
backgroundDownload();
|
|
962
|
+
}
|
|
963
|
+
call.resolve(jsRes);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
)
|
|
967
|
+
);
|
|
968
|
+
} catch (final Exception e) {
|
|
969
|
+
logger.error("Failed to setChannel: " + channel + " " + e.getMessage());
|
|
970
|
+
call.reject("Failed to setChannel: " + channel, e);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
@PluginMethod
|
|
975
|
+
public void getChannel(final PluginCall call) {
|
|
976
|
+
try {
|
|
977
|
+
logger.info("getChannel");
|
|
978
|
+
startNewThread(() ->
|
|
979
|
+
CapacitorUpdaterPlugin.this.implementation.getChannel((res) -> {
|
|
980
|
+
JSObject jsRes = mapToJSObject(res);
|
|
981
|
+
if (jsRes.has("error")) {
|
|
982
|
+
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
|
|
983
|
+
String errorCode = jsRes.getString("error");
|
|
984
|
+
|
|
985
|
+
JSObject errorObj = new JSObject();
|
|
986
|
+
errorObj.put("message", errorMessage);
|
|
987
|
+
errorObj.put("error", errorCode);
|
|
988
|
+
|
|
989
|
+
call.reject(errorMessage, "GETCHANNEL_FAILED", null, errorObj);
|
|
990
|
+
} else {
|
|
991
|
+
call.resolve(jsRes);
|
|
992
|
+
}
|
|
993
|
+
})
|
|
994
|
+
);
|
|
995
|
+
} catch (final Exception e) {
|
|
996
|
+
logger.error("Failed to getChannel " + e.getMessage());
|
|
997
|
+
call.reject("Failed to getChannel", e);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
@PluginMethod
|
|
1002
|
+
public void listChannels(final PluginCall call) {
|
|
1003
|
+
try {
|
|
1004
|
+
logger.info("listChannels");
|
|
1005
|
+
startNewThread(() ->
|
|
1006
|
+
CapacitorUpdaterPlugin.this.implementation.listChannels((res) -> {
|
|
1007
|
+
JSObject jsRes = mapToJSObject(res);
|
|
1008
|
+
if (jsRes.has("error")) {
|
|
1009
|
+
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
|
|
1010
|
+
String errorCode = jsRes.getString("error");
|
|
1011
|
+
|
|
1012
|
+
JSObject errorObj = new JSObject();
|
|
1013
|
+
errorObj.put("message", errorMessage);
|
|
1014
|
+
errorObj.put("error", errorCode);
|
|
1015
|
+
|
|
1016
|
+
call.reject(errorMessage, "LISTCHANNELS_FAILED", null, errorObj);
|
|
1017
|
+
} else {
|
|
1018
|
+
call.resolve(jsRes);
|
|
1019
|
+
}
|
|
1020
|
+
})
|
|
1021
|
+
);
|
|
1022
|
+
} catch (final Exception e) {
|
|
1023
|
+
logger.error("Failed to listChannels: " + e.getMessage());
|
|
1024
|
+
call.reject("Failed to listChannels", e);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
@PluginMethod
|
|
1029
|
+
public void download(final PluginCall call) {
|
|
1030
|
+
final String url = call.getString("url");
|
|
1031
|
+
final String version = call.getString("version");
|
|
1032
|
+
final String sessionKey = call.getString("sessionKey", "");
|
|
1033
|
+
final String checksum = call.getString("checksum", "");
|
|
1034
|
+
final JSONArray manifest = call.getData().optJSONArray("manifest");
|
|
1035
|
+
if (url == null) {
|
|
1036
|
+
logger.error("Download called without url");
|
|
1037
|
+
call.reject("Download called without url");
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
if (version == null) {
|
|
1041
|
+
logger.error("Download called without version");
|
|
1042
|
+
call.reject("Download called without version");
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
try {
|
|
1046
|
+
logger.info("Downloading " + url);
|
|
1047
|
+
startNewThread(() -> {
|
|
1048
|
+
try {
|
|
1049
|
+
final BundleInfo downloaded;
|
|
1050
|
+
if (manifest != null) {
|
|
1051
|
+
// For manifest downloads, we need to handle this asynchronously
|
|
1052
|
+
// since there's no synchronous downloadManifest method in Java
|
|
1053
|
+
CapacitorUpdaterPlugin.this.implementation.downloadBackground(url, version, sessionKey, checksum, manifest);
|
|
1054
|
+
// Return immediately with a pending status - the actual result will come via listeners
|
|
1055
|
+
final String id = CapacitorUpdaterPlugin.this.implementation.randomString();
|
|
1056
|
+
downloaded = new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), "");
|
|
1057
|
+
call.resolve(mapToJSObject(downloaded.toJSONMap()));
|
|
1058
|
+
return;
|
|
1059
|
+
} else {
|
|
1060
|
+
downloaded = CapacitorUpdaterPlugin.this.implementation.download(url, version, sessionKey, checksum);
|
|
1061
|
+
}
|
|
1062
|
+
if (downloaded.isErrorStatus()) {
|
|
1063
|
+
throw new RuntimeException("Download failed: " + downloaded.getStatus());
|
|
1064
|
+
} else {
|
|
1065
|
+
call.resolve(mapToJSObject(downloaded.toJSONMap()));
|
|
1066
|
+
}
|
|
1067
|
+
} catch (final Exception e) {
|
|
1068
|
+
logger.error("Failed to download from: " + url + " " + e.getMessage());
|
|
1069
|
+
call.reject("Failed to download from: " + url, e);
|
|
1070
|
+
final JSObject ret = new JSObject();
|
|
1071
|
+
ret.put("version", version);
|
|
1072
|
+
CapacitorUpdaterPlugin.this.notifyListeners("downloadFailed", ret);
|
|
1073
|
+
final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
1074
|
+
CapacitorUpdaterPlugin.this.implementation.sendStats("download_fail", current.getVersionName());
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
} catch (final Exception e) {
|
|
1078
|
+
logger.error("Failed to download from: " + url + " " + e.getMessage());
|
|
1079
|
+
call.reject("Failed to download from: " + url, e);
|
|
1080
|
+
final JSObject ret = new JSObject();
|
|
1081
|
+
ret.put("version", version);
|
|
1082
|
+
CapacitorUpdaterPlugin.this.notifyListeners("downloadFailed", ret);
|
|
1083
|
+
final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
1084
|
+
CapacitorUpdaterPlugin.this.implementation.sendStats("download_fail", current.getVersionName());
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
private void syncKeepUrlPathFlag(final boolean enabled) {
|
|
1089
|
+
if (this.bridge == null || this.bridge.getWebView() == null) {
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
final String script = enabled
|
|
1093
|
+
? "(function(){try{localStorage.setItem('" +
|
|
1094
|
+
KEEP_URL_FLAG_KEY +
|
|
1095
|
+
"','1');}catch(e){}window.__capgoKeepUrlPathAfterReload=true;var evt;try{evt=new CustomEvent('CapacitorUpdaterKeepUrlPathAfterReload',{detail:{enabled:true}});}catch(err){evt=document.createEvent('CustomEvent');evt.initCustomEvent('CapacitorUpdaterKeepUrlPathAfterReload',false,false,{enabled:true});}window.dispatchEvent(evt);})();"
|
|
1096
|
+
: "(function(){try{localStorage.removeItem('" +
|
|
1097
|
+
KEEP_URL_FLAG_KEY +
|
|
1098
|
+
"');}catch(e){}delete window.__capgoKeepUrlPathAfterReload;var evt;try{evt=new CustomEvent('CapacitorUpdaterKeepUrlPathAfterReload',{detail:{enabled:false}});}catch(err){evt=document.createEvent('CustomEvent');evt.initCustomEvent('CapacitorUpdaterKeepUrlPathAfterReload',false,false,{enabled:false});}window.dispatchEvent(evt);})();";
|
|
1099
|
+
this.bridge.getWebView().post(() -> this.bridge.getWebView().evaluateJavascript(script, null));
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
protected boolean _reload() {
|
|
1103
|
+
final String path = this.implementation.getCurrentBundlePath();
|
|
1104
|
+
if (this.keepUrlPathAfterReload) {
|
|
1105
|
+
this.syncKeepUrlPathFlag(true);
|
|
1106
|
+
}
|
|
1107
|
+
this.semaphoreUp();
|
|
1108
|
+
logger.info("Reloading: " + path);
|
|
1109
|
+
|
|
1110
|
+
AtomicReference<URL> url = new AtomicReference<>();
|
|
1111
|
+
if (this.keepUrlPathAfterReload) {
|
|
1112
|
+
try {
|
|
1113
|
+
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
1114
|
+
Semaphore mainThreadSemaphore = new Semaphore(0);
|
|
1115
|
+
this.bridge.executeOnMainThread(() -> {
|
|
1116
|
+
try {
|
|
1117
|
+
if (this.bridge != null && this.bridge.getWebView() != null) {
|
|
1118
|
+
String currentUrl = this.bridge.getWebView().getUrl();
|
|
1119
|
+
if (currentUrl != null) {
|
|
1120
|
+
url.set(new URL(currentUrl));
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
} catch (Exception e) {
|
|
1124
|
+
logger.error("Error executing on main thread " + e.getMessage());
|
|
1125
|
+
}
|
|
1126
|
+
mainThreadSemaphore.release();
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
// Add timeout to prevent indefinite blocking
|
|
1130
|
+
if (!mainThreadSemaphore.tryAcquire(10, TimeUnit.SECONDS)) {
|
|
1131
|
+
logger.error("Timeout waiting for main thread operation");
|
|
1132
|
+
}
|
|
1133
|
+
} else {
|
|
1134
|
+
try {
|
|
1135
|
+
if (this.bridge != null && this.bridge.getWebView() != null) {
|
|
1136
|
+
String currentUrl = this.bridge.getWebView().getUrl();
|
|
1137
|
+
if (currentUrl != null) {
|
|
1138
|
+
url.set(new URL(currentUrl));
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
} catch (Exception e) {
|
|
1142
|
+
logger.error("Error executing on main thread " + e.getMessage());
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
} catch (InterruptedException e) {
|
|
1146
|
+
logger.error("Error waiting for main thread or getting the current URL from webview " + e.getMessage());
|
|
1147
|
+
Thread.currentThread().interrupt(); // Restore interrupted status
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
if (url.get() != null) {
|
|
1152
|
+
if (this.implementation.isUsingBuiltin()) {
|
|
1153
|
+
this.bridge.getLocalServer().hostAssets(path);
|
|
1154
|
+
} else {
|
|
1155
|
+
this.bridge.getLocalServer().hostFiles(path);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
try {
|
|
1159
|
+
URL finalUrl = null;
|
|
1160
|
+
finalUrl = new URL(this.bridge.getAppUrl());
|
|
1161
|
+
finalUrl = new URL(finalUrl.getProtocol(), finalUrl.getHost(), finalUrl.getPort(), url.get().getPath());
|
|
1162
|
+
URL finalUrl1 = finalUrl;
|
|
1163
|
+
this.bridge.getWebView().post(() -> {
|
|
1164
|
+
this.bridge.getWebView().loadUrl(finalUrl1.toString());
|
|
1165
|
+
if (!this.keepUrlPathAfterReload) {
|
|
1166
|
+
this.bridge.getWebView().clearHistory();
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
} catch (MalformedURLException e) {
|
|
1170
|
+
logger.error("Cannot get finalUrl from capacitor bridge " + e.getMessage());
|
|
1171
|
+
|
|
1172
|
+
if (this.implementation.isUsingBuiltin()) {
|
|
1173
|
+
this.bridge.setServerAssetPath(path);
|
|
1174
|
+
} else {
|
|
1175
|
+
this.bridge.setServerBasePath(path);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
} else {
|
|
1179
|
+
if (this.implementation.isUsingBuiltin()) {
|
|
1180
|
+
this.bridge.setServerAssetPath(path);
|
|
1181
|
+
} else {
|
|
1182
|
+
this.bridge.setServerBasePath(path);
|
|
1183
|
+
}
|
|
1184
|
+
if (this.bridge != null && this.bridge.getWebView() != null) {
|
|
1185
|
+
this.bridge.getWebView().post(() -> {
|
|
1186
|
+
if (this.bridge.getWebView() != null) {
|
|
1187
|
+
this.bridge.getWebView().loadUrl(this.bridge.getAppUrl());
|
|
1188
|
+
if (!this.keepUrlPathAfterReload) {
|
|
1189
|
+
this.bridge.getWebView().clearHistory();
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
this.checkAppReady();
|
|
1197
|
+
this.notifyListeners("appReloaded", new JSObject());
|
|
1198
|
+
|
|
1199
|
+
// Wait for the reload to complete (until notifyAppReady is called)
|
|
1200
|
+
try {
|
|
1201
|
+
this.semaphoreWait(this.appReadyTimeout);
|
|
1202
|
+
} catch (Exception e) {
|
|
1203
|
+
logger.error("Error waiting for app ready: " + e.getMessage());
|
|
1204
|
+
return false;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
return true;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
@PluginMethod
|
|
1211
|
+
public void reload(final PluginCall call) {
|
|
1212
|
+
try {
|
|
1213
|
+
if (this._reload()) {
|
|
1214
|
+
call.resolve();
|
|
1215
|
+
} else {
|
|
1216
|
+
logger.error("Reload failed");
|
|
1217
|
+
call.reject("Reload failed");
|
|
1218
|
+
}
|
|
1219
|
+
} catch (final Exception e) {
|
|
1220
|
+
logger.error("Could not reload " + e.getMessage());
|
|
1221
|
+
call.reject("Could not reload", e);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
@PluginMethod
|
|
1226
|
+
public void next(final PluginCall call) {
|
|
1227
|
+
final String id = call.getString("id");
|
|
1228
|
+
if (id == null) {
|
|
1229
|
+
logger.error("Next called without id");
|
|
1230
|
+
call.reject("Next called without id");
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
try {
|
|
1234
|
+
logger.info("Setting next active id " + id);
|
|
1235
|
+
if (!this.implementation.setNextBundle(id)) {
|
|
1236
|
+
logger.error("Set next id failed. Bundle " + id + " does not exist.");
|
|
1237
|
+
call.reject("Set next id failed. Bundle " + id + " does not exist.");
|
|
1238
|
+
} else {
|
|
1239
|
+
call.resolve(mapToJSObject(this.implementation.getBundleInfo(id).toJSONMap()));
|
|
1240
|
+
}
|
|
1241
|
+
} catch (final Exception e) {
|
|
1242
|
+
logger.error("Could not set next id " + id + " " + e.getMessage());
|
|
1243
|
+
call.reject("Could not set next id: " + id, e);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
@PluginMethod
|
|
1248
|
+
public void set(final PluginCall call) {
|
|
1249
|
+
final String id = call.getString("id");
|
|
1250
|
+
if (id == null) {
|
|
1251
|
+
logger.error("Set called without id");
|
|
1252
|
+
call.reject("Set called without id");
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
try {
|
|
1256
|
+
logger.info("Setting active bundle " + id);
|
|
1257
|
+
if (!this.implementation.set(id)) {
|
|
1258
|
+
logger.info("No such bundle " + id);
|
|
1259
|
+
call.reject("Update failed, id " + id + " does not exist.");
|
|
1260
|
+
} else {
|
|
1261
|
+
logger.info("Bundle successfully set to " + id);
|
|
1262
|
+
this.reload(call);
|
|
1263
|
+
}
|
|
1264
|
+
} catch (final Exception e) {
|
|
1265
|
+
logger.error("Could not set id " + id + " " + e.getMessage());
|
|
1266
|
+
call.reject("Could not set id " + id, e);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
@PluginMethod
|
|
1271
|
+
public void delete(final PluginCall call) {
|
|
1272
|
+
final String id = call.getString("id");
|
|
1273
|
+
if (id == null) {
|
|
1274
|
+
logger.error("missing id");
|
|
1275
|
+
call.reject("missing id");
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
logger.info("Deleting id " + id);
|
|
1279
|
+
try {
|
|
1280
|
+
final Boolean res = this.implementation.delete(id);
|
|
1281
|
+
if (res) {
|
|
1282
|
+
call.resolve();
|
|
1283
|
+
} else {
|
|
1284
|
+
logger.error("Delete failed, id " + id + " does not exist");
|
|
1285
|
+
call.reject("Delete failed, id " + id + " does not exist or it cannot be deleted (perhaps it is the 'next' bundle)");
|
|
199
1286
|
}
|
|
200
|
-
|
|
1287
|
+
} catch (final Exception e) {
|
|
1288
|
+
logger.error("Could not delete id " + id + " " + e.getMessage());
|
|
1289
|
+
call.reject("Could not delete id " + id, e);
|
|
201
1290
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
public void notifyDownload(final String id, final int percent) {
|
|
224
|
-
try {
|
|
225
|
-
final JSObject ret = new JSObject();
|
|
226
|
-
ret.put("percent", percent);
|
|
227
|
-
final BundleInfo bundleInfo = this.implementation.getBundleInfo(id);
|
|
228
|
-
ret.put("bundle", bundleInfo.toJSON());
|
|
229
|
-
this.notifyListeners("download", ret);
|
|
230
|
-
if (percent == 100) {
|
|
231
|
-
this.notifyListeners("downloadComplete", bundleInfo.toJSON());
|
|
232
|
-
this.implementation.sendStats(
|
|
233
|
-
"download_complete",
|
|
234
|
-
bundleInfo.getVersionName()
|
|
235
|
-
);
|
|
236
|
-
} else if (percent % 10 == 0) {
|
|
237
|
-
this.implementation.sendStats(
|
|
238
|
-
"download_" + percent,
|
|
239
|
-
bundleInfo.getVersionName()
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
} catch (final Exception e) {
|
|
243
|
-
Log.e(CapacitorUpdater.TAG, "Could not notify listeners", e);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
@PluginMethod
|
|
248
|
-
public void getDeviceId(final PluginCall call) {
|
|
249
|
-
try {
|
|
250
|
-
final JSObject ret = new JSObject();
|
|
251
|
-
ret.put("deviceId", this.implementation.deviceID);
|
|
252
|
-
call.resolve(ret);
|
|
253
|
-
} catch (final Exception e) {
|
|
254
|
-
Log.e(CapacitorUpdater.TAG, "Could not get device id", e);
|
|
255
|
-
call.reject("Could not get device id", e);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
@PluginMethod
|
|
260
|
-
public void setCustomId(final PluginCall call) {
|
|
261
|
-
final String customId = call.getString("customId");
|
|
262
|
-
if (customId == null) {
|
|
263
|
-
Log.e(CapacitorUpdater.TAG, "setCustomId called without customId");
|
|
264
|
-
call.reject("setCustomId called without customId");
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
this.implementation.customId = customId;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
@PluginMethod
|
|
271
|
-
public void getPluginVersion(final PluginCall call) {
|
|
272
|
-
try {
|
|
273
|
-
final JSObject ret = new JSObject();
|
|
274
|
-
ret.put("version", this.PLUGIN_VERSION);
|
|
275
|
-
call.resolve(ret);
|
|
276
|
-
} catch (final Exception e) {
|
|
277
|
-
Log.e(CapacitorUpdater.TAG, "Could not get plugin version", e);
|
|
278
|
-
call.reject("Could not get plugin version", e);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
@PluginMethod
|
|
283
|
-
public void setChannel(final PluginCall call) {
|
|
284
|
-
final String channel = call.getString("channel");
|
|
285
|
-
if (channel == null) {
|
|
286
|
-
Log.e(CapacitorUpdater.TAG, "setChannel called without channel");
|
|
287
|
-
call.reject("setChannel called without channel");
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
Log.i(CapacitorUpdater.TAG, "setChannel " + channel);
|
|
292
|
-
new Thread(
|
|
293
|
-
new Runnable() {
|
|
294
|
-
@Override
|
|
295
|
-
public void run() {
|
|
296
|
-
CapacitorUpdaterPlugin.this.implementation.setChannel(
|
|
297
|
-
channel,
|
|
298
|
-
res -> {
|
|
299
|
-
if (res.has("error")) {
|
|
300
|
-
call.reject(res.getString("error"));
|
|
301
|
-
} else {
|
|
302
|
-
call.resolve(res);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
)
|
|
309
|
-
.start();
|
|
310
|
-
} catch (final Exception e) {
|
|
311
|
-
Log.e(CapacitorUpdater.TAG, "Failed to setChannel: " + channel, e);
|
|
312
|
-
call.reject("Failed to setChannel: " + channel, e);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
@PluginMethod
|
|
317
|
-
public void getChannel(final PluginCall call) {
|
|
318
|
-
try {
|
|
319
|
-
Log.i(CapacitorUpdater.TAG, "getChannel");
|
|
320
|
-
new Thread(
|
|
321
|
-
new Runnable() {
|
|
322
|
-
@Override
|
|
323
|
-
public void run() {
|
|
324
|
-
CapacitorUpdaterPlugin.this.implementation.getChannel(res -> {
|
|
325
|
-
if (res.has("error")) {
|
|
326
|
-
call.reject(res.getString("error"));
|
|
327
|
-
} else {
|
|
328
|
-
call.resolve(res);
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
)
|
|
334
|
-
.start();
|
|
335
|
-
} catch (final Exception e) {
|
|
336
|
-
Log.e(CapacitorUpdater.TAG, "Failed to getChannel", e);
|
|
337
|
-
call.reject("Failed to getChannel", e);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
@PluginMethod
|
|
342
|
-
public void download(final PluginCall call) {
|
|
343
|
-
final String url = call.getString("url");
|
|
344
|
-
final String version = call.getString("version");
|
|
345
|
-
final String sessionKey = call.getString("sessionKey", "");
|
|
346
|
-
final String checksum = call.getString("checksum", "");
|
|
347
|
-
if (url == null) {
|
|
348
|
-
Log.e(CapacitorUpdater.TAG, "Download called without url");
|
|
349
|
-
call.reject("Download called without url");
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
if (version == null) {
|
|
353
|
-
Log.e(CapacitorUpdater.TAG, "Download called without version");
|
|
354
|
-
call.reject("Download called without version");
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
try {
|
|
358
|
-
Log.i(CapacitorUpdater.TAG, "Downloading " + url);
|
|
359
|
-
new Thread(
|
|
360
|
-
new Runnable() {
|
|
361
|
-
@Override
|
|
362
|
-
public void run() {
|
|
363
|
-
try {
|
|
364
|
-
final BundleInfo downloaded =
|
|
365
|
-
CapacitorUpdaterPlugin.this.implementation.download(
|
|
366
|
-
url,
|
|
367
|
-
version,
|
|
368
|
-
sessionKey,
|
|
369
|
-
checksum
|
|
370
|
-
);
|
|
371
|
-
|
|
372
|
-
call.resolve(downloaded.toJSON());
|
|
373
|
-
} catch (final IOException e) {
|
|
374
|
-
Log.e(CapacitorUpdater.TAG, "Failed to download from: " + url, e);
|
|
375
|
-
call.reject("Failed to download from: " + url, e);
|
|
376
|
-
final JSObject ret = new JSObject();
|
|
377
|
-
ret.put("version", version);
|
|
378
|
-
CapacitorUpdaterPlugin.this.notifyListeners(
|
|
379
|
-
"downloadFailed",
|
|
380
|
-
ret
|
|
381
|
-
);
|
|
382
|
-
final BundleInfo current =
|
|
383
|
-
CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
384
|
-
CapacitorUpdaterPlugin.this.implementation.sendStats(
|
|
385
|
-
"download_fail",
|
|
386
|
-
current.getVersionName()
|
|
387
|
-
);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
@PluginMethod
|
|
1294
|
+
public void setBundleError(final PluginCall call) {
|
|
1295
|
+
if (!Boolean.TRUE.equals(this.allowManualBundleError)) {
|
|
1296
|
+
logger.error("setBundleError called without allowManualBundleError");
|
|
1297
|
+
call.reject("setBundleError not allowed. Set allowManualBundleError to true in your config to enable it.");
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
final String id = call.getString("id");
|
|
1301
|
+
if (id == null) {
|
|
1302
|
+
logger.error("setBundleError called without id");
|
|
1303
|
+
call.reject("setBundleError called without id");
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
try {
|
|
1307
|
+
final BundleInfo bundle = this.implementation.getBundleInfo(id);
|
|
1308
|
+
if (bundle == null || bundle.isUnknown()) {
|
|
1309
|
+
logger.error("setBundleError called with unknown bundle " + id);
|
|
1310
|
+
call.reject("Bundle " + id + " does not exist");
|
|
1311
|
+
return;
|
|
388
1312
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
1313
|
+
if (bundle.isBuiltin()) {
|
|
1314
|
+
logger.error("setBundleError called on builtin bundle");
|
|
1315
|
+
call.reject("Cannot set builtin bundle to error state");
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
if (Boolean.TRUE.equals(this.autoUpdate)) {
|
|
1319
|
+
logger.warn("setBundleError used while autoUpdate is enabled; this method is intended for manual mode");
|
|
1320
|
+
}
|
|
1321
|
+
this.implementation.setError(bundle);
|
|
1322
|
+
final JSObject ret = new JSObject();
|
|
1323
|
+
ret.put("bundle", mapToJSObject(this.implementation.getBundleInfo(id).toJSONMap()));
|
|
1324
|
+
call.resolve(ret);
|
|
1325
|
+
} catch (final Exception e) {
|
|
1326
|
+
logger.error("Could not set bundle error for id " + id + " " + e.getMessage());
|
|
1327
|
+
call.reject("Could not set bundle error for id " + id, e);
|
|
1328
|
+
}
|
|
405
1329
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
@PluginMethod
|
|
437
|
-
public void next(final PluginCall call) {
|
|
438
|
-
final String id = call.getString("id");
|
|
439
|
-
if (id == null) {
|
|
440
|
-
Log.e(CapacitorUpdater.TAG, "Next called without id");
|
|
441
|
-
call.reject("Next called without id");
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
try {
|
|
445
|
-
Log.i(CapacitorUpdater.TAG, "Setting next active id " + id);
|
|
446
|
-
if (!this.implementation.setNextBundle(id)) {
|
|
447
|
-
Log.e(
|
|
448
|
-
CapacitorUpdater.TAG,
|
|
449
|
-
"Set next id failed. Bundle " + id + " does not exist."
|
|
450
|
-
);
|
|
451
|
-
call.reject("Set next id failed. Bundle " + id + " does not exist.");
|
|
452
|
-
} else {
|
|
453
|
-
call.resolve(this.implementation.getBundleInfo(id).toJSON());
|
|
454
|
-
}
|
|
455
|
-
} catch (final Exception e) {
|
|
456
|
-
Log.e(CapacitorUpdater.TAG, "Could not set next id " + id, e);
|
|
457
|
-
call.reject("Could not set next id: " + id, e);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
@PluginMethod
|
|
462
|
-
public void set(final PluginCall call) {
|
|
463
|
-
final String id = call.getString("id");
|
|
464
|
-
if (id == null) {
|
|
465
|
-
Log.e(CapacitorUpdater.TAG, "Set called without id");
|
|
466
|
-
call.reject("Set called without id");
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
try {
|
|
470
|
-
Log.i(CapacitorUpdater.TAG, "Setting active bundle " + id);
|
|
471
|
-
if (!this.implementation.set(id)) {
|
|
472
|
-
Log.i(CapacitorUpdater.TAG, "No such bundle " + id);
|
|
473
|
-
call.reject("Update failed, id " + id + " does not exist.");
|
|
474
|
-
} else {
|
|
475
|
-
Log.i(CapacitorUpdater.TAG, "Bundle successfully set to " + id);
|
|
476
|
-
this.reload(call);
|
|
477
|
-
}
|
|
478
|
-
} catch (final Exception e) {
|
|
479
|
-
Log.e(CapacitorUpdater.TAG, "Could not set id " + id, e);
|
|
480
|
-
call.reject("Could not set id " + id, e);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
@PluginMethod
|
|
485
|
-
public void delete(final PluginCall call) {
|
|
486
|
-
final String id = call.getString("id");
|
|
487
|
-
if (id == null) {
|
|
488
|
-
Log.e(CapacitorUpdater.TAG, "missing id");
|
|
489
|
-
call.reject("missing id");
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
Log.i(CapacitorUpdater.TAG, "Deleting id " + id);
|
|
493
|
-
try {
|
|
494
|
-
final Boolean res = this.implementation.delete(id);
|
|
495
|
-
if (res) {
|
|
496
|
-
call.resolve();
|
|
497
|
-
} else {
|
|
498
|
-
Log.e(
|
|
499
|
-
CapacitorUpdater.TAG,
|
|
500
|
-
"Delete failed, id " + id + " does not exist"
|
|
501
|
-
);
|
|
502
|
-
call.reject("Delete failed, id " + id + " does not exist");
|
|
503
|
-
}
|
|
504
|
-
} catch (final Exception e) {
|
|
505
|
-
Log.e(CapacitorUpdater.TAG, "Could not delete id " + id, e);
|
|
506
|
-
call.reject("Could not delete id " + id, e);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
@PluginMethod
|
|
511
|
-
public void list(final PluginCall call) {
|
|
512
|
-
try {
|
|
513
|
-
final List<BundleInfo> res = this.implementation.list();
|
|
514
|
-
final JSObject ret = new JSObject();
|
|
515
|
-
final JSArray values = new JSArray();
|
|
516
|
-
for (final BundleInfo bundle : res) {
|
|
517
|
-
values.put(bundle.toJSON());
|
|
518
|
-
}
|
|
519
|
-
ret.put("bundles", values);
|
|
520
|
-
call.resolve(ret);
|
|
521
|
-
} catch (final Exception e) {
|
|
522
|
-
Log.e(CapacitorUpdater.TAG, "Could not list bundles", e);
|
|
523
|
-
call.reject("Could not list bundles", e);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
@PluginMethod
|
|
528
|
-
public void getLatest(final PluginCall call) {
|
|
529
|
-
try {
|
|
530
|
-
new Thread(
|
|
531
|
-
new Runnable() {
|
|
532
|
-
@Override
|
|
533
|
-
public void run() {
|
|
534
|
-
CapacitorUpdaterPlugin.this.implementation.getLatest(
|
|
535
|
-
CapacitorUpdaterPlugin.this.updateUrl,
|
|
536
|
-
res -> {
|
|
537
|
-
if (res.has("error")) {
|
|
538
|
-
call.reject(res.getString("error"));
|
|
1330
|
+
|
|
1331
|
+
@PluginMethod
|
|
1332
|
+
public void list(final PluginCall call) {
|
|
1333
|
+
try {
|
|
1334
|
+
final List<BundleInfo> res = this.implementation.list(call.getBoolean("raw", false));
|
|
1335
|
+
final JSObject ret = new JSObject();
|
|
1336
|
+
final JSArray values = new JSArray();
|
|
1337
|
+
for (final BundleInfo bundle : res) {
|
|
1338
|
+
values.put(mapToJSObject(bundle.toJSONMap()));
|
|
1339
|
+
}
|
|
1340
|
+
ret.put("bundles", values);
|
|
1341
|
+
call.resolve(ret);
|
|
1342
|
+
} catch (final Exception e) {
|
|
1343
|
+
logger.error("Could not list bundles " + e.getMessage());
|
|
1344
|
+
call.reject("Could not list bundles", e);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
@PluginMethod
|
|
1349
|
+
public void getLatest(final PluginCall call) {
|
|
1350
|
+
final String channel = call.getString("channel");
|
|
1351
|
+
startNewThread(() ->
|
|
1352
|
+
CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, channel, (res) -> {
|
|
1353
|
+
JSObject jsRes = mapToJSObject(res);
|
|
1354
|
+
if (jsRes.has("error")) {
|
|
1355
|
+
String error = jsRes.getString("error");
|
|
1356
|
+
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : "server did not provide a message";
|
|
1357
|
+
logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
|
|
1358
|
+
call.reject(jsRes.getString("error"));
|
|
539
1359
|
return;
|
|
540
|
-
|
|
541
|
-
call.
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
while (keys.hasNext()) {
|
|
546
|
-
String key = keys.next();
|
|
547
|
-
if (res.has(key)) {
|
|
548
|
-
try {
|
|
549
|
-
ret.put(key, res.get(key));
|
|
550
|
-
} catch (JSONException e) {
|
|
551
|
-
e.printStackTrace();
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
call.resolve(ret);
|
|
1360
|
+
} else if (jsRes.has("message")) {
|
|
1361
|
+
call.reject(jsRes.getString("message"));
|
|
1362
|
+
return;
|
|
1363
|
+
} else {
|
|
1364
|
+
call.resolve(jsRes);
|
|
556
1365
|
}
|
|
557
|
-
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
)
|
|
561
|
-
.start();
|
|
562
|
-
} catch (final Exception e) {
|
|
563
|
-
Log.e(CapacitorUpdater.TAG, "Failed to getLatest", e);
|
|
564
|
-
call.reject("Failed to getLatest", e);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
private boolean _reset(final Boolean toLastSuccessful) {
|
|
569
|
-
final BundleInfo fallback = this.implementation.getFallbackBundle();
|
|
570
|
-
this.implementation.reset();
|
|
571
|
-
|
|
572
|
-
if (toLastSuccessful && !fallback.isBuiltin()) {
|
|
573
|
-
Log.i(CapacitorUpdater.TAG, "Resetting to: " + fallback);
|
|
574
|
-
return this.implementation.set(fallback) && this._reload();
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
Log.i(CapacitorUpdater.TAG, "Resetting to native.");
|
|
578
|
-
return this._reload();
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
@PluginMethod
|
|
582
|
-
public void reset(final PluginCall call) {
|
|
583
|
-
try {
|
|
584
|
-
final Boolean toLastSuccessful = call.getBoolean(
|
|
585
|
-
"toLastSuccessful",
|
|
586
|
-
false
|
|
587
|
-
);
|
|
588
|
-
if (this._reset(toLastSuccessful)) {
|
|
589
|
-
call.resolve();
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
Log.e(CapacitorUpdater.TAG, "Reset failed");
|
|
593
|
-
call.reject("Reset failed");
|
|
594
|
-
} catch (final Exception e) {
|
|
595
|
-
Log.e(CapacitorUpdater.TAG, "Reset failed", e);
|
|
596
|
-
call.reject("Reset failed", e);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
@PluginMethod
|
|
601
|
-
public void current(final PluginCall call) {
|
|
602
|
-
try {
|
|
603
|
-
final JSObject ret = new JSObject();
|
|
604
|
-
final BundleInfo bundle = this.implementation.getCurrentBundle();
|
|
605
|
-
ret.put("bundle", bundle.toJSON());
|
|
606
|
-
ret.put("native", this.currentVersionNative);
|
|
607
|
-
call.resolve(ret);
|
|
608
|
-
} catch (final Exception e) {
|
|
609
|
-
Log.e(CapacitorUpdater.TAG, "Could not get current bundle", e);
|
|
610
|
-
call.reject("Could not get current bundle", e);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
@PluginMethod
|
|
615
|
-
public void notifyAppReady(final PluginCall call) {
|
|
616
|
-
try {
|
|
617
|
-
final BundleInfo bundle = this.implementation.getCurrentBundle();
|
|
618
|
-
this.implementation.setSuccess(bundle, this.autoDeletePrevious);
|
|
619
|
-
Log.i(
|
|
620
|
-
CapacitorUpdater.TAG,
|
|
621
|
-
"Current bundle loaded successfully. ['notifyAppReady()' was called] " +
|
|
622
|
-
bundle
|
|
623
|
-
);
|
|
624
|
-
call.resolve();
|
|
625
|
-
} catch (final Exception e) {
|
|
626
|
-
Log.e(
|
|
627
|
-
CapacitorUpdater.TAG,
|
|
628
|
-
"Failed to notify app ready state. [Error calling 'notifyAppReady()']",
|
|
629
|
-
e
|
|
630
|
-
);
|
|
631
|
-
call.reject("Failed to commit app ready state.", e);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
@PluginMethod
|
|
636
|
-
public void setMultiDelay(final PluginCall call) {
|
|
637
|
-
try {
|
|
638
|
-
final Object delayConditions = call.getData().opt("delayConditions");
|
|
639
|
-
if (delayConditions == null) {
|
|
640
|
-
Log.e(
|
|
641
|
-
CapacitorUpdater.TAG,
|
|
642
|
-
"setMultiDelay called without delayCondition"
|
|
1366
|
+
})
|
|
643
1367
|
);
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
"
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
private void _checkCancelDelay(Boolean killed) {
|
|
700
|
-
Gson gson = new Gson();
|
|
701
|
-
String delayUpdatePreferences = prefs.getString(
|
|
702
|
-
DELAY_CONDITION_PREFERENCES,
|
|
703
|
-
"[]"
|
|
704
|
-
);
|
|
705
|
-
Type type = new TypeToken<ArrayList<DelayCondition>>() {}.getType();
|
|
706
|
-
ArrayList<DelayCondition> delayConditionList = gson.fromJson(
|
|
707
|
-
delayUpdatePreferences,
|
|
708
|
-
type
|
|
709
|
-
);
|
|
710
|
-
for (DelayCondition condition : delayConditionList) {
|
|
711
|
-
String kind = condition.getKind().toString();
|
|
712
|
-
String value = condition.getValue();
|
|
713
|
-
if (!"".equals(kind)) {
|
|
714
|
-
switch (kind) {
|
|
715
|
-
case "background":
|
|
716
|
-
if (!killed) {
|
|
717
|
-
this._cancelDelay("background check");
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
private boolean _reset(final Boolean toLastSuccessful) {
|
|
1371
|
+
final BundleInfo fallback = this.implementation.getFallbackBundle();
|
|
1372
|
+
this.implementation.reset();
|
|
1373
|
+
|
|
1374
|
+
if (toLastSuccessful && !fallback.isBuiltin()) {
|
|
1375
|
+
logger.info("Resetting to: " + fallback);
|
|
1376
|
+
return this.implementation.set(fallback) && this._reload();
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
logger.info("Resetting to native.");
|
|
1380
|
+
return this._reload();
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
@PluginMethod
|
|
1384
|
+
public void reset(final PluginCall call) {
|
|
1385
|
+
try {
|
|
1386
|
+
final Boolean toLastSuccessful = call.getBoolean("toLastSuccessful", false);
|
|
1387
|
+
if (this._reset(toLastSuccessful)) {
|
|
1388
|
+
call.resolve();
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
logger.error("Reset failed");
|
|
1392
|
+
call.reject("Reset failed");
|
|
1393
|
+
} catch (final Exception e) {
|
|
1394
|
+
logger.error("Reset failed " + e.getMessage());
|
|
1395
|
+
call.reject("Reset failed", e);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
@PluginMethod
|
|
1400
|
+
public void current(final PluginCall call) {
|
|
1401
|
+
ensureBridgeSet();
|
|
1402
|
+
try {
|
|
1403
|
+
final JSObject ret = new JSObject();
|
|
1404
|
+
final BundleInfo bundle = this.implementation.getCurrentBundle();
|
|
1405
|
+
ret.put("bundle", mapToJSObject(bundle.toJSONMap()));
|
|
1406
|
+
ret.put("native", this.currentVersionNative);
|
|
1407
|
+
call.resolve(ret);
|
|
1408
|
+
} catch (final Exception e) {
|
|
1409
|
+
logger.error("Could not get current bundle " + e.getMessage());
|
|
1410
|
+
call.reject("Could not get current bundle", e);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
@PluginMethod
|
|
1415
|
+
public void getNextBundle(final PluginCall call) {
|
|
1416
|
+
try {
|
|
1417
|
+
final BundleInfo bundle = this.implementation.getNextBundle();
|
|
1418
|
+
if (bundle == null) {
|
|
1419
|
+
call.resolve(null);
|
|
1420
|
+
return;
|
|
718
1421
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1422
|
+
|
|
1423
|
+
call.resolve(mapToJSObject(bundle.toJSONMap()));
|
|
1424
|
+
} catch (final Exception e) {
|
|
1425
|
+
logger.error("Could not get next bundle " + e.getMessage());
|
|
1426
|
+
call.reject("Could not get next bundle", e);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
@PluginMethod
|
|
1431
|
+
public void getFailedUpdate(final PluginCall call) {
|
|
1432
|
+
try {
|
|
1433
|
+
final BundleInfo bundle = this.readLastFailedBundle();
|
|
1434
|
+
if (bundle == null || bundle.isUnknown()) {
|
|
1435
|
+
call.resolve(null);
|
|
1436
|
+
return;
|
|
724
1437
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1438
|
+
|
|
1439
|
+
this.persistLastFailedBundle(null);
|
|
1440
|
+
|
|
1441
|
+
final JSObject ret = new JSObject();
|
|
1442
|
+
ret.put("bundle", mapToJSObject(bundle.toJSONMap()));
|
|
1443
|
+
call.resolve(ret);
|
|
1444
|
+
} catch (final Exception e) {
|
|
1445
|
+
logger.error("Could not get failed update " + e.getMessage());
|
|
1446
|
+
call.reject("Could not get failed update", e);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
public void checkForUpdateAfterDelay() {
|
|
1451
|
+
if (this.periodCheckDelay == 0 || !this._isAutoUpdateEnabled()) {
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
final Timer timer = new Timer();
|
|
1455
|
+
timer.schedule(
|
|
1456
|
+
new TimerTask() {
|
|
1457
|
+
@Override
|
|
1458
|
+
public void run() {
|
|
1459
|
+
try {
|
|
1460
|
+
CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, null, (res) -> {
|
|
1461
|
+
JSObject jsRes = mapToJSObject(res);
|
|
1462
|
+
if (jsRes.has("error")) {
|
|
1463
|
+
String error = jsRes.getString("error");
|
|
1464
|
+
String errorMessage = jsRes.has("message")
|
|
1465
|
+
? jsRes.getString("message")
|
|
1466
|
+
: "server did not provide a message";
|
|
1467
|
+
logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
|
|
1468
|
+
} else if (jsRes.has("version")) {
|
|
1469
|
+
String newVersion = jsRes.getString("version");
|
|
1470
|
+
String currentVersion = String.valueOf(CapacitorUpdaterPlugin.this.implementation.getCurrentBundle());
|
|
1471
|
+
if (!Objects.equals(newVersion, currentVersion)) {
|
|
1472
|
+
logger.info("New version found: " + newVersion);
|
|
1473
|
+
CapacitorUpdaterPlugin.this.backgroundDownload();
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
} catch (final Exception e) {
|
|
1478
|
+
logger.error("Failed to check for update " + e.getMessage());
|
|
1479
|
+
}
|
|
736
1480
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1481
|
+
},
|
|
1482
|
+
this.periodCheckDelay,
|
|
1483
|
+
this.periodCheckDelay
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
@PluginMethod
|
|
1488
|
+
public void notifyAppReady(final PluginCall call) {
|
|
1489
|
+
ensureBridgeSet();
|
|
1490
|
+
try {
|
|
1491
|
+
final BundleInfo bundle = this.implementation.getCurrentBundle();
|
|
1492
|
+
this.implementation.setSuccess(bundle, this.autoDeletePrevious);
|
|
1493
|
+
logger.info("Current bundle loaded successfully. ['notifyAppReady()' was called] " + bundle);
|
|
1494
|
+
logger.info("semaphoreReady countDown");
|
|
1495
|
+
this.semaphoreDown();
|
|
1496
|
+
logger.info("semaphoreReady countDown done");
|
|
1497
|
+
final JSObject ret = new JSObject();
|
|
1498
|
+
ret.put("bundle", mapToJSObject(bundle.toJSONMap()));
|
|
1499
|
+
call.resolve(ret);
|
|
1500
|
+
} catch (final Exception e) {
|
|
1501
|
+
logger.error("Failed to notify app ready state. [Error calling 'notifyAppReady()'] " + e.getMessage());
|
|
1502
|
+
call.reject("Failed to commit app ready state.", e);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
@PluginMethod
|
|
1507
|
+
public void setMultiDelay(final PluginCall call) {
|
|
1508
|
+
try {
|
|
1509
|
+
final JSONArray delayConditions = call.getData().optJSONArray("delayConditions");
|
|
1510
|
+
if (delayConditions == null) {
|
|
1511
|
+
logger.error("setMultiDelay called without delayCondition");
|
|
1512
|
+
call.reject("setMultiDelay called without delayCondition");
|
|
1513
|
+
return;
|
|
742
1514
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
if (this.currentVersionNative.isAtLeast(versionLimit)) {
|
|
749
|
-
this._cancelDelay("nativeVersion above limit");
|
|
1515
|
+
for (int i = 0; i < delayConditions.length(); i++) {
|
|
1516
|
+
final JSONObject object = delayConditions.optJSONObject(i);
|
|
1517
|
+
if (object != null && object.optString("kind").equals("background") && object.optString("value").isEmpty()) {
|
|
1518
|
+
object.put("value", "0");
|
|
1519
|
+
delayConditions.put(i, object);
|
|
750
1520
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
if (this.delayUpdateUtils.setMultiDelay(delayConditions.toString())) {
|
|
1524
|
+
call.resolve();
|
|
754
1525
|
} else {
|
|
755
|
-
|
|
1526
|
+
call.reject("Failed to delay update");
|
|
756
1527
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1528
|
+
} catch (final Exception e) {
|
|
1529
|
+
logger.error("Failed to delay update, [Error calling 'setMultiDelay()'] " + e.getMessage());
|
|
1530
|
+
call.reject("Failed to delay update", e);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
@PluginMethod
|
|
1535
|
+
public void cancelDelay(final PluginCall call) {
|
|
1536
|
+
if (this.delayUpdateUtils.cancelDelay("JS")) {
|
|
1537
|
+
call.resolve();
|
|
1538
|
+
} else {
|
|
1539
|
+
call.reject("Failed to cancel delay");
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
private Boolean _isAutoUpdateEnabled() {
|
|
1544
|
+
final CapConfig config = CapConfig.loadDefault(this.getActivity());
|
|
1545
|
+
String serverUrl = config.getServerUrl();
|
|
1546
|
+
if (serverUrl != null && !serverUrl.isEmpty()) {
|
|
1547
|
+
// log warning autoupdate disabled when serverUrl is set
|
|
1548
|
+
logger.warn("AutoUpdate is automatic disabled when serverUrl is set.");
|
|
1549
|
+
}
|
|
1550
|
+
return (
|
|
1551
|
+
CapacitorUpdaterPlugin.this.autoUpdate &&
|
|
1552
|
+
!"".equals(CapacitorUpdaterPlugin.this.updateUrl) &&
|
|
1553
|
+
(serverUrl == null || serverUrl.isEmpty())
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
@PluginMethod
|
|
1558
|
+
public void isAutoUpdateEnabled(final PluginCall call) {
|
|
1559
|
+
try {
|
|
1560
|
+
final JSObject ret = new JSObject();
|
|
1561
|
+
ret.put("enabled", this._isAutoUpdateEnabled());
|
|
1562
|
+
call.resolve(ret);
|
|
1563
|
+
} catch (final Exception e) {
|
|
1564
|
+
logger.error("Could not get autoUpdate status " + e.getMessage());
|
|
1565
|
+
call.reject("Could not get autoUpdate status", e);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
@PluginMethod
|
|
1570
|
+
public void isAutoUpdateAvailable(final PluginCall call) {
|
|
1571
|
+
try {
|
|
1572
|
+
final CapConfig config = CapConfig.loadDefault(this.getActivity());
|
|
1573
|
+
String serverUrl = config.getServerUrl();
|
|
1574
|
+
final JSObject ret = new JSObject();
|
|
1575
|
+
ret.put("available", serverUrl == null || serverUrl.isEmpty());
|
|
1576
|
+
call.resolve(ret);
|
|
1577
|
+
} catch (final Exception e) {
|
|
1578
|
+
logger.error("Could not get autoUpdate availability " + e.getMessage());
|
|
1579
|
+
call.reject("Could not get autoUpdate availability", e);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
private void checkAppReady() {
|
|
1584
|
+
try {
|
|
1585
|
+
if (this.appReadyCheck != null) {
|
|
1586
|
+
this.appReadyCheck.interrupt();
|
|
1587
|
+
}
|
|
1588
|
+
this.appReadyCheck = startNewThread(new DeferredNotifyAppReadyCheck());
|
|
1589
|
+
} catch (final Exception e) {
|
|
1590
|
+
logger.error("Failed to start " + DeferredNotifyAppReadyCheck.class.getName() + " " + e.getMessage());
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
private boolean isValidURL(String urlStr) {
|
|
1595
|
+
try {
|
|
1596
|
+
new URL(urlStr);
|
|
1597
|
+
return true;
|
|
1598
|
+
} catch (MalformedURLException e) {
|
|
1599
|
+
return false;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
private void ensureBridgeSet() {
|
|
1604
|
+
if (this.bridge != null && this.bridge.getWebView() != null) {
|
|
1605
|
+
logger.setBridge(this.bridge);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
private void endBackGroundTaskWithNotif(String msg, String latestVersionName, BundleInfo current, Boolean error) {
|
|
1610
|
+
endBackGroundTaskWithNotif(msg, latestVersionName, current, error, false, "download_fail", "downloadFailed", true);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
private void endBackGroundTaskWithNotif(
|
|
1614
|
+
String msg,
|
|
1615
|
+
String latestVersionName,
|
|
1616
|
+
BundleInfo current,
|
|
1617
|
+
Boolean error,
|
|
1618
|
+
Boolean isDirectUpdate
|
|
1619
|
+
) {
|
|
1620
|
+
endBackGroundTaskWithNotif(msg, latestVersionName, current, error, isDirectUpdate, "download_fail", "downloadFailed", true);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
private void endBackGroundTaskWithNotif(
|
|
1624
|
+
String msg,
|
|
1625
|
+
String latestVersionName,
|
|
1626
|
+
BundleInfo current,
|
|
1627
|
+
Boolean error,
|
|
1628
|
+
Boolean isDirectUpdate,
|
|
1629
|
+
String failureAction,
|
|
1630
|
+
String failureEvent
|
|
1631
|
+
) {
|
|
1632
|
+
endBackGroundTaskWithNotif(msg, latestVersionName, current, error, isDirectUpdate, failureAction, failureEvent, true);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
private void endBackGroundTaskWithNotif(
|
|
1636
|
+
String msg,
|
|
1637
|
+
String latestVersionName,
|
|
1638
|
+
BundleInfo current,
|
|
1639
|
+
Boolean error,
|
|
1640
|
+
Boolean isDirectUpdate,
|
|
1641
|
+
String failureAction,
|
|
1642
|
+
String failureEvent,
|
|
1643
|
+
boolean shouldSendStats
|
|
1644
|
+
) {
|
|
1645
|
+
if (error) {
|
|
1646
|
+
logger.info(
|
|
1647
|
+
"endBackGroundTaskWithNotif error: " +
|
|
1648
|
+
error +
|
|
1649
|
+
" current: " +
|
|
1650
|
+
current.getVersionName() +
|
|
1651
|
+
"latestVersionName: " +
|
|
1652
|
+
latestVersionName
|
|
1653
|
+
);
|
|
1654
|
+
if (shouldSendStats) {
|
|
1655
|
+
this.implementation.sendStats(failureAction, current.getVersionName());
|
|
1656
|
+
}
|
|
1657
|
+
final JSObject ret = new JSObject();
|
|
1658
|
+
ret.put("version", latestVersionName);
|
|
1659
|
+
this.notifyListeners(failureEvent, ret);
|
|
1660
|
+
}
|
|
1661
|
+
final JSObject ret = new JSObject();
|
|
1662
|
+
ret.put("bundle", mapToJSObject(current.toJSONMap()));
|
|
1663
|
+
this.notifyListeners("noNeedUpdate", ret);
|
|
1664
|
+
this.sendReadyToJs(current, msg, isDirectUpdate);
|
|
1665
|
+
this.backgroundDownloadTask = null;
|
|
1666
|
+
logger.info("endBackGroundTaskWithNotif " + msg);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
private Thread backgroundDownload() {
|
|
1670
|
+
final boolean plannedDirectUpdate = this.shouldUseDirectUpdate();
|
|
1671
|
+
final boolean initialDirectUpdateAllowed = this.isDirectUpdateCurrentlyAllowed(plannedDirectUpdate);
|
|
1672
|
+
this.implementation.directUpdate = initialDirectUpdateAllowed;
|
|
1673
|
+
final String messageUpdate = initialDirectUpdateAllowed
|
|
1674
|
+
? "Update will occur now."
|
|
1675
|
+
: "Update will occur next time app moves to background.";
|
|
1676
|
+
return startNewThread(() -> {
|
|
1677
|
+
logger.info("Check for update via: " + CapacitorUpdaterPlugin.this.updateUrl);
|
|
1678
|
+
try {
|
|
1679
|
+
CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, null, (res) -> {
|
|
1680
|
+
JSObject jsRes = mapToJSObject(res);
|
|
1681
|
+
final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
1682
|
+
|
|
1683
|
+
// Handle network errors and other failures first
|
|
1684
|
+
if (jsRes.has("error")) {
|
|
1685
|
+
String error = jsRes.getString("error");
|
|
1686
|
+
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : "server did not provide a message";
|
|
1687
|
+
int statusCode = jsRes.has("statusCode") ? jsRes.optInt("statusCode", 0) : 0;
|
|
1688
|
+
boolean responseIsOk = statusCode >= 200 && statusCode < 300;
|
|
1689
|
+
|
|
1690
|
+
logger.error(
|
|
1691
|
+
"getLatest failed with error: " + error + ", message: " + errorMessage + ", statusCode: " + statusCode
|
|
889
1692
|
);
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
CapacitorUpdaterPlugin.this.
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
"Latest bundle already exists and download is NOT required. Update will occur next time app moves to background."
|
|
1693
|
+
String latestVersion = jsRes.has("version") ? jsRes.getString("version") : current.getVersionName();
|
|
1694
|
+
|
|
1695
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1696
|
+
errorMessage,
|
|
1697
|
+
latestVersion,
|
|
1698
|
+
current,
|
|
1699
|
+
true,
|
|
1700
|
+
plannedDirectUpdate,
|
|
1701
|
+
"download_fail",
|
|
1702
|
+
"downloadFailed",
|
|
1703
|
+
!responseIsOk
|
|
902
1704
|
);
|
|
903
|
-
final JSObject ret = new JSObject();
|
|
904
|
-
ret.put("bundle", latest.toJSON());
|
|
905
|
-
CapacitorUpdaterPlugin.this.notifyListeners(
|
|
906
|
-
"updateAvailable",
|
|
907
|
-
ret
|
|
908
|
-
);
|
|
909
|
-
CapacitorUpdaterPlugin.this.implementation.setNextBundle(
|
|
910
|
-
latest.getId()
|
|
911
|
-
);
|
|
912
1705
|
return;
|
|
913
|
-
}
|
|
914
|
-
if (latest.isDeleted()) {
|
|
915
|
-
Log.i(
|
|
916
|
-
CapacitorUpdater.TAG,
|
|
917
|
-
"Latest bundle already exists and will be deleted, download will overwrite it."
|
|
918
|
-
);
|
|
919
|
-
try {
|
|
920
|
-
final Boolean deleted =
|
|
921
|
-
CapacitorUpdaterPlugin.this.implementation.delete(
|
|
922
|
-
latest.getId(),
|
|
923
|
-
true
|
|
924
|
-
);
|
|
925
|
-
if (deleted) {
|
|
926
|
-
Log.i(
|
|
927
|
-
CapacitorUpdater.TAG,
|
|
928
|
-
"Failed bundle deleted: " +
|
|
929
|
-
latest.getVersionName()
|
|
930
|
-
);
|
|
931
|
-
}
|
|
932
|
-
} catch (final IOException e) {
|
|
933
|
-
Log.e(
|
|
934
|
-
CapacitorUpdater.TAG,
|
|
935
|
-
"Failed to delete failed bundle: " +
|
|
936
|
-
latest.getVersionName(),
|
|
937
|
-
e
|
|
938
|
-
);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
1706
|
}
|
|
942
1707
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
"New bundle: " +
|
|
951
|
-
latestVersionName +
|
|
952
|
-
" found. Current is: " +
|
|
953
|
-
current.getVersionName() +
|
|
954
|
-
". Update will occur next time app moves to background."
|
|
1708
|
+
try {
|
|
1709
|
+
final String latestVersionName = jsRes.getString("version");
|
|
1710
|
+
|
|
1711
|
+
if ("builtin".equals(latestVersionName)) {
|
|
1712
|
+
logger.info("Latest version is builtin");
|
|
1713
|
+
final boolean directUpdateAllowedNow = CapacitorUpdaterPlugin.this.isDirectUpdateCurrentlyAllowed(
|
|
1714
|
+
plannedDirectUpdate
|
|
955
1715
|
);
|
|
1716
|
+
if (directUpdateAllowedNow) {
|
|
1717
|
+
logger.info("Direct update to builtin version");
|
|
1718
|
+
this._reset(false);
|
|
1719
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1720
|
+
"Updated to builtin version",
|
|
1721
|
+
latestVersionName,
|
|
1722
|
+
CapacitorUpdaterPlugin.this.implementation.getCurrentBundle(),
|
|
1723
|
+
false,
|
|
1724
|
+
true
|
|
1725
|
+
);
|
|
1726
|
+
} else {
|
|
1727
|
+
if (plannedDirectUpdate && !directUpdateAllowedNow) {
|
|
1728
|
+
logger.info(
|
|
1729
|
+
"Direct update skipped because splashscreen timeout occurred. Update will be applied later."
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
logger.info("Setting next bundle to builtin");
|
|
1733
|
+
CapacitorUpdaterPlugin.this.implementation.setNextBundle(BundleInfo.ID_BUILTIN);
|
|
1734
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1735
|
+
"Next update will be to builtin version",
|
|
1736
|
+
latestVersionName,
|
|
1737
|
+
current,
|
|
1738
|
+
false
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
956
1743
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
url,
|
|
966
|
-
latestVersionName,
|
|
967
|
-
sessionKey,
|
|
968
|
-
checksum
|
|
969
|
-
);
|
|
970
|
-
} catch (final Exception e) {
|
|
971
|
-
Log.e(
|
|
972
|
-
CapacitorUpdater.TAG,
|
|
973
|
-
"error downloading file",
|
|
974
|
-
e
|
|
1744
|
+
if (!jsRes.has("url") || !CapacitorUpdaterPlugin.this.isValidURL(jsRes.getString("url"))) {
|
|
1745
|
+
logger.error("Error no url or wrong format");
|
|
1746
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1747
|
+
"Error no url or wrong format",
|
|
1748
|
+
current.getVersionName(),
|
|
1749
|
+
current,
|
|
1750
|
+
true,
|
|
1751
|
+
plannedDirectUpdate
|
|
975
1752
|
);
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
final BundleInfo
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
"
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
if (
|
|
1757
|
+
latestVersionName != null && !latestVersionName.isEmpty() && !current.getVersionName().equals(latestVersionName)
|
|
1758
|
+
) {
|
|
1759
|
+
final BundleInfo latest = CapacitorUpdaterPlugin.this.implementation.getBundleInfoByName(latestVersionName);
|
|
1760
|
+
if (latest != null) {
|
|
1761
|
+
final JSObject ret = new JSObject();
|
|
1762
|
+
ret.put("bundle", mapToJSObject(latest.toJSONMap()));
|
|
1763
|
+
if (latest.isErrorStatus()) {
|
|
1764
|
+
logger.error("Latest bundle already exists, and is in error state. Aborting update.");
|
|
1765
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1766
|
+
"Latest bundle already exists, and is in error state. Aborting update.",
|
|
1767
|
+
latestVersionName,
|
|
1768
|
+
current,
|
|
1769
|
+
true,
|
|
1770
|
+
plannedDirectUpdate
|
|
1771
|
+
);
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
if (latest.isDownloaded()) {
|
|
1775
|
+
logger.info("Latest bundle already exists and download is NOT required. " + messageUpdate);
|
|
1776
|
+
final boolean directUpdateAllowedNow = CapacitorUpdaterPlugin.this.isDirectUpdateCurrentlyAllowed(
|
|
1777
|
+
plannedDirectUpdate
|
|
1778
|
+
);
|
|
1779
|
+
if (directUpdateAllowedNow) {
|
|
1780
|
+
String delayUpdatePreferences = prefs.getString(DelayUpdateUtils.DELAY_CONDITION_PREFERENCES, "[]");
|
|
1781
|
+
ArrayList<DelayCondition> delayConditionList = delayUpdateUtils.parseDelayConditions(
|
|
1782
|
+
delayUpdatePreferences
|
|
1783
|
+
);
|
|
1784
|
+
if (!delayConditionList.isEmpty()) {
|
|
1785
|
+
logger.info("Update delayed until delay conditions met");
|
|
1786
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1787
|
+
"Update delayed until delay conditions met",
|
|
1788
|
+
latestVersionName,
|
|
1789
|
+
latest,
|
|
1790
|
+
false,
|
|
1791
|
+
plannedDirectUpdate
|
|
1792
|
+
);
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
CapacitorUpdaterPlugin.this.implementation.set(latest);
|
|
1796
|
+
CapacitorUpdaterPlugin.this._reload();
|
|
1797
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1798
|
+
"Update installed",
|
|
1799
|
+
latestVersionName,
|
|
1800
|
+
latest,
|
|
1801
|
+
false,
|
|
1802
|
+
true
|
|
1803
|
+
);
|
|
1804
|
+
} else {
|
|
1805
|
+
if (plannedDirectUpdate && !directUpdateAllowedNow) {
|
|
1806
|
+
logger.info(
|
|
1807
|
+
"Direct update skipped because splashscreen timeout occurred. Update will install on next background."
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
CapacitorUpdaterPlugin.this.notifyListeners("updateAvailable", ret);
|
|
1811
|
+
CapacitorUpdaterPlugin.this.implementation.setNextBundle(latest.getId());
|
|
1812
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1813
|
+
"update downloaded, will install next background",
|
|
1814
|
+
latestVersionName,
|
|
1815
|
+
latest,
|
|
1816
|
+
false
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
if (latest.isDeleted()) {
|
|
1822
|
+
logger.info("Latest bundle already exists and will be deleted, download will overwrite it.");
|
|
1823
|
+
try {
|
|
1824
|
+
final Boolean deleted = CapacitorUpdaterPlugin.this.implementation.delete(latest.getId(), true);
|
|
1825
|
+
if (deleted) {
|
|
1826
|
+
logger.info("Failed bundle deleted: " + latest.getVersionName());
|
|
1827
|
+
}
|
|
1828
|
+
} catch (final IOException e) {
|
|
1829
|
+
logger.error("Failed to delete failed bundle: " + latest.getVersionName() + " " + e.getMessage());
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
startNewThread(() -> {
|
|
1834
|
+
try {
|
|
1835
|
+
logger.info(
|
|
1836
|
+
"New bundle: " +
|
|
1837
|
+
latestVersionName +
|
|
1838
|
+
" found. Current is: " +
|
|
1839
|
+
current.getVersionName() +
|
|
1840
|
+
". " +
|
|
1841
|
+
messageUpdate
|
|
1842
|
+
);
|
|
1843
|
+
|
|
1844
|
+
final String url = jsRes.getString("url");
|
|
1845
|
+
final String sessionKey = jsRes.has("sessionKey") ? jsRes.getString("sessionKey") : "";
|
|
1846
|
+
final String checksum = jsRes.has("checksum") ? jsRes.getString("checksum") : "";
|
|
1847
|
+
|
|
1848
|
+
if (jsRes.has("manifest")) {
|
|
1849
|
+
// Handle manifest-based download
|
|
1850
|
+
JSONArray manifest = jsRes.getJSONArray("manifest");
|
|
1851
|
+
CapacitorUpdaterPlugin.this.implementation.downloadBackground(
|
|
1852
|
+
url,
|
|
1853
|
+
latestVersionName,
|
|
1854
|
+
sessionKey,
|
|
1855
|
+
checksum,
|
|
1856
|
+
manifest
|
|
1857
|
+
);
|
|
1858
|
+
} else {
|
|
1859
|
+
// Handle single file download (existing code)
|
|
1860
|
+
CapacitorUpdaterPlugin.this.implementation.downloadBackground(
|
|
1861
|
+
url,
|
|
1862
|
+
latestVersionName,
|
|
1863
|
+
sessionKey,
|
|
1864
|
+
checksum,
|
|
1865
|
+
null
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
} catch (final Exception e) {
|
|
1869
|
+
logger.error("error downloading file " + e.getMessage());
|
|
1870
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1871
|
+
"Error downloading file",
|
|
1872
|
+
latestVersionName,
|
|
1873
|
+
CapacitorUpdaterPlugin.this.implementation.getCurrentBundle(),
|
|
1874
|
+
true,
|
|
1875
|
+
plannedDirectUpdate
|
|
1876
|
+
);
|
|
1877
|
+
}
|
|
1878
|
+
});
|
|
1879
|
+
} else {
|
|
1880
|
+
logger.info("No need to update, " + current.getId() + " is the latest bundle.");
|
|
1881
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif("No need to update", latestVersionName, current, false);
|
|
995
1882
|
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1883
|
+
} catch (final Exception e) {
|
|
1884
|
+
logger.error("error in update check " + e.getMessage());
|
|
1885
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1886
|
+
"Error in update check",
|
|
1887
|
+
current.getVersionName(),
|
|
1888
|
+
current,
|
|
1889
|
+
true,
|
|
1890
|
+
plannedDirectUpdate
|
|
1891
|
+
);
|
|
1892
|
+
}
|
|
1893
|
+
});
|
|
1894
|
+
} catch (final Exception e) {
|
|
1895
|
+
logger.error("getLatest call failed: " + e.getMessage());
|
|
1896
|
+
final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
1897
|
+
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1898
|
+
"Network connection failed",
|
|
1899
|
+
current.getVersionName(),
|
|
1900
|
+
current,
|
|
1901
|
+
true,
|
|
1902
|
+
plannedDirectUpdate
|
|
1903
|
+
);
|
|
1904
|
+
}
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
private void installNext() {
|
|
1909
|
+
try {
|
|
1910
|
+
String delayUpdatePreferences = prefs.getString(DelayUpdateUtils.DELAY_CONDITION_PREFERENCES, "[]");
|
|
1911
|
+
ArrayList<DelayCondition> delayConditionList = delayUpdateUtils.parseDelayConditions(delayUpdatePreferences);
|
|
1912
|
+
if (!delayConditionList.isEmpty()) {
|
|
1913
|
+
logger.info("Update delayed until delay conditions met");
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
final BundleInfo current = this.implementation.getCurrentBundle();
|
|
1917
|
+
final BundleInfo next = this.implementation.getNextBundle();
|
|
1918
|
+
|
|
1919
|
+
if (next != null && !next.isErrorStatus() && !next.getId().equals(current.getId())) {
|
|
1920
|
+
// There is a next bundle waiting for activation
|
|
1921
|
+
logger.debug("Next bundle is: " + next.getVersionName());
|
|
1922
|
+
if (this.implementation.set(next) && this._reload()) {
|
|
1923
|
+
logger.info("Updated to bundle: " + next.getVersionName());
|
|
1924
|
+
this.implementation.setNextBundle(null);
|
|
1925
|
+
} else {
|
|
1926
|
+
logger.error("Update to bundle: " + next.getVersionName() + " Failed!");
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
} catch (final Exception e) {
|
|
1930
|
+
logger.error("Error during onActivityStopped " + e.getMessage());
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
private void checkRevert() {
|
|
1935
|
+
// Automatically roll back to fallback version if notifyAppReady has not been called yet
|
|
1936
|
+
final BundleInfo current = this.implementation.getCurrentBundle();
|
|
1937
|
+
|
|
1938
|
+
if (current.isBuiltin()) {
|
|
1939
|
+
logger.info("Built-in bundle is active. We skip the check for notifyAppReady.");
|
|
1940
|
+
return;
|
|
1941
|
+
}
|
|
1942
|
+
logger.debug("Current bundle is: " + current);
|
|
1943
|
+
|
|
1944
|
+
if (BundleStatus.SUCCESS != current.getStatus()) {
|
|
1945
|
+
logger.error("notifyAppReady was not called, roll back current bundle: " + current.getId());
|
|
1946
|
+
logger.info("Did you forget to call 'notifyAppReady()' in your Capacitor App code?");
|
|
1947
|
+
final JSObject ret = new JSObject();
|
|
1948
|
+
ret.put("bundle", mapToJSObject(current.toJSONMap()));
|
|
1949
|
+
this.persistLastFailedBundle(current);
|
|
1950
|
+
this.notifyListeners("updateFailed", ret);
|
|
1951
|
+
this.implementation.sendStats("update_fail", current.getVersionName());
|
|
1952
|
+
this.implementation.setError(current);
|
|
1953
|
+
this._reset(true);
|
|
1954
|
+
if (CapacitorUpdaterPlugin.this.autoDeleteFailed && !current.isBuiltin()) {
|
|
1955
|
+
logger.info("Deleting failing bundle: " + current.getVersionName());
|
|
1956
|
+
try {
|
|
1957
|
+
final Boolean res = this.implementation.delete(current.getId(), false);
|
|
1958
|
+
if (res) {
|
|
1959
|
+
logger.info("Failed bundle deleted: " + current.getVersionName());
|
|
1960
|
+
}
|
|
1961
|
+
} catch (final IOException e) {
|
|
1962
|
+
logger.error("Failed to delete failed bundle: " + current.getVersionName() + " " + e.getMessage());
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
} else {
|
|
1966
|
+
logger.info("notifyAppReady was called. This is fine: " + current.getId());
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
private class DeferredNotifyAppReadyCheck implements Runnable {
|
|
1971
|
+
|
|
1972
|
+
@Override
|
|
1973
|
+
public void run() {
|
|
1974
|
+
try {
|
|
1975
|
+
logger.info("Wait for " + CapacitorUpdaterPlugin.this.appReadyTimeout + "ms, then check for notifyAppReady");
|
|
1976
|
+
Thread.sleep(CapacitorUpdaterPlugin.this.appReadyTimeout);
|
|
1977
|
+
CapacitorUpdaterPlugin.this.checkRevert();
|
|
1978
|
+
CapacitorUpdaterPlugin.this.appReadyCheck = null;
|
|
1979
|
+
} catch (final InterruptedException e) {
|
|
1980
|
+
logger.info(DeferredNotifyAppReadyCheck.class.getName() + " was interrupted.");
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
public void appMovedToForeground() {
|
|
1986
|
+
final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
1987
|
+
CapacitorUpdaterPlugin.this.implementation.sendStats("app_moved_to_foreground", current.getVersionName());
|
|
1988
|
+
this.delayUpdateUtils.checkCancelDelay(DelayUpdateUtils.CancelDelaySource.FOREGROUND);
|
|
1989
|
+
this.delayUpdateUtils.unsetBackgroundTimestamp();
|
|
1990
|
+
|
|
1991
|
+
if (
|
|
1992
|
+
CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() &&
|
|
1993
|
+
(this.backgroundDownloadTask == null || !this.backgroundDownloadTask.isAlive())
|
|
1994
|
+
) {
|
|
1995
|
+
this.backgroundDownloadTask = this.backgroundDownload();
|
|
1996
|
+
} else {
|
|
1997
|
+
final CapConfig config = CapConfig.loadDefault(this.getActivity());
|
|
1998
|
+
String serverUrl = config.getServerUrl();
|
|
1999
|
+
if (serverUrl != null && !serverUrl.isEmpty()) {
|
|
2000
|
+
CapacitorUpdaterPlugin.this.implementation.sendStats("blocked_by_server_url", current.getVersionName());
|
|
2001
|
+
}
|
|
2002
|
+
logger.info("Auto update is disabled");
|
|
2003
|
+
this.sendReadyToJs(current, "disabled");
|
|
2004
|
+
}
|
|
2005
|
+
this.checkAppReady();
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
public void appMovedToBackground() {
|
|
2009
|
+
final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
2010
|
+
|
|
2011
|
+
// Show splashscreen FIRST, before any other background work to ensure launcher shows it
|
|
2012
|
+
if (this.autoSplashscreen) {
|
|
2013
|
+
boolean canShowSplashscreen = true;
|
|
2014
|
+
|
|
2015
|
+
if (!this._isAutoUpdateEnabled()) {
|
|
2016
|
+
logger.warn(
|
|
2017
|
+
"autoSplashscreen is enabled but autoUpdate is disabled. Splashscreen will not be shown. Enable autoUpdate or disable autoSplashscreen."
|
|
2018
|
+
);
|
|
2019
|
+
canShowSplashscreen = false;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
if (!this.shouldUseDirectUpdate()) {
|
|
2023
|
+
if ("false".equals(this.directUpdateMode)) {
|
|
2024
|
+
logger.warn(
|
|
2025
|
+
"autoSplashscreen is enabled but directUpdate is not configured for immediate updates. Set directUpdate to 'always' or disable autoSplashscreen."
|
|
1005
2026
|
);
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
);
|
|
1012
|
-
}
|
|
1013
|
-
} catch (final JSONException e) {
|
|
1014
|
-
Log.e(CapacitorUpdater.TAG, "error parsing JSON", e);
|
|
1015
|
-
final JSObject retNoNeed = new JSObject();
|
|
1016
|
-
retNoNeed.put("bundle", current.toJSON());
|
|
1017
|
-
CapacitorUpdaterPlugin.this.notifyListeners(
|
|
1018
|
-
"noNeedUpdate",
|
|
1019
|
-
retNoNeed
|
|
2027
|
+
} else if ("atInstall".equals(this.directUpdateMode) || "onLaunch".equals(this.directUpdateMode)) {
|
|
2028
|
+
logger.info(
|
|
2029
|
+
"autoSplashscreen is enabled but directUpdate is set to \"" +
|
|
2030
|
+
this.directUpdateMode +
|
|
2031
|
+
"\". This is normal. Skipping autoSplashscreen logic."
|
|
1020
2032
|
);
|
|
1021
2033
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
2034
|
+
canShowSplashscreen = false;
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
if (canShowSplashscreen) {
|
|
2038
|
+
logger.info("Showing splashscreen for launcher/task switcher");
|
|
2039
|
+
this.showSplashscreen();
|
|
2040
|
+
}
|
|
1024
2041
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
private void installNext() {
|
|
1031
|
-
try {
|
|
1032
|
-
Gson gson = new Gson();
|
|
1033
|
-
String delayUpdatePreferences = prefs.getString(
|
|
1034
|
-
DELAY_CONDITION_PREFERENCES,
|
|
1035
|
-
"[]"
|
|
1036
|
-
);
|
|
1037
|
-
Type type = new TypeToken<ArrayList<DelayCondition>>() {}.getType();
|
|
1038
|
-
ArrayList<DelayCondition> delayConditionList = gson.fromJson(
|
|
1039
|
-
delayUpdatePreferences,
|
|
1040
|
-
type
|
|
1041
|
-
);
|
|
1042
|
-
if (delayConditionList != null && delayConditionList.size() != 0) {
|
|
1043
|
-
Log.i(CapacitorUpdater.TAG, "Update delayed to next backgrounding");
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
|
-
final BundleInfo current = this.implementation.getCurrentBundle();
|
|
1047
|
-
final BundleInfo next = this.implementation.getNextBundle();
|
|
1048
|
-
|
|
1049
|
-
if (
|
|
1050
|
-
next != null &&
|
|
1051
|
-
!next.isErrorStatus() &&
|
|
1052
|
-
!next.getId().equals(current.getId())
|
|
1053
|
-
) {
|
|
1054
|
-
// There is a next bundle waiting for activation
|
|
1055
|
-
Log.d(CapacitorUpdater.TAG, "Next bundle is: " + next.getVersionName());
|
|
1056
|
-
if (this.implementation.set(next) && this._reload()) {
|
|
1057
|
-
Log.i(
|
|
1058
|
-
CapacitorUpdater.TAG,
|
|
1059
|
-
"Updated to bundle: " + next.getVersionName()
|
|
1060
|
-
);
|
|
1061
|
-
this.implementation.setNextBundle(null);
|
|
1062
|
-
} else {
|
|
1063
|
-
Log.e(
|
|
1064
|
-
CapacitorUpdater.TAG,
|
|
1065
|
-
"Update to bundle: " + next.getVersionName() + " Failed!"
|
|
1066
|
-
);
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
} catch (final Exception e) {
|
|
1070
|
-
Log.e(CapacitorUpdater.TAG, "Error during onActivityStopped", e);
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
private void checkRevert() {
|
|
1075
|
-
// Automatically roll back to fallback version if notifyAppReady has not been called yet
|
|
1076
|
-
final BundleInfo current = this.implementation.getCurrentBundle();
|
|
1077
|
-
|
|
1078
|
-
if (current.isBuiltin()) {
|
|
1079
|
-
Log.i(CapacitorUpdater.TAG, "Built-in bundle is active. Nothing to do.");
|
|
1080
|
-
return;
|
|
1081
|
-
}
|
|
1082
|
-
Log.d(CapacitorUpdater.TAG, "Current bundle is: " + current);
|
|
1083
|
-
|
|
1084
|
-
if (BundleStatus.SUCCESS != current.getStatus()) {
|
|
1085
|
-
Log.e(
|
|
1086
|
-
CapacitorUpdater.TAG,
|
|
1087
|
-
"notifyAppReady was not called, roll back current bundle: " +
|
|
1088
|
-
current.getId()
|
|
1089
|
-
);
|
|
1090
|
-
Log.i(
|
|
1091
|
-
CapacitorUpdater.TAG,
|
|
1092
|
-
"Did you forget to call 'notifyAppReady()' in your Capacitor App code?"
|
|
1093
|
-
);
|
|
1094
|
-
final JSObject ret = new JSObject();
|
|
1095
|
-
ret.put("bundle", current.toJSON());
|
|
1096
|
-
this.notifyListeners("updateFailed", ret);
|
|
1097
|
-
this.implementation.sendStats("update_fail", current.getVersionName());
|
|
1098
|
-
this.implementation.setError(current);
|
|
1099
|
-
this._reset(true);
|
|
1100
|
-
if (
|
|
1101
|
-
CapacitorUpdaterPlugin.this.autoDeleteFailed && !current.isBuiltin()
|
|
1102
|
-
) {
|
|
1103
|
-
Log.i(
|
|
1104
|
-
CapacitorUpdater.TAG,
|
|
1105
|
-
"Deleting failing bundle: " + current.getVersionName()
|
|
1106
|
-
);
|
|
2042
|
+
|
|
2043
|
+
// Do other background work after splashscreen is shown
|
|
2044
|
+
CapacitorUpdaterPlugin.this.implementation.sendStats("app_moved_to_background", current.getVersionName());
|
|
2045
|
+
logger.info("Checking for pending update");
|
|
2046
|
+
|
|
1107
2047
|
try {
|
|
1108
|
-
|
|
1109
|
-
this.
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
);
|
|
1115
|
-
}
|
|
1116
|
-
} catch (final IOException e) {
|
|
1117
|
-
Log.e(
|
|
1118
|
-
CapacitorUpdater.TAG,
|
|
1119
|
-
"Failed to delete failed bundle: " + current.getVersionName(),
|
|
1120
|
-
e
|
|
1121
|
-
);
|
|
2048
|
+
// We need to set "backgrounded time"
|
|
2049
|
+
this.delayUpdateUtils.setBackgroundTimestamp(System.currentTimeMillis());
|
|
2050
|
+
this.delayUpdateUtils.checkCancelDelay(DelayUpdateUtils.CancelDelaySource.BACKGROUND);
|
|
2051
|
+
this.installNext();
|
|
2052
|
+
} catch (final Exception e) {
|
|
2053
|
+
logger.error("Error during onActivityStopped " + e.getMessage());
|
|
1122
2054
|
}
|
|
1123
|
-
}
|
|
1124
|
-
} else {
|
|
1125
|
-
Log.i(
|
|
1126
|
-
CapacitorUpdater.TAG,
|
|
1127
|
-
"notifyAppReady was called. This is fine: " + current.getId()
|
|
1128
|
-
);
|
|
1129
2055
|
}
|
|
1130
|
-
}
|
|
1131
2056
|
|
|
1132
|
-
|
|
2057
|
+
private boolean isMainActivity() {
|
|
2058
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
2059
|
+
return false;
|
|
2060
|
+
}
|
|
2061
|
+
try {
|
|
2062
|
+
Context mContext = this.getContext();
|
|
2063
|
+
ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
|
|
2064
|
+
List<ActivityManager.AppTask> runningTasks = activityManager.getAppTasks();
|
|
2065
|
+
if (runningTasks.isEmpty()) {
|
|
2066
|
+
return false;
|
|
2067
|
+
}
|
|
2068
|
+
ActivityManager.RecentTaskInfo runningTask = runningTasks.get(0).getTaskInfo();
|
|
2069
|
+
String className = Objects.requireNonNull(runningTask.baseIntent.getComponent()).getClassName();
|
|
2070
|
+
if (runningTask.topActivity == null) {
|
|
2071
|
+
return false;
|
|
2072
|
+
}
|
|
2073
|
+
String runningActivity = runningTask.topActivity.getClassName();
|
|
2074
|
+
return className.equals(runningActivity);
|
|
2075
|
+
} catch (NullPointerException e) {
|
|
2076
|
+
return false;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
1133
2079
|
|
|
1134
2080
|
@Override
|
|
1135
|
-
public void
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
backgroundTask =
|
|
1201
|
-
new Thread(
|
|
1202
|
-
new Runnable() {
|
|
1203
|
-
@Override
|
|
1204
|
-
public void run() {
|
|
2081
|
+
public void handleOnStart() {
|
|
2082
|
+
try {
|
|
2083
|
+
if (isPreviousMainActivity) {
|
|
2084
|
+
logger.info("handleOnStart: appMovedToForeground");
|
|
2085
|
+
this.appMovedToForeground();
|
|
2086
|
+
}
|
|
2087
|
+
logger.info("handleOnStart: onActivityStarted " + getActivity().getClass().getName());
|
|
2088
|
+
isPreviousMainActivity = true;
|
|
2089
|
+
|
|
2090
|
+
// Initialize shake menu if enabled and activity is BridgeActivity
|
|
2091
|
+
if (shakeMenuEnabled && getActivity() instanceof com.getcapacitor.BridgeActivity && shakeMenu == null) {
|
|
2092
|
+
try {
|
|
2093
|
+
shakeMenu = new ShakeMenu(this, (com.getcapacitor.BridgeActivity) getActivity(), logger);
|
|
2094
|
+
logger.info("Shake menu initialized");
|
|
2095
|
+
} catch (Exception e) {
|
|
2096
|
+
logger.error("Failed to initialize shake menu: " + e.getMessage());
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
} catch (Exception e) {
|
|
2100
|
+
logger.error("Failed to run handleOnStart: " + e.getMessage());
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
@Override
|
|
2105
|
+
public void handleOnStop() {
|
|
2106
|
+
try {
|
|
2107
|
+
isPreviousMainActivity = isMainActivity();
|
|
2108
|
+
if (isPreviousMainActivity) {
|
|
2109
|
+
logger.info("handleOnStop: appMovedToBackground");
|
|
2110
|
+
this.appMovedToBackground();
|
|
2111
|
+
}
|
|
2112
|
+
} catch (Exception e) {
|
|
2113
|
+
logger.error("Failed to run handleOnStop: " + e.getMessage());
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
@Override
|
|
2118
|
+
public void handleOnResume() {
|
|
2119
|
+
try {
|
|
2120
|
+
if (backgroundTask != null && taskRunning) {
|
|
2121
|
+
backgroundTask.interrupt();
|
|
2122
|
+
}
|
|
2123
|
+
this.implementation.activity = getActivity();
|
|
2124
|
+
} catch (Exception e) {
|
|
2125
|
+
logger.error("Failed to run handleOnResume: " + e.getMessage());
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
@Override
|
|
2130
|
+
public void handleOnPause() {
|
|
2131
|
+
try {
|
|
2132
|
+
this.implementation.activity = getActivity();
|
|
2133
|
+
} catch (Exception e) {
|
|
2134
|
+
logger.error("Failed to run handleOnPause: " + e.getMessage());
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
@Override
|
|
2139
|
+
public void handleOnDestroy() {
|
|
2140
|
+
try {
|
|
2141
|
+
logger.info("onActivityDestroyed " + getActivity().getClass().getName());
|
|
2142
|
+
this.implementation.activity = getActivity();
|
|
2143
|
+
|
|
2144
|
+
// Clean up shake menu
|
|
2145
|
+
if (shakeMenu != null) {
|
|
1205
2146
|
try {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
Log.i(
|
|
1212
|
-
CapacitorUpdater.TAG,
|
|
1213
|
-
"Background Task canceled, Activity resumed before timer completes"
|
|
1214
|
-
);
|
|
2147
|
+
shakeMenu.stop();
|
|
2148
|
+
shakeMenu = null;
|
|
2149
|
+
logger.info("Shake menu cleaned up");
|
|
2150
|
+
} catch (Exception e) {
|
|
2151
|
+
logger.error("Failed to clean up shake menu: " + e.getMessage());
|
|
1215
2152
|
}
|
|
1216
|
-
}
|
|
1217
2153
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
this.implementation.activity = activity;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
@Override
|
|
1300
|
-
public void onActivityDestroyed(@NonNull final Activity activity) {
|
|
1301
|
-
this.implementation.activity = activity;
|
|
1302
|
-
counterActivityCreate--;
|
|
1303
|
-
if (counterActivityCreate == 0) {
|
|
1304
|
-
this.appKilled();
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
2154
|
+
} catch (Exception e) {
|
|
2155
|
+
logger.error("Failed to run handleOnDestroy: " + e.getMessage());
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
@PluginMethod
|
|
2160
|
+
public void setShakeMenu(final PluginCall call) {
|
|
2161
|
+
final Boolean enabled = call.getBoolean("enabled");
|
|
2162
|
+
if (enabled == null) {
|
|
2163
|
+
logger.error("setShakeMenu called without enabled parameter");
|
|
2164
|
+
call.reject("setShakeMenu called without enabled parameter");
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
this.shakeMenuEnabled = enabled;
|
|
2169
|
+
logger.info("Shake menu " + (enabled ? "enabled" : "disabled"));
|
|
2170
|
+
|
|
2171
|
+
// Manage shake menu instance based on enabled state
|
|
2172
|
+
if (enabled && getActivity() instanceof com.getcapacitor.BridgeActivity && shakeMenu == null) {
|
|
2173
|
+
try {
|
|
2174
|
+
shakeMenu = new ShakeMenu(this, (com.getcapacitor.BridgeActivity) getActivity(), logger);
|
|
2175
|
+
logger.info("Shake menu initialized");
|
|
2176
|
+
} catch (Exception e) {
|
|
2177
|
+
logger.error("Failed to initialize shake menu: " + e.getMessage());
|
|
2178
|
+
}
|
|
2179
|
+
} else if (!enabled && shakeMenu != null) {
|
|
2180
|
+
try {
|
|
2181
|
+
shakeMenu.stop();
|
|
2182
|
+
shakeMenu = null;
|
|
2183
|
+
logger.info("Shake menu stopped");
|
|
2184
|
+
} catch (Exception e) {
|
|
2185
|
+
logger.error("Failed to stop shake menu: " + e.getMessage());
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
call.resolve();
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
@PluginMethod
|
|
2193
|
+
public void isShakeMenuEnabled(final PluginCall call) {
|
|
2194
|
+
try {
|
|
2195
|
+
final JSObject ret = new JSObject();
|
|
2196
|
+
ret.put("enabled", this.shakeMenuEnabled);
|
|
2197
|
+
call.resolve(ret);
|
|
2198
|
+
} catch (final Exception e) {
|
|
2199
|
+
logger.error("Could not get shake menu status " + e.getMessage());
|
|
2200
|
+
call.reject("Could not get shake menu status", e);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
@PluginMethod
|
|
2205
|
+
public void getAppId(final PluginCall call) {
|
|
2206
|
+
try {
|
|
2207
|
+
final JSObject ret = new JSObject();
|
|
2208
|
+
ret.put("appId", this.implementation.appId);
|
|
2209
|
+
call.resolve(ret);
|
|
2210
|
+
} catch (final Exception e) {
|
|
2211
|
+
logger.error("Could not get appId " + e.getMessage());
|
|
2212
|
+
call.reject("Could not get appId", e);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
@PluginMethod
|
|
2217
|
+
public void setAppId(final PluginCall call) {
|
|
2218
|
+
if (!this.getConfig().getBoolean("allowModifyAppId", false)) {
|
|
2219
|
+
logger.error("setAppId not allowed set allowModifyAppId in your config to true to allow it");
|
|
2220
|
+
call.reject("setAppId not allowed");
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
final String appId = call.getString("appId");
|
|
2224
|
+
if (appId == null) {
|
|
2225
|
+
logger.error("setAppId called without appId");
|
|
2226
|
+
call.reject("setAppId called without appId");
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
this.implementation.appId = appId;
|
|
2230
|
+
call.resolve();
|
|
2231
|
+
}
|
|
1307
2232
|
}
|