@hot-updater/react-native 1.0.7 → 1.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.
Files changed (144) hide show
  1. package/HotUpdater.podspec +14 -2
  2. package/Package.swift +24 -3
  3. package/android/build.gradle +2 -6
  4. package/android/hot-updater-fingerprint.gradle +135 -0
  5. package/android/src/main/cpp/HotUpdaterRecovery.cpp +291 -21
  6. package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +176 -15
  7. package/android/src/main/java/com/hotupdater/ArtifactFileResolver.kt +20 -8
  8. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +145 -31
  9. package/android/src/main/java/com/hotupdater/CohortService.kt +10 -2
  10. package/android/src/main/java/com/hotupdater/FileManagerService.kt +3 -1
  11. package/android/src/main/java/com/hotupdater/HotUpdaterConfig.kt +249 -21
  12. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +8 -4
  13. package/android/src/main/java/com/hotupdater/HotUpdaterHeldContent.kt +92 -0
  14. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +319 -113
  15. package/android/src/main/java/com/hotupdater/HotUpdaterInstance.kt +87 -0
  16. package/android/src/main/java/com/hotupdater/HotUpdaterInstanceEnvironment.kt +323 -0
  17. package/android/src/main/java/com/hotupdater/HotUpdaterMainThread.kt +46 -0
  18. package/android/src/main/java/com/hotupdater/HotUpdaterProcess.kt +30 -0
  19. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryAttribution.kt +492 -0
  20. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +1541 -159
  21. package/android/src/main/java/com/hotupdater/HotUpdaterRecoverySlots.kt +137 -0
  22. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +4 -0
  23. package/android/src/main/java/com/hotupdater/HotUpdaterRuntimeInstance.kt +53 -0
  24. package/android/src/main/java/com/hotupdater/HotUpdaterUpdateCheck.kt +149 -0
  25. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +12 -2
  26. package/android/src/main/java/com/hotupdater/NativeConfigUtils.kt +5 -6
  27. package/android/src/main/java/com/hotupdater/NativeUpdateCheck.kt +172 -0
  28. package/android/src/main/java/com/hotupdater/NativeUpdateChecker.kt +1007 -0
  29. package/android/src/main/java/com/hotupdater/NativeUpdateEventQueue.kt +111 -0
  30. package/android/src/main/java/com/hotupdater/OkHttpUpdateCheckTransport.kt +88 -0
  31. package/android/src/main/java/com/hotupdater/ReleaseCatalog.kt +491 -0
  32. package/android/src/main/java/com/hotupdater/ReleaseCatalogClient.kt +405 -0
  33. package/android/src/main/java/com/hotupdater/ReleaseSelection.kt +474 -0
  34. package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +32 -10
  35. package/android/src/main/java/com/hotupdater/StrictJson.kt +392 -0
  36. package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +24 -9
  37. package/android/src/main/java/com/hotupdater/WeakIdentitySet.kt +76 -0
  38. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BitReader.java +314 -0
  39. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliError.java +47 -0
  40. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliInputStream.java +174 -0
  41. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliRuntimeException.java +21 -0
  42. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Context.java +58 -0
  43. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Decode.java +1574 -0
  44. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Decoder.java +72 -0
  45. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Dictionary.java +103 -0
  46. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/DictionaryData.java +82 -0
  47. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Huffman.java +143 -0
  48. package/{LICENSE → android/src/main/java/com/hotupdater/vendor/brotli/dec/LICENSE} +6 -20
  49. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/README.md +39 -0
  50. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/State.java +100 -0
  51. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Transform.java +240 -0
  52. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Utils.java +163 -0
  53. package/android/src/main/resources/META-INF/LICENSE-brotli +19 -0
  54. package/android/src/newarch/HotUpdater.kt +53 -15
  55. package/android/src/newarch/HotUpdaterModule.kt +42 -17
  56. package/android/src/newarch/JavaScriptExceptionHooks.kt +45 -8
  57. package/android/src/oldarch/HotUpdater.kt +51 -12
  58. package/android/src/oldarch/HotUpdaterModule.kt +43 -17
  59. package/android/src/oldarch/HotUpdaterSpec.kt +2 -0
  60. package/android/src/oldarch/JavaScriptExceptionHooks.kt +25 -16
  61. package/ios/HotUpdater/Interface/HotUpdaterInterface.h +13 -0
  62. package/ios/HotUpdater/Internal/ArtifactFileResolver.swift +38 -7
  63. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +10 -4
  64. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +186 -48
  65. package/ios/HotUpdater/Internal/CohortService.swift +8 -0
  66. package/ios/HotUpdater/Internal/HotUpdater.mm +821 -71
  67. package/ios/HotUpdater/Internal/HotUpdaterAsync.swift +7 -0
  68. package/ios/HotUpdater/Internal/HotUpdaterConfig.swift +208 -25
  69. package/ios/HotUpdater/Internal/HotUpdaterFingerprintHash.m +10 -0
  70. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +940 -261
  71. package/ios/HotUpdater/Internal/HotUpdaterInstanceEnvironment.swift +492 -0
  72. package/ios/HotUpdater/Internal/HotUpdaterInstanceRouter.swift +83 -0
  73. package/ios/HotUpdater/Internal/HotUpdaterInterface.swift +28 -1
  74. package/ios/HotUpdater/Internal/HotUpdaterLaunchRecovery.swift +1553 -0
  75. package/ios/HotUpdater/Internal/HotUpdaterNativeUpdates.swift +353 -0
  76. package/ios/HotUpdater/Internal/LaunchPreparationLock.swift +54 -0
  77. package/ios/HotUpdater/Internal/NativeUpdateCheck.swift +2051 -0
  78. package/ios/HotUpdater/Internal/ReleaseCatalog.swift +634 -0
  79. package/ios/HotUpdater/Internal/ReleaseCatalogCacheService.swift +1 -1
  80. package/ios/HotUpdater/Internal/ReleaseSelection.swift +482 -0
  81. package/ios/HotUpdater/Internal/SignatureVerifier.swift +27 -10
  82. package/ios/HotUpdater/Internal/StrictJson.swift +363 -0
  83. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +4 -2
  84. package/ios/HotUpdater/Internal/UpdateFailure.swift +2 -0
  85. package/ios/HotUpdater/Package.swift +11 -0
  86. package/ios/HotUpdater/Public/HotUpdater/HotUpdater.h +4 -0
  87. package/ios/HotUpdater/Public/HotUpdater.h +21 -2
  88. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +66 -0
  89. package/ios/Plugins/HotUpdaterFingerprintPlugin/HotUpdaterFingerprintPlugin.swift +45 -0
  90. package/ios/scripts/generate-fingerprint-hash.zsh +85 -0
  91. package/lib/commonjs/checkForUpdate.js +3 -1
  92. package/lib/commonjs/checkForUpdate.js.map +1 -1
  93. package/lib/commonjs/index.js +9 -12
  94. package/lib/commonjs/index.js.map +1 -1
  95. package/lib/commonjs/native.js +24 -79
  96. package/lib/commonjs/native.js.map +1 -1
  97. package/lib/commonjs/nativeUpdateEvents.js +66 -0
  98. package/lib/commonjs/nativeUpdateEvents.js.map +1 -0
  99. package/lib/commonjs/sdkVersion.js +1 -1
  100. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  101. package/lib/module/checkForUpdate.js +3 -1
  102. package/lib/module/checkForUpdate.js.map +1 -1
  103. package/lib/module/index.js +10 -13
  104. package/lib/module/index.js.map +1 -1
  105. package/lib/module/native.js +19 -73
  106. package/lib/module/native.js.map +1 -1
  107. package/lib/module/nativeUpdateEvents.js +61 -0
  108. package/lib/module/nativeUpdateEvents.js.map +1 -0
  109. package/lib/module/sdkVersion.js +1 -1
  110. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  111. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/index.d.ts +4 -14
  113. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  114. package/lib/typescript/commonjs/native.d.ts +9 -46
  115. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  116. package/lib/typescript/commonjs/nativeUpdateEvents.d.ts +12 -0
  117. package/lib/typescript/commonjs/nativeUpdateEvents.d.ts.map +1 -0
  118. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  119. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +19 -3
  120. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  121. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  122. package/lib/typescript/module/index.d.ts +4 -14
  123. package/lib/typescript/module/index.d.ts.map +1 -1
  124. package/lib/typescript/module/native.d.ts +9 -46
  125. package/lib/typescript/module/native.d.ts.map +1 -1
  126. package/lib/typescript/module/nativeUpdateEvents.d.ts +12 -0
  127. package/lib/typescript/module/nativeUpdateEvents.d.ts.map +1 -0
  128. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  129. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +19 -3
  130. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  131. package/package.json +4 -4
  132. package/src/checkForUpdate.ts +7 -1
  133. package/src/index.ts +9 -17
  134. package/src/native.ts +19 -91
  135. package/src/nativeUpdateEvents.ts +77 -0
  136. package/src/sdkVersion.ts +1 -1
  137. package/src/specs/NativeHotUpdater.ts +20 -3
  138. package/android/libs/README.md +0 -50
  139. package/android/libs/hot-updater-brotli-dec-1.2.0.jar +0 -0
  140. package/android/src/bridge/BridgeReactInstance.kt +0 -38
  141. package/android/src/bridgeless/BridgeReactInstance.kt +0 -24
  142. package/android/src/main/java/com/hotupdater/ReactIntegrationManagerBase.kt +0 -34
  143. package/android/src/newarch/ReactIntegrationManager.kt +0 -185
  144. package/android/src/oldarch/ReactIntegrationManager.kt +0 -120
@@ -0,0 +1,392 @@
1
+ package com.hotupdater
2
+
3
+ import org.json.JSONArray
4
+ import org.json.JSONObject
5
+
6
+ /**
7
+ * Parses exactly one JSON value (RFC 8259) as `JSON.parse` does, into
8
+ * `org.json` values. `org.json` also accepts comments, single quotes, unquoted
9
+ * keys, trailing commas, and other non-JSON syntax, and Android's
10
+ * `JSONObject` rejects a number that overflows to infinity, which `JSON.parse`
11
+ * accepts, so update responses are parsed here instead. Iterative, so deep
12
+ * nesting cannot overflow the stack. A value nested deeper than
13
+ * [MAX_BUILD_DEPTH], far deeper than any field Hot Updater reads, is checked
14
+ * but not built, as on iOS: it parses as `JSONObject.NULL`, so a deep unknown
15
+ * field costs no memory beyond the text.
16
+ */
17
+ internal object StrictJson {
18
+ /** The deepest nesting of arrays and objects that is built. */
19
+ const val MAX_BUILD_DEPTH = 128
20
+
21
+ /**
22
+ * Stands for a number that overflows to infinity, which `JSONObject`
23
+ * cannot hold. It is neither a number, a string, nor null, so a known
24
+ * field that reads it fails as `Infinity` fails in JavaScript.
25
+ */
26
+ object NonFiniteNumber
27
+
28
+ fun isValid(text: String): Boolean = Checker(text).check()
29
+
30
+ /** The value of [text], or null when [text] is not exactly one JSON value. */
31
+ fun parse(text: String): Any? {
32
+ if (!isValid(text)) return null
33
+ return Builder(text).build()
34
+ }
35
+
36
+ /** Builds the value of a text [Checker] accepted. */
37
+ private class Builder(
38
+ private val text: String,
39
+ ) {
40
+ private var index = 0
41
+
42
+ private class Container(
43
+ val value: Any,
44
+ ) {
45
+ var key = ""
46
+
47
+ fun add(item: Any) {
48
+ when (value) {
49
+ is JSONObject -> value.put(key, item)
50
+ is JSONArray -> value.put(item)
51
+ }
52
+ }
53
+ }
54
+
55
+ /** [create] within the build depth; past it, a placeholder that keeps nothing. */
56
+ private fun buildable(
57
+ depth: Int,
58
+ create: () -> Any,
59
+ ): Any = if (depth >= MAX_BUILD_DEPTH) JSONObject.NULL else create()
60
+
61
+ fun build(): Any {
62
+ val containers = ArrayDeque<Container>()
63
+ var expectValue = true
64
+ while (true) {
65
+ skipWhitespace()
66
+ if (expectValue) {
67
+ val value: Any =
68
+ when (text[index]) {
69
+ '{' -> {
70
+ index++
71
+ skipWhitespace()
72
+ if (text[index] == '}') {
73
+ index++
74
+ JSONObject()
75
+ } else {
76
+ containers.addLast(Container(buildable(containers.size) { JSONObject() }).apply { key = member() })
77
+ continue
78
+ }
79
+ }
80
+
81
+ '[' -> {
82
+ index++
83
+ skipWhitespace()
84
+ if (text[index] == ']') {
85
+ index++
86
+ JSONArray()
87
+ } else {
88
+ containers.addLast(Container(buildable(containers.size) { JSONArray() }))
89
+ continue
90
+ }
91
+ }
92
+
93
+ '"' -> {
94
+ string()
95
+ }
96
+
97
+ 't' -> {
98
+ literal("true", true)
99
+ }
100
+
101
+ 'f' -> {
102
+ literal("false", false)
103
+ }
104
+
105
+ 'n' -> {
106
+ literal("null", JSONObject.NULL)
107
+ }
108
+
109
+ else -> {
110
+ number()
111
+ }
112
+ }
113
+ expectValue = false
114
+ val container = containers.lastOrNull() ?: return value
115
+ container.add(value)
116
+ } else {
117
+ val container = containers.last()
118
+ when (text[index++]) {
119
+ ',' -> {
120
+ if (container.value is JSONObject) {
121
+ skipWhitespace()
122
+ container.key = member()
123
+ }
124
+ expectValue = true
125
+ }
126
+
127
+ else -> {
128
+ // The closing bracket the checker matched.
129
+ containers.removeLast()
130
+ val parent = containers.lastOrNull() ?: return container.value
131
+ parent.add(container.value)
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ private fun member(): String {
139
+ val key = string()
140
+ skipWhitespace()
141
+ index++ // ':'
142
+ return key
143
+ }
144
+
145
+ private fun string(): String {
146
+ index++
147
+ val builder = StringBuilder()
148
+ while (true) {
149
+ val char = text[index++]
150
+ when (char) {
151
+ '"' -> {
152
+ return builder.toString()
153
+ }
154
+
155
+ '\\' -> {
156
+ when (val escape = text[index++]) {
157
+ 'b' -> {
158
+ builder.append('\b')
159
+ }
160
+
161
+ 'f' -> {
162
+ builder.append('\u000C')
163
+ }
164
+
165
+ 'n' -> {
166
+ builder.append('\n')
167
+ }
168
+
169
+ 'r' -> {
170
+ builder.append('\r')
171
+ }
172
+
173
+ 't' -> {
174
+ builder.append('\t')
175
+ }
176
+
177
+ 'u' -> {
178
+ builder.append(text.substring(index, index + 4).toInt(16).toChar())
179
+ index += 4
180
+ }
181
+
182
+ else -> {
183
+ builder.append(escape)
184
+ }
185
+ }
186
+ }
187
+
188
+ else -> {
189
+ builder.append(char)
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ private fun number(): Any {
196
+ val start = index
197
+ while (index < text.length && text[index] in "+-0123456789.eE") index++
198
+ val value = text.substring(start, index).toDouble()
199
+ return if (value.isInfinite()) NonFiniteNumber else value
200
+ }
201
+
202
+ private fun literal(
203
+ word: String,
204
+ value: Any,
205
+ ): Any {
206
+ index += word.length
207
+ return value
208
+ }
209
+
210
+ private fun skipWhitespace() {
211
+ while (index < text.length && text[index].let { it == ' ' || it == '\t' || it == '\n' || it == '\r' }) {
212
+ index++
213
+ }
214
+ }
215
+ }
216
+
217
+ private class Checker(
218
+ private val text: String,
219
+ ) {
220
+ private var index = 0
221
+ private val containers = ArrayDeque<Char>()
222
+
223
+ fun check(): Boolean {
224
+ var expectValue = true
225
+ while (true) {
226
+ skipWhitespace()
227
+ if (expectValue) {
228
+ when (peek()) {
229
+ '{' -> {
230
+ index++
231
+ skipWhitespace()
232
+ if (peek() == '}') {
233
+ index++
234
+ expectValue = false
235
+ } else {
236
+ containers.addLast('{')
237
+ if (!member()) return false
238
+ }
239
+ }
240
+
241
+ '[' -> {
242
+ index++
243
+ skipWhitespace()
244
+ if (peek() == ']') {
245
+ index++
246
+ expectValue = false
247
+ } else {
248
+ containers.addLast('[')
249
+ }
250
+ }
251
+
252
+ '"' -> {
253
+ if (!string()) return false
254
+ expectValue = false
255
+ }
256
+
257
+ 't' -> {
258
+ if (literal("true")) expectValue = false else return false
259
+ }
260
+
261
+ 'f' -> {
262
+ if (literal("false")) expectValue = false else return false
263
+ }
264
+
265
+ 'n' -> {
266
+ if (literal("null")) expectValue = false else return false
267
+ }
268
+
269
+ else -> {
270
+ if (number()) expectValue = false else return false
271
+ }
272
+ }
273
+ } else {
274
+ val container = containers.lastOrNull() ?: return index == text.length
275
+ when (peek()) {
276
+ ',' -> {
277
+ index++
278
+ if (container == '{') {
279
+ skipWhitespace()
280
+ if (!member()) return false
281
+ }
282
+ expectValue = true
283
+ }
284
+
285
+ '}' -> {
286
+ if (container == '{') {
287
+ index++
288
+ containers.removeLast()
289
+ } else {
290
+ return false
291
+ }
292
+ }
293
+
294
+ ']' -> {
295
+ if (container == '[') {
296
+ index++
297
+ containers.removeLast()
298
+ } else {
299
+ return false
300
+ }
301
+ }
302
+
303
+ else -> {
304
+ return false
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+
311
+ /** An object member's key and colon; its value comes next. */
312
+ private fun member(): Boolean {
313
+ if (peek() != '"' || !string()) return false
314
+ skipWhitespace()
315
+ if (peek() != ':') return false
316
+ index++
317
+ return true
318
+ }
319
+
320
+ private fun string(): Boolean {
321
+ index++
322
+ while (index < text.length) {
323
+ val char = text[index++]
324
+ when {
325
+ char == '"' -> {
326
+ return true
327
+ }
328
+
329
+ char < ' ' -> {
330
+ return false
331
+ }
332
+
333
+ char == '\\' -> {
334
+ if (index >= text.length) return false
335
+ when (text[index++]) {
336
+ '"', '\\', '/', 'b', 'f', 'n', 'r', 't' -> {
337
+ Unit
338
+ }
339
+
340
+ 'u' -> {
341
+ if (index + 4 > text.length) return false
342
+ repeat(4) { if (!isHexDigit(text[index++])) return false }
343
+ }
344
+
345
+ else -> {
346
+ return false
347
+ }
348
+ }
349
+ }
350
+ }
351
+ }
352
+ return false
353
+ }
354
+
355
+ private fun number(): Boolean {
356
+ if (peek() == '-') index++
357
+ when {
358
+ peek() == '0' -> index++
359
+ peek() in '1'..'9' -> while (peek() in '0'..'9') index++
360
+ else -> return false
361
+ }
362
+ if (peek() == '.') {
363
+ index++
364
+ if (peek() !in '0'..'9') return false
365
+ while (peek() in '0'..'9') index++
366
+ }
367
+ if (peek() == 'e' || peek() == 'E') {
368
+ index++
369
+ if (peek() == '+' || peek() == '-') index++
370
+ if (peek() !in '0'..'9') return false
371
+ while (peek() in '0'..'9') index++
372
+ }
373
+ return true
374
+ }
375
+
376
+ private fun literal(word: String): Boolean {
377
+ if (!text.startsWith(word, index)) return false
378
+ index += word.length
379
+ return true
380
+ }
381
+
382
+ private fun skipWhitespace() {
383
+ while (index < text.length && text[index].let { it == ' ' || it == '\t' || it == '\n' || it == '\r' }) {
384
+ index++
385
+ }
386
+ }
387
+
388
+ private fun peek(): Char = if (index < text.length) text[index] else '\u0000'
389
+
390
+ private fun isHexDigit(char: Char): Boolean = char in '0'..'9' || char in 'a'..'f' || char in 'A'..'F'
391
+ }
392
+ }
@@ -33,19 +33,34 @@ interface PreferencesService {
33
33
  class VersionedPreferencesService(
34
34
  private val context: Context,
35
35
  private val isolationKey: String,
36
+ // The family of isolation keys this service replaces: main's, or one named instance's.
37
+ private val familyPrefix: String = "HotUpdaterPrefs_",
36
38
  ) : PreferencesService {
37
- private val prefs: SharedPreferences
38
-
39
- init {
40
-
41
- val sharedPrefsDir = File(context.applicationInfo.dataDir, "shared_prefs")
42
- if (sharedPrefsDir.exists() && sharedPrefsDir.isDirectory) {
43
- sharedPrefsDir.listFiles()?.forEach { file ->
44
- if (file.name.startsWith("HotUpdaterPrefs_") && file.name != "$isolationKey.xml") {
45
- file.delete()
39
+ companion object {
40
+ /**
41
+ * Deletes the preferences of every other isolation key in [familyPrefix]'s
42
+ * family, the app version or channel this instance left behind. Files of
43
+ * other families belong to other instances and stay.
44
+ */
45
+ internal fun deleteStalePreferences(
46
+ sharedPrefsDir: File,
47
+ familyPrefix: String,
48
+ isolationKey: String,
49
+ ) {
50
+ if (sharedPrefsDir.exists() && sharedPrefsDir.isDirectory) {
51
+ sharedPrefsDir.listFiles()?.forEach { file ->
52
+ if (file.name.startsWith(familyPrefix) && file.name != "$isolationKey.xml") {
53
+ file.delete()
54
+ }
46
55
  }
47
56
  }
48
57
  }
58
+ }
59
+
60
+ private val prefs: SharedPreferences
61
+
62
+ init {
63
+ deleteStalePreferences(File(context.applicationInfo.dataDir, "shared_prefs"), familyPrefix, isolationKey)
49
64
 
50
65
  prefs = context.getSharedPreferences(isolationKey, Context.MODE_PRIVATE)
51
66
  }
@@ -0,0 +1,76 @@
1
+ package com.hotupdater
2
+
3
+ import java.lang.ref.ReferenceQueue
4
+ import java.lang.ref.WeakReference
5
+
6
+ /**
7
+ * A set of objects by identity that doesn't keep them alive. An identity
8
+ * hash code alone can't stand for an object: once it is collected, a new
9
+ * object may get the same code, and would be taken for the old one. Thread
10
+ * safe.
11
+ */
12
+ internal class WeakIdentitySet<T : Any> {
13
+ private class Entry<T : Any>(
14
+ value: T,
15
+ queue: ReferenceQueue<T>?,
16
+ ) : WeakReference<T>(value, queue) {
17
+ private val hash = System.identityHashCode(value)
18
+
19
+ override fun hashCode(): Int = hash
20
+
21
+ override fun equals(other: Any?): Boolean {
22
+ if (this === other) return true
23
+ if (other !is Entry<*>) return false
24
+ val value = get() ?: return false
25
+ return value === other.get()
26
+ }
27
+ }
28
+
29
+ private val collected = ReferenceQueue<T>()
30
+ private val entries = HashSet<Entry<T>>()
31
+
32
+ /** True when [value] was not in the set. */
33
+ @Synchronized
34
+ fun add(value: T): Boolean {
35
+ dropCollected()
36
+ return entries.add(Entry(value, collected))
37
+ }
38
+
39
+ @Synchronized
40
+ fun remove(value: T): Boolean {
41
+ dropCollected()
42
+ return entries.remove(Entry(value, null))
43
+ }
44
+
45
+ @Synchronized
46
+ operator fun contains(value: T): Boolean {
47
+ dropCollected()
48
+ return Entry(value, null) in entries
49
+ }
50
+
51
+ @Synchronized
52
+ fun isEmpty(): Boolean {
53
+ dropCollected()
54
+ return entries.isEmpty()
55
+ }
56
+
57
+ val size: Int
58
+ @Synchronized get() {
59
+ dropCollected()
60
+ return entries.size
61
+ }
62
+
63
+ @Synchronized
64
+ fun clear() {
65
+ entries.clear()
66
+ while (collected.poll() != null) Unit
67
+ }
68
+
69
+ // A collected entry equals only itself, which removes it.
70
+ private fun dropCollected() {
71
+ while (true) {
72
+ val entry = collected.poll() ?: return
73
+ entries.remove(entry)
74
+ }
75
+ }
76
+ }