@capgo/capacitor-updater 8.49.0 → 8.49.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -6
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +110 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +44 -5
- package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +45 -30
- package/android/src/main/java/ee/forgr/capacitor_updater/ThreeFingerPinchDetector.java +217 -63
- package/dist/docs.json +3 -3
- package/dist/esm/definitions.d.ts +15 -6
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +87 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +32 -6
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +22 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1246,6 +1246,11 @@ Assign this device to a specific update channel at runtime.
|
|
|
1246
1246
|
Channels allow you to distribute different bundle versions to different groups of users
|
|
1247
1247
|
(e.g., "production", "beta", "staging"). This method switches the device to a new channel.
|
|
1248
1248
|
|
|
1249
|
+
**Device Override UI:** `setChannel()` validates the channel with the backend, then stores the
|
|
1250
|
+
selected channel locally on the device. It does not create or update a backend Device Override,
|
|
1251
|
+
so the device will not appear as overridden in the Capgo dashboard. Only assignments created
|
|
1252
|
+
from the dashboard or the Public API are shown in the Device Override UI.
|
|
1253
|
+
|
|
1249
1254
|
**Requirements:**
|
|
1250
1255
|
- The target channel must allow self-assignment (configured in your Capgo dashboard or backend)
|
|
1251
1256
|
- The backend may accept or reject the request based on channel settings
|
|
@@ -1272,7 +1277,8 @@ CapacitorUpdater.addListener('channelPrivate', (data) => {
|
|
|
1272
1277
|
});
|
|
1273
1278
|
```
|
|
1274
1279
|
|
|
1275
|
-
This sends a request to the Capgo backend
|
|
1280
|
+
This sends a request to the Capgo backend to validate the specified channel, then stores the
|
|
1281
|
+
channel locally on the device.
|
|
1276
1282
|
|
|
1277
1283
|
| Param | Type | Description |
|
|
1278
1284
|
| ------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -1291,11 +1297,12 @@ This sends a request to the Capgo backend linking your device ID to the specifie
|
|
|
1291
1297
|
unsetChannel(options: UnsetChannelOptions) => Promise<void>
|
|
1292
1298
|
```
|
|
1293
1299
|
|
|
1294
|
-
Remove the
|
|
1300
|
+
Remove the plugin-managed local channel assignment and return to the default channel.
|
|
1295
1301
|
|
|
1296
|
-
This
|
|
1302
|
+
This clears only the channel stored locally by {@link setChannel}; it does not delete Dashboard or Public API Device Override records. After the local assignment is cleared, normal channel precedence applies:
|
|
1303
|
+
- An existing Dashboard or Public API Device Override, if one exists
|
|
1297
1304
|
- The {@link PluginsConfig.CapacitorUpdater.defaultChannel} if configured, or
|
|
1298
|
-
- Your backend
|
|
1305
|
+
- Your backend default channel for this app
|
|
1299
1306
|
|
|
1300
1307
|
Use this when:
|
|
1301
1308
|
- Users opt out of beta/testing programs
|
|
@@ -1444,7 +1451,7 @@ It's automatically generated and stored securely by the plugin.
|
|
|
1444
1451
|
**Privacy & Security characteristics:**
|
|
1445
1452
|
- Generated as a UUID (not based on hardware identifiers)
|
|
1446
1453
|
- Stored securely in platform-specific secure storage
|
|
1447
|
-
- Android:
|
|
1454
|
+
- Android: mirrored into backup-restorable app preferences for reinstall restore
|
|
1448
1455
|
- iOS: Keychain with `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`
|
|
1449
1456
|
- Not synced to cloud (iOS)
|
|
1450
1457
|
- Follows Apple and Google privacy best practices
|
|
@@ -1452,7 +1459,9 @@ It's automatically generated and stored securely by the plugin.
|
|
|
1452
1459
|
|
|
1453
1460
|
**Persistence:**
|
|
1454
1461
|
The device ID persists across app reinstalls to maintain consistent device identity
|
|
1455
|
-
for update tracking and analytics.
|
|
1462
|
+
for update tracking and analytics when platform storage is preserved. On Android,
|
|
1463
|
+
apps with custom backup rules must keep the plugin app preferences eligible for
|
|
1464
|
+
backup/restore; disabling Android backup or clearing app data creates a new ID.
|
|
1456
1465
|
|
|
1457
1466
|
Use this to:
|
|
1458
1467
|
- Debug update delivery issues (check what ID the server sees)
|
|
@@ -119,6 +119,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
119
119
|
private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
|
|
120
120
|
private static final String LAST_REPORTED_APP_EXIT_TIMESTAMP_PREF_KEY = "CapacitorUpdater.lastReportedAppExitTimestamp";
|
|
121
121
|
private static final String LAST_WEBVIEW_RENDER_PROCESS_GONE_PREF_KEY = "CapacitorUpdater.lastWebViewRenderProcessGone";
|
|
122
|
+
private static final String LAST_VERSION_OS_PREF_KEY = "CapacitorUpdater.lastVersionOs";
|
|
123
|
+
private static final String LAST_VERSION_BUILD_PREF_KEY = "CapacitorUpdater.lastVersionBuild";
|
|
124
|
+
private static final String LAST_VERSION_CODE_PREF_KEY = "CapacitorUpdater.lastVersionCode";
|
|
125
|
+
private static final String OS_VERSION_CHANGED_ACTION = "os_version_changed";
|
|
126
|
+
private static final String NATIVE_APP_VERSION_CHANGED_ACTION = "native_app_version_changed";
|
|
122
127
|
private static final String SPLASH_SCREEN_PLUGIN_ID = "SplashScreen";
|
|
123
128
|
private static final int SPLASH_SCREEN_RETRY_DELAY_MS = 100;
|
|
124
129
|
private static final int SPLASH_SCREEN_MAX_RETRIES = 20;
|
|
@@ -137,7 +142,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
137
142
|
static final int APPLICATION_EXIT_REASON_USER_REQUESTED = 10;
|
|
138
143
|
static final int APPLICATION_EXIT_REASON_DEPENDENCY_DIED = 12;
|
|
139
144
|
|
|
140
|
-
private final String pluginVersion = "8.49.
|
|
145
|
+
private final String pluginVersion = "8.49.2";
|
|
141
146
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
142
147
|
|
|
143
148
|
private SharedPreferences.Editor editor;
|
|
@@ -857,6 +862,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
857
862
|
this.clearPreviewSessionForNativeBuildChange();
|
|
858
863
|
}
|
|
859
864
|
this.leavePreviewSessionForLaunchIntentIfNeeded();
|
|
865
|
+
this.reportNativeVersionStatsIfChanged();
|
|
860
866
|
this.reportPreviousAppExitReasons();
|
|
861
867
|
this.reportPreviousWebViewRenderProcessGone();
|
|
862
868
|
this.installWebViewStatsReporter();
|
|
@@ -1299,6 +1305,109 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1299
1305
|
return !lastKnownVersion.isEmpty() && !lastKnownVersion.equals(this.currentBuildVersion);
|
|
1300
1306
|
}
|
|
1301
1307
|
|
|
1308
|
+
void reportNativeVersionStatsIfChanged() {
|
|
1309
|
+
if (this.implementation == null || this.prefs == null || this.editor == null) {
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
this.reportNativeVersionStatsIfChanged(
|
|
1314
|
+
this.implementation.versionBuild,
|
|
1315
|
+
this.implementation.versionCode,
|
|
1316
|
+
this.implementation.versionOs
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
void reportNativeVersionStatsIfChanged(
|
|
1321
|
+
final String currentVersionBuild,
|
|
1322
|
+
final String currentVersionCode,
|
|
1323
|
+
final String currentVersionOs
|
|
1324
|
+
) {
|
|
1325
|
+
if (this.implementation == null || this.prefs == null || this.editor == null) {
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
final String normalizedVersionBuild = this.normalizedStatsValue(currentVersionBuild);
|
|
1330
|
+
final String normalizedVersionCode = this.normalizedStatsValue(currentVersionCode);
|
|
1331
|
+
final String normalizedVersionOs = this.normalizedStatsValue(currentVersionOs);
|
|
1332
|
+
final String previousVersionOs = this.prefs.getString(LAST_VERSION_OS_PREF_KEY, "");
|
|
1333
|
+
final String previousVersionBuild = this.prefs.getString(LAST_VERSION_BUILD_PREF_KEY, "");
|
|
1334
|
+
final String previousVersionCode = this.prefs.getString(LAST_VERSION_CODE_PREF_KEY, "");
|
|
1335
|
+
final boolean osVersionChanged =
|
|
1336
|
+
!normalizedVersionOs.isEmpty() &&
|
|
1337
|
+
previousVersionOs != null &&
|
|
1338
|
+
!previousVersionOs.isEmpty() &&
|
|
1339
|
+
!previousVersionOs.equals(normalizedVersionOs);
|
|
1340
|
+
|
|
1341
|
+
if (osVersionChanged) {
|
|
1342
|
+
final Map<String, String> metadata = new HashMap<>();
|
|
1343
|
+
metadata.put("previous_version_os", previousVersionOs);
|
|
1344
|
+
metadata.put("current_version_os", normalizedVersionOs);
|
|
1345
|
+
this.implementation.sendStats(
|
|
1346
|
+
OS_VERSION_CHANGED_ACTION,
|
|
1347
|
+
this.implementation.getCurrentBundle().getVersionName(),
|
|
1348
|
+
"",
|
|
1349
|
+
metadata,
|
|
1350
|
+
() -> this.persistLastVersionOs(normalizedVersionOs)
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
final boolean hasPreviousNativeVersion =
|
|
1355
|
+
(previousVersionBuild != null && !previousVersionBuild.isEmpty()) ||
|
|
1356
|
+
(previousVersionCode != null && !previousVersionCode.isEmpty());
|
|
1357
|
+
final boolean nativeVersionChanged =
|
|
1358
|
+
hasPreviousNativeVersion &&
|
|
1359
|
+
(!Objects.equals(previousVersionBuild, normalizedVersionBuild) || !Objects.equals(previousVersionCode, normalizedVersionCode));
|
|
1360
|
+
|
|
1361
|
+
if (nativeVersionChanged) {
|
|
1362
|
+
final Map<String, String> metadata = new HashMap<>();
|
|
1363
|
+
metadata.put("previous_version_build", previousVersionBuild == null ? "" : previousVersionBuild);
|
|
1364
|
+
metadata.put("current_version_build", normalizedVersionBuild);
|
|
1365
|
+
metadata.put("previous_version_code", previousVersionCode == null ? "" : previousVersionCode);
|
|
1366
|
+
metadata.put("current_version_code", normalizedVersionCode);
|
|
1367
|
+
this.implementation.sendStats(
|
|
1368
|
+
NATIVE_APP_VERSION_CHANGED_ACTION,
|
|
1369
|
+
this.implementation.getCurrentBundle().getVersionName(),
|
|
1370
|
+
"",
|
|
1371
|
+
metadata,
|
|
1372
|
+
() -> this.persistLastNativeAppVersion(normalizedVersionBuild, normalizedVersionCode)
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
if (!osVersionChanged || !nativeVersionChanged) {
|
|
1377
|
+
if (!osVersionChanged) {
|
|
1378
|
+
this.editor.putString(LAST_VERSION_OS_PREF_KEY, normalizedVersionOs);
|
|
1379
|
+
}
|
|
1380
|
+
if (!nativeVersionChanged) {
|
|
1381
|
+
this.editor.putString(LAST_VERSION_BUILD_PREF_KEY, normalizedVersionBuild);
|
|
1382
|
+
this.editor.putString(LAST_VERSION_CODE_PREF_KEY, normalizedVersionCode);
|
|
1383
|
+
}
|
|
1384
|
+
this.editor.apply();
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
private void persistLastVersionOs(final String versionOs) {
|
|
1389
|
+
if (this.editor == null) {
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
this.editor.putString(LAST_VERSION_OS_PREF_KEY, versionOs);
|
|
1394
|
+
this.editor.apply();
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
private void persistLastNativeAppVersion(final String versionBuild, final String versionCode) {
|
|
1398
|
+
if (this.editor == null) {
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
this.editor.putString(LAST_VERSION_BUILD_PREF_KEY, versionBuild);
|
|
1403
|
+
this.editor.putString(LAST_VERSION_CODE_PREF_KEY, versionCode);
|
|
1404
|
+
this.editor.apply();
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
private String normalizedStatsValue(final String value) {
|
|
1408
|
+
return value == null ? "" : value;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1302
1411
|
private void reportPreviousAppExitReasons() {
|
|
1303
1412
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || this.implementation == null || this.implementation.statsUrl.isEmpty()) {
|
|
1304
1413
|
return;
|
|
@@ -102,11 +102,22 @@ public class CapgoUpdater {
|
|
|
102
102
|
private static volatile boolean rateLimitStatisticSent = false;
|
|
103
103
|
|
|
104
104
|
// Stats batching - queue events and send max once per second
|
|
105
|
-
private final List<
|
|
105
|
+
private final List<QueuedStatsEvent> statsQueue = new CopyOnWriteArrayList<>();
|
|
106
106
|
private final ScheduledExecutorService statsScheduler = Executors.newSingleThreadScheduledExecutor();
|
|
107
107
|
private ScheduledFuture<?> statsFlushTask = null;
|
|
108
108
|
private static final long STATS_FLUSH_INTERVAL_MS = 1000;
|
|
109
109
|
|
|
110
|
+
private static final class QueuedStatsEvent {
|
|
111
|
+
|
|
112
|
+
private final JSONObject event;
|
|
113
|
+
private final Runnable onSent;
|
|
114
|
+
|
|
115
|
+
private QueuedStatsEvent(final JSONObject event, final Runnable onSent) {
|
|
116
|
+
this.event = event;
|
|
117
|
+
this.onSent = onSent;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
110
121
|
private final Map<String, CompletableFuture<BundleInfo>> downloadFutures = new ConcurrentHashMap<>();
|
|
111
122
|
private final ExecutorService io = Executors.newSingleThreadExecutor();
|
|
112
123
|
|
|
@@ -2086,6 +2097,16 @@ public class CapgoUpdater {
|
|
|
2086
2097
|
}
|
|
2087
2098
|
|
|
2088
2099
|
public void sendStats(final String action, final String versionName, final String oldVersionName, final Map<String, String> metadata) {
|
|
2100
|
+
this.sendStats(action, versionName, oldVersionName, metadata, null);
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
public void sendStats(
|
|
2104
|
+
final String action,
|
|
2105
|
+
final String versionName,
|
|
2106
|
+
final String oldVersionName,
|
|
2107
|
+
final Map<String, String> metadata,
|
|
2108
|
+
final Runnable onSent
|
|
2109
|
+
) {
|
|
2089
2110
|
if (this.previewSession) {
|
|
2090
2111
|
if (logger != null) {
|
|
2091
2112
|
logger.debug("Skipping sendStats during preview session.");
|
|
@@ -2120,7 +2141,7 @@ public class CapgoUpdater {
|
|
|
2120
2141
|
return;
|
|
2121
2142
|
}
|
|
2122
2143
|
|
|
2123
|
-
statsQueue.add(json);
|
|
2144
|
+
statsQueue.add(new QueuedStatsEvent(json, onSent));
|
|
2124
2145
|
ensureStatsTimerStarted();
|
|
2125
2146
|
}
|
|
2126
2147
|
|
|
@@ -2147,7 +2168,7 @@ public class CapgoUpdater {
|
|
|
2147
2168
|
}
|
|
2148
2169
|
|
|
2149
2170
|
// Copy and clear the queue atomically using synchronized block
|
|
2150
|
-
List<
|
|
2171
|
+
List<QueuedStatsEvent> eventsToSend;
|
|
2151
2172
|
synchronized (statsQueue) {
|
|
2152
2173
|
if (statsQueue.isEmpty()) {
|
|
2153
2174
|
return;
|
|
@@ -2157,8 +2178,8 @@ public class CapgoUpdater {
|
|
|
2157
2178
|
}
|
|
2158
2179
|
|
|
2159
2180
|
JSONArray jsonArray = new JSONArray();
|
|
2160
|
-
for (
|
|
2161
|
-
jsonArray.put(event);
|
|
2181
|
+
for (QueuedStatsEvent queuedEvent : eventsToSend) {
|
|
2182
|
+
jsonArray.put(queuedEvent.event);
|
|
2162
2183
|
}
|
|
2163
2184
|
|
|
2164
2185
|
Request request = new Request.Builder()
|
|
@@ -2186,6 +2207,7 @@ public class CapgoUpdater {
|
|
|
2186
2207
|
if (response.isSuccessful()) {
|
|
2187
2208
|
logger.info("Stats batch sent successfully");
|
|
2188
2209
|
logger.debug("Sent " + eventCount + " events");
|
|
2210
|
+
runStatsCallbacks(eventsToSend);
|
|
2189
2211
|
} else {
|
|
2190
2212
|
logger.error("Error sending stats batch");
|
|
2191
2213
|
logger.debug("Response code: " + response.code());
|
|
@@ -2196,6 +2218,23 @@ public class CapgoUpdater {
|
|
|
2196
2218
|
);
|
|
2197
2219
|
}
|
|
2198
2220
|
|
|
2221
|
+
private void runStatsCallbacks(final List<QueuedStatsEvent> sentEvents) {
|
|
2222
|
+
for (final QueuedStatsEvent sentEvent : sentEvents) {
|
|
2223
|
+
if (sentEvent.onSent == null) {
|
|
2224
|
+
continue;
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
try {
|
|
2228
|
+
sentEvent.onSent.run();
|
|
2229
|
+
} catch (Exception e) {
|
|
2230
|
+
if (logger != null) {
|
|
2231
|
+
logger.error("Error running stats sent callback");
|
|
2232
|
+
logger.debug("Error: " + e.getMessage());
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2199
2238
|
public BundleInfo getBundleInfo(final String id) {
|
|
2200
2239
|
String trueId = BundleInfo.VERSION_UNKNOWN;
|
|
2201
2240
|
if (id != null) {
|
|
@@ -26,10 +26,10 @@ import javax.crypto.spec.GCMParameterSpec;
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Helper class to manage device ID persistence across app installations.
|
|
29
|
-
* Uses Android Keystore
|
|
29
|
+
* Uses Android Keystore-backed storage and backup-restorable SharedPreferences.
|
|
30
30
|
*
|
|
31
|
-
* The device ID is a random UUID stored in
|
|
32
|
-
*
|
|
31
|
+
* The device ID is a random UUID stored in encrypted preferences and mirrored
|
|
32
|
+
* to appUUID so Android backup/restore can keep it across reinstalls.
|
|
33
33
|
*/
|
|
34
34
|
public class DeviceIdHelper {
|
|
35
35
|
|
|
@@ -45,10 +45,10 @@ public class DeviceIdHelper {
|
|
|
45
45
|
* Gets or creates a device ID that persists across reinstalls.
|
|
46
46
|
*
|
|
47
47
|
* This method:
|
|
48
|
-
* 1. First checks for an existing ID in Keystore-encrypted storage
|
|
49
|
-
* 2. Falls back to legacy SharedPreferences
|
|
48
|
+
* 1. First checks for an existing ID in Keystore-encrypted storage
|
|
49
|
+
* 2. Falls back to legacy SharedPreferences restored by Android backup
|
|
50
50
|
* 3. Generates a new UUID if neither exists
|
|
51
|
-
* 4. Stores the ID in
|
|
51
|
+
* 4. Stores the ID synchronously in both stores for future use
|
|
52
52
|
*
|
|
53
53
|
* @param context Application context
|
|
54
54
|
* @param legacyPrefs Legacy SharedPreferences (for migration)
|
|
@@ -60,7 +60,9 @@ public class DeviceIdHelper {
|
|
|
60
60
|
String deviceId = getDeviceIdFromKeystore(context);
|
|
61
61
|
|
|
62
62
|
if (deviceId != null && !deviceId.isEmpty()) {
|
|
63
|
-
|
|
63
|
+
deviceId = deviceId.toLowerCase();
|
|
64
|
+
saveLegacyDeviceId(legacyPrefs, deviceId);
|
|
65
|
+
return deviceId;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
// Migration: Check legacy SharedPreferences for existing device ID
|
|
@@ -74,7 +76,8 @@ public class DeviceIdHelper {
|
|
|
74
76
|
// Ensure lowercase for consistency
|
|
75
77
|
deviceId = deviceId.toLowerCase();
|
|
76
78
|
|
|
77
|
-
// Save to Keystore storage
|
|
79
|
+
// Save to backup-restorable preferences and Keystore storage
|
|
80
|
+
saveLegacyDeviceId(legacyPrefs, deviceId);
|
|
78
81
|
saveDeviceIdToKeystore(context, deviceId);
|
|
79
82
|
|
|
80
83
|
return deviceId;
|
|
@@ -90,31 +93,35 @@ public class DeviceIdHelper {
|
|
|
90
93
|
* @param context Application context
|
|
91
94
|
* @return Device ID string or null if not found
|
|
92
95
|
*/
|
|
93
|
-
private static String getDeviceIdFromKeystore(Context context)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
private static String getDeviceIdFromKeystore(Context context) {
|
|
97
|
+
try {
|
|
98
|
+
SharedPreferences prefs = context.getSharedPreferences(DEVICE_ID_PREFS, Context.MODE_PRIVATE);
|
|
99
|
+
String encryptedDeviceId = prefs.getString(DEVICE_ID_KEY, null);
|
|
100
|
+
String ivString = prefs.getString(IV_KEY, null);
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
if (encryptedDeviceId == null || ivString == null) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
// Get the encryption key from Keystore
|
|
107
|
+
SecretKey key = getOrCreateKey();
|
|
108
|
+
if (key == null) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
107
111
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
// Decrypt the device ID
|
|
113
|
+
byte[] encryptedBytes = android.util.Base64.decode(encryptedDeviceId, android.util.Base64.DEFAULT);
|
|
114
|
+
byte[] iv = android.util.Base64.decode(ivString, android.util.Base64.DEFAULT);
|
|
111
115
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
|
117
|
+
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
|
118
|
+
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
|
115
119
|
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
|
|
121
|
+
return new String(decryptedBytes, StandardCharsets.UTF_8);
|
|
122
|
+
} catch (Exception e) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
/**
|
|
@@ -146,7 +153,7 @@ public class DeviceIdHelper {
|
|
|
146
153
|
.edit()
|
|
147
154
|
.putString(DEVICE_ID_KEY, android.util.Base64.encodeToString(encryptedBytes, android.util.Base64.DEFAULT))
|
|
148
155
|
.putString(IV_KEY, android.util.Base64.encodeToString(iv, android.util.Base64.DEFAULT))
|
|
149
|
-
.
|
|
156
|
+
.commit();
|
|
150
157
|
}
|
|
151
158
|
|
|
152
159
|
/**
|
|
@@ -207,9 +214,17 @@ public class DeviceIdHelper {
|
|
|
207
214
|
|
|
208
215
|
if (deviceId == null || deviceId.isEmpty()) {
|
|
209
216
|
deviceId = UUID.randomUUID().toString();
|
|
210
|
-
legacyPrefs
|
|
217
|
+
saveLegacyDeviceId(legacyPrefs, deviceId);
|
|
211
218
|
}
|
|
212
219
|
|
|
213
220
|
return deviceId.toLowerCase();
|
|
214
221
|
}
|
|
222
|
+
|
|
223
|
+
private static void saveLegacyDeviceId(SharedPreferences legacyPrefs, String deviceId) {
|
|
224
|
+
if (legacyPrefs == null || deviceId == null || deviceId.isEmpty()) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
legacyPrefs.edit().putString(LEGACY_PREFS_KEY, deviceId).commit();
|
|
229
|
+
}
|
|
215
230
|
}
|