@capgo/capacitor-updater 8.0.1 → 8.2.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 +9 -7
- package/README.md +984 -215
- package/android/build.gradle +24 -12
- package/android/proguard-rules.pro +22 -5
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +110 -22
- package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +2 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1316 -489
- package/android/src/main/java/ee/forgr/capacitor_updater/{CapacitorUpdater.java → CapgoUpdater.java} +662 -203
- package/android/src/main/java/ee/forgr/capacitor_updater/{CryptoCipherV2.java → CryptoCipher.java} +138 -33
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +0 -3
- 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 +497 -133
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +80 -25
- 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 +873 -154
- package/dist/esm/definitions.d.ts +881 -114
- 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 +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +12 -1
- package/dist/esm/web.js +29 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +311 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +311 -2
- 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 +1610 -0
- package/ios/{Plugin/CapacitorUpdater.swift → Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift} +541 -231
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +286 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +220 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/InternalUtils.swift +54 -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 +21 -19
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +0 -975
- package/ios/Plugin/CryptoCipherV2.swift +0 -310
- /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
package/android/src/main/java/ee/forgr/capacitor_updater/{CryptoCipherV2.java → CryptoCipher.java}
RENAMED
|
@@ -12,7 +12,6 @@ package ee.forgr.capacitor_updater;
|
|
|
12
12
|
* references: http://stackoverflow.com/questions/12471999/rsa-encryption-decryption-in-android
|
|
13
13
|
*/
|
|
14
14
|
import android.util.Base64;
|
|
15
|
-
import android.util.Log;
|
|
16
15
|
import java.io.BufferedInputStream;
|
|
17
16
|
import java.io.DataInputStream;
|
|
18
17
|
import java.io.File;
|
|
@@ -36,7 +35,13 @@ import javax.crypto.SecretKey;
|
|
|
36
35
|
import javax.crypto.spec.IvParameterSpec;
|
|
37
36
|
import javax.crypto.spec.SecretKeySpec;
|
|
38
37
|
|
|
39
|
-
public class
|
|
38
|
+
public class CryptoCipher {
|
|
39
|
+
|
|
40
|
+
private static Logger logger;
|
|
41
|
+
|
|
42
|
+
public static void setLogger(Logger loggerInstance) {
|
|
43
|
+
logger = loggerInstance;
|
|
44
|
+
}
|
|
40
45
|
|
|
41
46
|
public static byte[] decryptRSA(byte[] source, PublicKey publicKey)
|
|
42
47
|
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
|
@@ -135,11 +140,11 @@ public class CryptoCipherV2 {
|
|
|
135
140
|
|
|
136
141
|
public static void decryptFile(final File file, final String publicKey, final String ivSessionKey) throws IOException {
|
|
137
142
|
if (publicKey.isEmpty() || ivSessionKey == null || ivSessionKey.isEmpty() || ivSessionKey.split(":").length != 2) {
|
|
138
|
-
|
|
143
|
+
logger.info("Encryption not set, no public key or session, ignored");
|
|
139
144
|
return;
|
|
140
145
|
}
|
|
141
146
|
if (!publicKey.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
|
|
142
|
-
|
|
147
|
+
logger.error("The public key is not a valid RSA Public key");
|
|
143
148
|
return;
|
|
144
149
|
}
|
|
145
150
|
|
|
@@ -148,10 +153,10 @@ public class CryptoCipherV2 {
|
|
|
148
153
|
String sessionKeyB64 = ivSessionKey.split(":")[1];
|
|
149
154
|
byte[] iv = Base64.decode(ivB64.getBytes(), Base64.DEFAULT);
|
|
150
155
|
byte[] sessionKey = Base64.decode(sessionKeyB64.getBytes(), Base64.DEFAULT);
|
|
151
|
-
PublicKey pKey =
|
|
152
|
-
byte[] decryptedSessionKey =
|
|
156
|
+
PublicKey pKey = CryptoCipher.stringToPublicKey(publicKey);
|
|
157
|
+
byte[] decryptedSessionKey = CryptoCipher.decryptRSA(sessionKey, pKey);
|
|
153
158
|
|
|
154
|
-
SecretKey sKey =
|
|
159
|
+
SecretKey sKey = CryptoCipher.byteToSessionKey(decryptedSessionKey);
|
|
155
160
|
byte[] content = new byte[(int) file.length()];
|
|
156
161
|
|
|
157
162
|
try (
|
|
@@ -161,52 +166,133 @@ public class CryptoCipherV2 {
|
|
|
161
166
|
) {
|
|
162
167
|
dis.readFully(content);
|
|
163
168
|
dis.close();
|
|
164
|
-
byte[] decrypted =
|
|
169
|
+
byte[] decrypted = CryptoCipher.decryptAES(content, sKey, iv);
|
|
165
170
|
// write the decrypted string to the file
|
|
166
171
|
try (final FileOutputStream fos = new FileOutputStream(file.getAbsolutePath())) {
|
|
167
172
|
fos.write(decrypted);
|
|
168
173
|
}
|
|
169
174
|
}
|
|
170
175
|
} catch (GeneralSecurityException e) {
|
|
171
|
-
|
|
176
|
+
logger.info("decryptFile fail");
|
|
172
177
|
e.printStackTrace();
|
|
173
178
|
throw new IOException("GeneralSecurityException");
|
|
174
179
|
}
|
|
175
180
|
}
|
|
176
181
|
|
|
182
|
+
private static byte[] hexStringToByteArray(String s) {
|
|
183
|
+
int len = s.length();
|
|
184
|
+
byte[] data = new byte[len / 2];
|
|
185
|
+
for (int i = 0; i < len; i += 2) {
|
|
186
|
+
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
|
|
187
|
+
}
|
|
188
|
+
return data;
|
|
189
|
+
}
|
|
190
|
+
|
|
177
191
|
public static String decryptChecksum(String checksum, String publicKey) throws IOException {
|
|
178
192
|
if (publicKey.isEmpty()) {
|
|
179
|
-
|
|
193
|
+
logger.error("No encryption set (public key) ignored");
|
|
180
194
|
return checksum;
|
|
181
195
|
}
|
|
182
196
|
try {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
String
|
|
188
|
-
|
|
197
|
+
// TODO: remove this in a month or two
|
|
198
|
+
// Determine if input is hex or base64 encoded
|
|
199
|
+
// Hex strings only contain 0-9 and a-f, while base64 contains other characters
|
|
200
|
+
byte[] checksumBytes;
|
|
201
|
+
String detectedFormat;
|
|
202
|
+
if (checksum.matches("^[0-9a-fA-F]+$")) {
|
|
203
|
+
// Hex encoded (new format from CLI for plugin versions >= 5.30.0, 6.30.0, 7.30.0)
|
|
204
|
+
checksumBytes = hexStringToByteArray(checksum);
|
|
205
|
+
detectedFormat = "hex";
|
|
206
|
+
} else {
|
|
207
|
+
// TODO: remove backwards compatibility
|
|
208
|
+
// Base64 encoded (old format for backwards compatibility)
|
|
209
|
+
checksumBytes = Base64.decode(checksum, Base64.DEFAULT);
|
|
210
|
+
detectedFormat = "base64";
|
|
211
|
+
}
|
|
212
|
+
logger.debug(
|
|
213
|
+
"Received encrypted checksum format: " +
|
|
214
|
+
detectedFormat +
|
|
215
|
+
" (length: " +
|
|
216
|
+
checksum.length() +
|
|
217
|
+
" chars, " +
|
|
218
|
+
checksumBytes.length +
|
|
219
|
+
" bytes)"
|
|
220
|
+
);
|
|
221
|
+
PublicKey pKey = CryptoCipher.stringToPublicKey(publicKey);
|
|
222
|
+
byte[] decryptedChecksum = CryptoCipher.decryptRSA(checksumBytes, pKey);
|
|
223
|
+
// Return as hex string to match calcChecksum output format
|
|
224
|
+
StringBuilder hexString = new StringBuilder();
|
|
225
|
+
for (byte b : decryptedChecksum) {
|
|
226
|
+
String hex = Integer.toHexString(0xff & b);
|
|
227
|
+
if (hex.length() == 1) hexString.append('0');
|
|
228
|
+
hexString.append(hex);
|
|
229
|
+
}
|
|
230
|
+
String result = hexString.toString();
|
|
231
|
+
|
|
232
|
+
// Detect checksum algorithm based on length
|
|
233
|
+
String detectedAlgorithm;
|
|
234
|
+
if (decryptedChecksum.length == 32) {
|
|
235
|
+
detectedAlgorithm = "SHA-256";
|
|
236
|
+
} else if (decryptedChecksum.length == 4) {
|
|
237
|
+
detectedAlgorithm = "CRC32 (deprecated)";
|
|
238
|
+
logger.error(
|
|
239
|
+
"CRC32 checksum detected. This algorithm is deprecated and no longer supported. Please update your CLI to use SHA-256 checksums."
|
|
240
|
+
);
|
|
241
|
+
} else {
|
|
242
|
+
detectedAlgorithm = "unknown (" + decryptedChecksum.length + " bytes)";
|
|
243
|
+
logger.error(
|
|
244
|
+
"Unknown checksum algorithm detected with " + decryptedChecksum.length + " bytes. Expected SHA-256 (32 bytes)."
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
logger.debug(
|
|
248
|
+
"Decrypted checksum: " +
|
|
249
|
+
detectedAlgorithm +
|
|
250
|
+
" hex format (length: " +
|
|
251
|
+
result.length() +
|
|
252
|
+
" chars, " +
|
|
253
|
+
decryptedChecksum.length +
|
|
254
|
+
" bytes)"
|
|
255
|
+
);
|
|
256
|
+
return result;
|
|
189
257
|
} catch (GeneralSecurityException e) {
|
|
190
|
-
|
|
258
|
+
logger.error("decryptChecksum fail: " + e.getMessage());
|
|
191
259
|
throw new IOException("Decryption failed: " + e.getMessage());
|
|
192
260
|
}
|
|
193
261
|
}
|
|
194
262
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Detect checksum algorithm based on hex string length.
|
|
265
|
+
* SHA-256 = 64 hex chars (32 bytes)
|
|
266
|
+
* CRC32 = 8 hex chars (4 bytes)
|
|
267
|
+
*/
|
|
268
|
+
public static String detectChecksumAlgorithm(String hexChecksum) {
|
|
269
|
+
if (hexChecksum == null || hexChecksum.isEmpty()) {
|
|
270
|
+
return "empty";
|
|
199
271
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
272
|
+
int len = hexChecksum.length();
|
|
273
|
+
if (len == 64) {
|
|
274
|
+
return "SHA-256";
|
|
275
|
+
} else if (len == 8) {
|
|
276
|
+
return "CRC32 (deprecated)";
|
|
277
|
+
} else {
|
|
278
|
+
return "unknown (" + len + " hex chars)";
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Log checksum info and warn if deprecated algorithm detected.
|
|
284
|
+
*/
|
|
285
|
+
public static void logChecksumInfo(String label, String hexChecksum) {
|
|
286
|
+
String algorithm = detectChecksumAlgorithm(hexChecksum);
|
|
287
|
+
logger.debug(label + ": " + algorithm + " hex format (length: " + hexChecksum.length() + " chars)");
|
|
288
|
+
if (algorithm.contains("CRC32")) {
|
|
289
|
+
logger.error(
|
|
290
|
+
"CRC32 checksum detected. This algorithm is deprecated and no longer supported. Please update your CLI to use SHA-256 checksums."
|
|
291
|
+
);
|
|
292
|
+
} else if (algorithm.contains("unknown")) {
|
|
293
|
+
logger.error(
|
|
294
|
+
"Unknown checksum algorithm detected. Expected SHA-256 (64 hex chars) but got " + hexChecksum.length() + " chars."
|
|
295
|
+
);
|
|
210
296
|
}
|
|
211
297
|
}
|
|
212
298
|
|
|
@@ -216,7 +302,7 @@ public class CryptoCipherV2 {
|
|
|
216
302
|
try {
|
|
217
303
|
digest = MessageDigest.getInstance("SHA-256");
|
|
218
304
|
} catch (java.security.NoSuchAlgorithmException e) {
|
|
219
|
-
|
|
305
|
+
logger.error("SHA-256 algorithm not available");
|
|
220
306
|
return "";
|
|
221
307
|
}
|
|
222
308
|
|
|
@@ -235,7 +321,7 @@ public class CryptoCipherV2 {
|
|
|
235
321
|
}
|
|
236
322
|
return hexString.toString();
|
|
237
323
|
} catch (IOException e) {
|
|
238
|
-
|
|
324
|
+
logger.error("Cannot calc checksum v2: " + file.getPath() + " " + e.getMessage());
|
|
239
325
|
return "";
|
|
240
326
|
}
|
|
241
327
|
}
|
|
@@ -273,4 +359,23 @@ public class CryptoCipherV2 {
|
|
|
273
359
|
|
|
274
360
|
throw new IllegalArgumentException("size too large, only up to 64KiB length encoding supported: " + size);
|
|
275
361
|
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Get first 4 characters of the public key for identification.
|
|
365
|
+
* Returns 4-character string or empty string if key is invalid/empty.
|
|
366
|
+
*/
|
|
367
|
+
public static String calcKeyId(String publicKey) {
|
|
368
|
+
if (publicKey == null || publicKey.isEmpty()) {
|
|
369
|
+
return "";
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Remove PEM headers and whitespace to get the raw key data
|
|
373
|
+
String cleanedKey = publicKey
|
|
374
|
+
.replaceAll("\\s+", "")
|
|
375
|
+
.replace("-----BEGINRSAPUBLICKEY-----", "")
|
|
376
|
+
.replace("-----ENDRSAPUBLICKEY-----", "");
|
|
377
|
+
|
|
378
|
+
// Return first 4 characters of the base64-encoded key
|
|
379
|
+
return cleanedKey.length() >= 4 ? cleanedKey.substring(0, 4) : cleanedKey;
|
|
380
|
+
}
|
|
276
381
|
}
|
|
@@ -7,15 +7,12 @@
|
|
|
7
7
|
package ee.forgr.capacitor_updater;
|
|
8
8
|
|
|
9
9
|
import androidx.annotation.NonNull;
|
|
10
|
-
import com.google.gson.annotations.SerializedName;
|
|
11
10
|
import java.util.Objects;
|
|
12
11
|
|
|
13
12
|
public class DelayCondition {
|
|
14
13
|
|
|
15
|
-
@SerializedName("kind")
|
|
16
14
|
private DelayUntilNext kind;
|
|
17
15
|
|
|
18
|
-
@SerializedName("value")
|
|
19
16
|
private String value;
|
|
20
17
|
|
|
21
18
|
public DelayCondition(DelayUntilNext kind, String value) {
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
package ee.forgr.capacitor_updater;
|
|
2
|
+
|
|
3
|
+
import android.content.SharedPreferences;
|
|
4
|
+
import io.github.g00fy2.versioncompare.Version;
|
|
5
|
+
import java.text.SimpleDateFormat;
|
|
6
|
+
import java.util.ArrayList;
|
|
7
|
+
import java.util.Date;
|
|
8
|
+
import org.json.JSONArray;
|
|
9
|
+
import org.json.JSONException;
|
|
10
|
+
import org.json.JSONObject;
|
|
11
|
+
|
|
12
|
+
public class DelayUpdateUtils {
|
|
13
|
+
|
|
14
|
+
private final Logger logger;
|
|
15
|
+
|
|
16
|
+
public static final String DELAY_CONDITION_PREFERENCES = "DELAY_CONDITION_PREFERENCES_CAPGO";
|
|
17
|
+
public static final String BACKGROUND_TIMESTAMP_KEY = "BACKGROUND_TIMESTAMP_KEY_CAPGO";
|
|
18
|
+
|
|
19
|
+
private final SharedPreferences prefs;
|
|
20
|
+
private final SharedPreferences.Editor editor;
|
|
21
|
+
private final Version currentVersionNative;
|
|
22
|
+
|
|
23
|
+
public DelayUpdateUtils(SharedPreferences prefs, SharedPreferences.Editor editor, Version currentVersionNative, Logger logger) {
|
|
24
|
+
this.prefs = prefs;
|
|
25
|
+
this.editor = editor;
|
|
26
|
+
this.currentVersionNative = currentVersionNative;
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public enum CancelDelaySource {
|
|
31
|
+
KILLED,
|
|
32
|
+
BACKGROUND,
|
|
33
|
+
FOREGROUND
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public void checkCancelDelay(CancelDelaySource source) {
|
|
37
|
+
String delayUpdatePreferences = prefs.getString(DELAY_CONDITION_PREFERENCES, "[]");
|
|
38
|
+
ArrayList<DelayCondition> delayConditionList = parseDelayConditions(delayUpdatePreferences);
|
|
39
|
+
ArrayList<DelayCondition> delayConditionListToKeep = new ArrayList<>(delayConditionList.size());
|
|
40
|
+
int index = 0;
|
|
41
|
+
|
|
42
|
+
for (DelayCondition condition : delayConditionList) {
|
|
43
|
+
DelayUntilNext kind = condition.getKind();
|
|
44
|
+
String value = condition.getValue();
|
|
45
|
+
switch (kind) {
|
|
46
|
+
case DelayUntilNext.background:
|
|
47
|
+
if (source == CancelDelaySource.FOREGROUND) {
|
|
48
|
+
long backgroundedAt = getBackgroundTimestamp();
|
|
49
|
+
long now = System.currentTimeMillis();
|
|
50
|
+
long delta = Math.max(0, now - backgroundedAt);
|
|
51
|
+
long longValue = 0L;
|
|
52
|
+
try {
|
|
53
|
+
longValue = Long.parseLong(value);
|
|
54
|
+
} catch (NumberFormatException e) {
|
|
55
|
+
logger.error(
|
|
56
|
+
"Background condition (value: " +
|
|
57
|
+
value +
|
|
58
|
+
") had an invalid value at index " +
|
|
59
|
+
index +
|
|
60
|
+
". We will likely remove it."
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (delta > longValue) {
|
|
65
|
+
logger.info(
|
|
66
|
+
"Background condition (value: " +
|
|
67
|
+
value +
|
|
68
|
+
") deleted at index " +
|
|
69
|
+
index +
|
|
70
|
+
". Delta: " +
|
|
71
|
+
delta +
|
|
72
|
+
", longValue: " +
|
|
73
|
+
longValue
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
delayConditionListToKeep.add(condition);
|
|
78
|
+
logger.info(
|
|
79
|
+
"Background delay (value: " +
|
|
80
|
+
value +
|
|
81
|
+
") condition kept at index " +
|
|
82
|
+
index +
|
|
83
|
+
" (source: " +
|
|
84
|
+
source.toString() +
|
|
85
|
+
")"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
case DelayUntilNext.kill:
|
|
90
|
+
if (source == CancelDelaySource.KILLED) {
|
|
91
|
+
logger.info("Kill delay (value: " + value + ") condition removed at index " + index + " after app kill");
|
|
92
|
+
} else {
|
|
93
|
+
delayConditionListToKeep.add(condition);
|
|
94
|
+
logger.info(
|
|
95
|
+
"Kill delay (value: " + value + ") condition kept at index " + index + " (source: " + source.toString() + ")"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
case DelayUntilNext.date:
|
|
100
|
+
if (!"".equals(value)) {
|
|
101
|
+
try {
|
|
102
|
+
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
|
|
103
|
+
Date date = sdf.parse(value);
|
|
104
|
+
assert date != null;
|
|
105
|
+
if (new Date().compareTo(date) > 0) {
|
|
106
|
+
logger.info("Date delay (value: " + value + ") condition removed due to expired date at index " + index);
|
|
107
|
+
} else {
|
|
108
|
+
delayConditionListToKeep.add(condition);
|
|
109
|
+
logger.info("Date delay (value: " + value + ") condition kept at index " + index);
|
|
110
|
+
}
|
|
111
|
+
} catch (final Exception e) {
|
|
112
|
+
logger.error(
|
|
113
|
+
"Date delay (value: " +
|
|
114
|
+
value +
|
|
115
|
+
") condition removed due to parsing issue at index " +
|
|
116
|
+
index +
|
|
117
|
+
" " +
|
|
118
|
+
e.getMessage()
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
logger.debug("Date delay (value: " + value + ") condition removed due to empty value at index " + index);
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
case DelayUntilNext.nativeVersion:
|
|
126
|
+
if (!"".equals(value)) {
|
|
127
|
+
try {
|
|
128
|
+
final Version versionLimit = new Version(value);
|
|
129
|
+
if (this.currentVersionNative.isAtLeast(versionLimit)) {
|
|
130
|
+
logger.info(
|
|
131
|
+
"Native version delay (value: " + value + ") condition removed due to above limit at index " + index
|
|
132
|
+
);
|
|
133
|
+
} else {
|
|
134
|
+
delayConditionListToKeep.add(condition);
|
|
135
|
+
logger.info("Native version delay (value: " + value + ") condition kept at index " + index);
|
|
136
|
+
}
|
|
137
|
+
} catch (final Exception e) {
|
|
138
|
+
logger.error(
|
|
139
|
+
"Native version delay (value: " +
|
|
140
|
+
value +
|
|
141
|
+
") condition removed due to parsing issue at index " +
|
|
142
|
+
index +
|
|
143
|
+
" " +
|
|
144
|
+
e.getMessage()
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
logger.debug("Native version delay (value: " + value + ") condition removed due to empty value at index " + index);
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
index++;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (delayConditionListToKeep.isEmpty()) {
|
|
156
|
+
this.cancelDelay("checkCancelDelay");
|
|
157
|
+
} else {
|
|
158
|
+
this.setMultiDelay(convertDelayConditionsToJson(delayConditionListToKeep));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public ArrayList<DelayCondition> parseDelayConditions(String json) {
|
|
163
|
+
ArrayList<DelayCondition> conditions = new ArrayList<>();
|
|
164
|
+
if (json == null || json.isEmpty()) {
|
|
165
|
+
return conditions;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
JSONArray array = new JSONArray(json);
|
|
169
|
+
for (int i = 0; i < array.length(); i++) {
|
|
170
|
+
JSONObject item = array.optJSONObject(i);
|
|
171
|
+
if (item == null) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
String kindValue = item.optString("kind", "");
|
|
175
|
+
String value = item.optString("value", "");
|
|
176
|
+
if (kindValue.isEmpty()) {
|
|
177
|
+
logger.warn("Delay condition missing kind at index " + i);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
DelayUntilNext kind = DelayUntilNext.valueOf(kindValue);
|
|
182
|
+
conditions.add(new DelayCondition(kind, value));
|
|
183
|
+
} catch (IllegalArgumentException e) {
|
|
184
|
+
logger.warn("Unknown delay condition kind '" + kindValue + "' at index " + i);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch (JSONException e) {
|
|
188
|
+
logger.error("Failed to parse delay conditions: " + e.getMessage());
|
|
189
|
+
}
|
|
190
|
+
return conditions;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private String convertDelayConditionsToJson(ArrayList<DelayCondition> conditions) {
|
|
194
|
+
JSONArray array = new JSONArray();
|
|
195
|
+
for (DelayCondition condition : conditions) {
|
|
196
|
+
try {
|
|
197
|
+
JSONObject obj = new JSONObject();
|
|
198
|
+
obj.put("kind", condition.getKind().name());
|
|
199
|
+
obj.put("value", condition.getValue());
|
|
200
|
+
array.put(obj);
|
|
201
|
+
} catch (JSONException e) {
|
|
202
|
+
logger.error("Failed to serialize delay condition: " + e.getMessage());
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return array.toString();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
public Boolean setMultiDelay(String delayConditions) {
|
|
209
|
+
try {
|
|
210
|
+
this.editor.putString(DELAY_CONDITION_PREFERENCES, delayConditions);
|
|
211
|
+
this.editor.commit();
|
|
212
|
+
logger.info("Delay update saved");
|
|
213
|
+
return true;
|
|
214
|
+
} catch (final Exception e) {
|
|
215
|
+
logger.error("Failed to delay update, [Error calling '_setMultiDelay()'] " + e.getMessage());
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
public void setBackgroundTimestamp(long backgroundTimestamp) {
|
|
221
|
+
try {
|
|
222
|
+
this.editor.putLong(BACKGROUND_TIMESTAMP_KEY, backgroundTimestamp);
|
|
223
|
+
this.editor.commit();
|
|
224
|
+
logger.info("Background timestamp set");
|
|
225
|
+
} catch (final Exception e) {
|
|
226
|
+
logger.error("Failed to delay update, [Error calling '_setBackgroundTimestamp()'] " + e.getMessage());
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public void unsetBackgroundTimestamp() {
|
|
231
|
+
try {
|
|
232
|
+
this.editor.remove(BACKGROUND_TIMESTAMP_KEY);
|
|
233
|
+
this.editor.commit();
|
|
234
|
+
logger.info("Background timestamp unset");
|
|
235
|
+
} catch (final Exception e) {
|
|
236
|
+
logger.error("Failed to delay update, [Error calling '_unsetBackgroundTimestamp()'] " + e.getMessage());
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private long getBackgroundTimestamp() {
|
|
241
|
+
try {
|
|
242
|
+
return this.prefs.getLong(BACKGROUND_TIMESTAMP_KEY, 0);
|
|
243
|
+
} catch (final Exception e) {
|
|
244
|
+
logger.error("Failed to delay update, [Error calling '_getBackgroundTimestamp()'] " + e.getMessage());
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public boolean cancelDelay(String source) {
|
|
250
|
+
try {
|
|
251
|
+
this.editor.remove(DELAY_CONDITION_PREFERENCES);
|
|
252
|
+
this.editor.commit();
|
|
253
|
+
logger.info("All delays canceled from " + source);
|
|
254
|
+
return true;
|
|
255
|
+
} catch (final Exception e) {
|
|
256
|
+
logger.error("Failed to cancel update delay " + e.getMessage());
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|