@_nazmiforreal/flutter-ota 0.1.26 → 0.1.28

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 (259) hide show
  1. package/README.md +112 -18
  2. package/bin/flutter-ota-linux-x64 +0 -0
  3. package/bin/flutter-ota.js +12 -11
  4. package/dart-src/packages/cli-tools/README.md +146 -2
  5. package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +46 -13
  6. package/dart-src/packages/cli-tools/lib/src/backend.dart +6 -6
  7. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +2 -0
  8. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +7 -7
  9. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +4 -1
  10. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +36 -8
  11. package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +1 -1
  12. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +1 -1
  13. package/dart-src/packages/cli-tools/lib/src/config.dart +5 -5
  14. package/dart-src/packages/cli-tools/lib/src/operations.dart +2 -2
  15. package/dart-src/packages/cli-tools/pubspec.yaml +8 -21
  16. package/dart-src/packages/cli-tools/test/backends_test.dart +3 -3
  17. package/dart-src/packages/cli-tools/test/cli_test.dart +1 -1
  18. package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +2 -2
  19. package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +2 -2
  20. package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +4 -1
  21. package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +2 -2
  22. package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +1 -1
  23. package/dart-src/root/README.md +334 -0
  24. package/dart-src/root/analysis_options.yaml +21 -0
  25. package/dart-src/root/android/build.gradle +116 -0
  26. package/dart-src/root/android/consumer-rules.pro +23 -0
  27. package/dart-src/root/android/settings.gradle +1 -0
  28. package/dart-src/root/android/src/main/AndroidManifest.xml +21 -0
  29. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/ApplyResult.kt +52 -0
  30. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BlacklistStore.kt +154 -0
  31. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BootDiagnosticStore.kt +150 -0
  32. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/CrashGuard.kt +271 -0
  33. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherApplication.kt +201 -0
  34. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherAutoInitProvider.kt +93 -0
  35. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherPlugin.kt +245 -0
  36. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/LoaderHook.kt +298 -0
  37. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatchManager.kt +1579 -0
  38. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfig.kt +173 -0
  39. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/SignatureVerifier.kt +177 -0
  40. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/CrashGuardTest.kt +61 -0
  41. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/LoaderHookTest.kt +25 -0
  42. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatchManagerTest.kt +401 -0
  43. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfigTest.kt +30 -0
  44. package/dart-src/root/bin/pack.dart +182 -0
  45. package/dart-src/root/lib/flutter_ota_kit.dart +1170 -0
  46. package/dart-src/root/lib/src/app_version_resolver.dart +36 -0
  47. package/dart-src/root/lib/src/aws_update_source.dart +97 -0
  48. package/dart-src/root/lib/src/blacklist.dart +54 -0
  49. package/dart-src/root/lib/src/boot_diagnostic.dart +138 -0
  50. package/dart-src/root/lib/src/cloudflare_update_source.dart +97 -0
  51. package/dart-src/root/lib/src/flutter_ota_app.dart +69 -0
  52. package/dart-src/root/lib/src/io.dart +48 -0
  53. package/dart-src/root/lib/src/io_stub.dart +19 -0
  54. package/dart-src/root/lib/src/ota_progress_overlay.dart +1186 -0
  55. package/dart-src/root/lib/src/pack/pack.dart +434 -0
  56. package/dart-src/root/lib/src/patch_info.dart +12 -0
  57. package/dart-src/root/lib/src/patcher_channel.dart +160 -0
  58. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_database.dart +1 -1
  59. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_s3_client.dart +1 -1
  60. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage.dart +1 -1
  61. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage_profile.dart +1 -1
  62. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/with_cloudfront_signed_url.dart +1 -1
  63. package/dart-src/root/lib/src/pkg/client/flutter_ota_kit_client.dart +1 -0
  64. package/dart-src/root/lib/src/pkg/client/src/server_update_source.dart +46 -0
  65. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/cloudflare_worker_database.dart +1 -1
  66. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_build_where.dart +1 -1
  67. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_bundle_mapper.dart +3 -3
  68. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database.dart +1 -1
  69. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database_plugin.dart +2 -2
  70. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_s3_client.dart +1 -1
  71. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage.dart +1 -1
  72. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage_profile.dart +1 -1
  73. package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/status.dart +1 -1
  74. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/flutter_ota_kit_plugin_core.dart +1 -1
  75. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_blob_database_plugin.dart +1 -1
  76. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/filter_compatible_app_versions.dart +1 -1
  77. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/get_update_info.dart +1 -1
  78. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/request_update_bundle_state.dart +1 -1
  79. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/resolve_update_info_from_bundles.dart +1 -1
  80. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/types.dart +2 -2
  81. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_bundle_mapper.dart +2 -2
  82. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_database.dart +26 -15
  83. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_storage.dart +1 -1
  84. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_bundle_mapper.dart +2 -2
  85. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_database.dart +2 -2
  86. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_get_update_info.dart +2 -2
  87. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_storage.dart +1 -1
  88. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_types.dart +1 -1
  89. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_bundle_mapper.dart +2 -2
  90. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_database.dart +2 -2
  91. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_database.dart +1 -1
  92. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_storage.dart +1 -1
  93. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_storage.dart +1 -1
  94. package/dart-src/root/lib/src/pocketbase_update_source.dart +107 -0
  95. package/dart-src/root/lib/src/postgres_update_source.dart +95 -0
  96. package/dart-src/root/lib/src/shared_update_check.dart +131 -0
  97. package/dart-src/root/lib/src/supabase_update_source.dart +93 -0
  98. package/dart-src/root/pubspec.yaml +54 -0
  99. package/package.json +1 -1
  100. package/scripts/postinstall.js +17 -13
  101. package/scripts/vendor.sh +22 -7
  102. package/dart-src/packages/core/CHANGELOG.md +0 -21
  103. package/dart-src/packages/core/README.md +0 -23
  104. package/dart-src/packages/core/example/lib/main.dart +0 -69
  105. package/dart-src/packages/core/pubspec.yaml +0 -13
  106. package/dart-src/packages/core/test/rollout_test.dart +0 -135
  107. package/dart-src/packages/core/test/semver_test.dart +0 -55
  108. package/dart-src/packages/core/test/semver_vectors.json +0 -1
  109. package/dart-src/packages/core/test/types_test.dart +0 -131
  110. package/dart-src/packages/core/test/uuid_test.dart +0 -40
  111. package/dart-src/packages/core/tool/dbg.dart +0 -7
  112. package/dart-src/plugins/aws/.gitkeep +0 -0
  113. package/dart-src/plugins/aws/CHANGELOG.md +0 -23
  114. package/dart-src/plugins/aws/LICENSE +0 -21
  115. package/dart-src/plugins/aws/README.md +0 -28
  116. package/dart-src/plugins/aws/example/lib/main.dart +0 -60
  117. package/dart-src/plugins/aws/pubspec.yaml +0 -30
  118. package/dart-src/plugins/aws/test/aws_database_test.dart +0 -161
  119. package/dart-src/plugins/aws/test/aws_storage_test.dart +0 -73
  120. package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +0 -78
  121. package/dart-src/plugins/cloudflare/.gitkeep +0 -0
  122. package/dart-src/plugins/cloudflare/CHANGELOG.md +0 -23
  123. package/dart-src/plugins/cloudflare/LICENSE +0 -21
  124. package/dart-src/plugins/cloudflare/README.md +0 -25
  125. package/dart-src/plugins/cloudflare/example/lib/main.dart +0 -62
  126. package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +0 -16
  127. package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +0 -8
  128. package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +0 -38
  129. package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +0 -11
  130. package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +0 -24
  131. package/dart-src/plugins/cloudflare/pubspec.yaml +0 -28
  132. package/dart-src/plugins/cloudflare/sql/bundles.sql +0 -42
  133. package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +0 -126
  134. package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +0 -106
  135. package/dart-src/plugins/cloudflare/test/d1_database_test.dart +0 -200
  136. package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +0 -261
  137. package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +0 -78
  138. package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +0 -74
  139. package/dart-src/plugins/plugin-core/CHANGELOG.md +0 -20
  140. package/dart-src/plugins/plugin-core/LICENSE +0 -21
  141. package/dart-src/plugins/plugin-core/README.md +0 -21
  142. package/dart-src/plugins/plugin-core/example/lib/main.dart +0 -61
  143. package/dart-src/plugins/plugin-core/pubspec.yaml +0 -23
  144. package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +0 -170
  145. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +0 -74
  146. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +0 -345
  147. package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +0 -843
  148. package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +0 -298
  149. package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +0 -195
  150. package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +0 -56
  151. package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +0 -1589
  152. package/dart-src/plugins/plugin-core/test/helpers_test.dart +0 -283
  153. package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +0 -202
  154. package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +0 -86
  155. package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +0 -88
  156. package/dart-src/plugins/pocketbase/CHANGELOG.md +0 -23
  157. package/dart-src/plugins/pocketbase/LICENSE +0 -21
  158. package/dart-src/plugins/pocketbase/README.md +0 -54
  159. package/dart-src/plugins/pocketbase/example/lib/main.dart +0 -62
  160. package/dart-src/plugins/pocketbase/pubspec.yaml +0 -26
  161. package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +0 -12
  162. package/dart-src/plugins/postgres/.gitkeep +0 -0
  163. package/dart-src/plugins/postgres/CHANGELOG.md +0 -23
  164. package/dart-src/plugins/postgres/LICENSE +0 -21
  165. package/dart-src/plugins/postgres/README.md +0 -29
  166. package/dart-src/plugins/postgres/example/lib/main.dart +0 -67
  167. package/dart-src/plugins/postgres/pubspec.yaml +0 -26
  168. package/dart-src/plugins/postgres/sql/bundles.sql +0 -45
  169. package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +0 -21
  170. package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +0 -171
  171. package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +0 -126
  172. package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +0 -125
  173. package/dart-src/plugins/postgres/sql/hash_user_id.sql +0 -30
  174. package/dart-src/plugins/postgres/sql/is_device_eligible.sql +0 -237
  175. package/dart-src/plugins/postgres/sql/prepareSql.ts +0 -11
  176. package/dart-src/plugins/postgres/test/mock_postgres_client.dart +0 -320
  177. package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +0 -139
  178. package/dart-src/plugins/postgres/test/postgres_database_test.dart +0 -210
  179. package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +0 -60
  180. package/dart-src/plugins/supabase/CHANGELOG.md +0 -24
  181. package/dart-src/plugins/supabase/LICENSE +0 -21
  182. package/dart-src/plugins/supabase/README.md +0 -53
  183. package/dart-src/plugins/supabase/example/lib/main.dart +0 -63
  184. package/dart-src/plugins/supabase/pubspec.yaml +0 -27
  185. package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +0 -44
  186. package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +0 -968
  187. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +0 -232
  188. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +0 -137
  189. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +0 -206
  190. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +0 -186
  191. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +0 -505
  192. package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +0 -50
  193. package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +0 -20
  194. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +0 -75
  195. package/dart-src/plugins/supabase/test/mock_supabase_client.dart +0 -654
  196. package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +0 -115
  197. package/dart-src/plugins/supabase/test/supabase_database_test.dart +0 -198
  198. package/dart-src/plugins/supabase/test/supabase_storage_test.dart +0 -168
  199. /package/dart-src/{packages/core → root}/LICENSE +0 -0
  200. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/flutter_ota_kit_aws.dart +0 -0
  201. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_client.dart +0 -0
  202. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_signer.dart +0 -0
  203. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_config.dart +0 -0
  204. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_ssm_client.dart +0 -0
  205. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/flutter_ota_kit_cloudflare.dart +0 -0
  206. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_client.dart +0 -0
  207. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_config.dart +0 -0
  208. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_config.dart +0 -0
  209. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/flutter_ota_kit_core.dart +0 -0
  210. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/app_update_info.dart +0 -0
  211. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle.dart +0 -0
  212. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_artifacts.dart +0 -0
  213. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_patch_artifact.dart +0 -0
  214. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/changed_asset.dart +0 -0
  215. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/get_bundles_args.dart +0 -0
  216. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/metadata.dart +0 -0
  217. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/patch_info.dart +0 -0
  218. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/platform.dart +0 -0
  219. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/rollout.dart +0 -0
  220. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver.dart +0 -0
  221. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_range.dart +0 -0
  222. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_version.dart +0 -0
  223. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/strategy.dart +0 -0
  224. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/update_bundle_params.dart +0 -0
  225. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/uuid.dart +0 -0
  226. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/asset_storage_layout.dart +0 -0
  227. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_helpers.dart +0 -0
  228. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_storage_layout.dart +0 -0
  229. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work.dart +0 -0
  230. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work_store.dart +0 -0
  231. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/calculate_pagination.dart +0 -0
  232. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/compression_format.dart +0 -0
  233. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/content_addressed_assets.dart +0 -0
  234. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_database_plugin.dart +0 -0
  235. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_key_builder.dart +0 -0
  236. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_plugin.dart +0 -0
  237. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/generate_min_bundle_id.dart +0 -0
  238. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/is_object.dart +0 -0
  239. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/legacy_asset_storage_layout.dart +0 -0
  240. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/paginate_bundles.dart +0 -0
  241. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/parse_storage_uri.dart +0 -0
  242. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/query_bundles.dart +0 -0
  243. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_helpers.dart +0 -0
  244. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_profile.dart +0 -0
  245. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/uuidv7.dart +0 -0
  246. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/flutter_ota_kit_pocketbase.dart +0 -0
  247. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_client.dart +0 -0
  248. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_config.dart +0 -0
  249. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/flutter_ota_kit_postgres.dart +0 -0
  250. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_client.dart +0 -0
  251. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_config.dart +0 -0
  252. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/edge.dart +0 -0
  253. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/flutter_ota_kit_supabase.dart +0 -0
  254. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/error_message.dart +0 -0
  255. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_adapter.dart +0 -0
  256. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_http.dart +0 -0
  257. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_config.dart +0 -0
  258. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_signed_url_batcher.dart +0 -0
  259. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/types.dart +0 -0
@@ -0,0 +1,271 @@
1
+ package com.flutter_patcher.flutter_patcher
2
+
3
+ import android.app.ActivityManager
4
+ import android.app.ApplicationExitInfo
5
+ import android.content.Context
6
+ import android.os.Build
7
+ import android.os.Process
8
+ import android.util.Log
9
+ import java.io.File
10
+
11
+ /**
12
+ * 熔断器:防止补丁导致启动崩溃时无限重启。
13
+ *
14
+ * 状态机(解耦版):
15
+ * 1. Application.attachBaseContext → [markBooting] 写 patch_loading=true(commit 同步)+ pid
16
+ * 2. Dart 首帧 → [markBootSuccess] 立即清 patch_loading=false + crash_count=0;
17
+ * **保留** last_booting_pid 给下次冷启动的 ExitInfo 查询
18
+ * 3. Dart 首帧 + verifyAfter 秒 → 卸载 Dart 错误钩子(仅 Dart 层动作,不触原生)
19
+ *
20
+ * 下次冷启动 [shouldLoadPatch] 综合判定:
21
+ *
22
+ * - **API 30+ 且有 pid 记录**:通过 [ActivityManager.getHistoricalProcessExitReasons]
23
+ * 拿到精确死因。覆盖首帧前后所有崩溃。只有 [ApplicationExitInfo.REASON_CRASH]
24
+ * / [ApplicationExitInfo.REASON_CRASH_NATIVE] / [ApplicationExitInfo.REASON_ANR]
25
+ * / [ApplicationExitInfo.REASON_INITIALIZATION_FAILURE] 计入 crash_count;
26
+ * 用户从最近任务划掉、系统 OOM 等不计入。
27
+ * - **API < 30 或 ExitInfo 无记录**:退回到 patch_loading 兜底——
28
+ * patch_loading=true(首帧前死了)→ 视为崩溃;
29
+ * patch_loading=false(首帧后死的)→ 不计崩溃。
30
+ *
31
+ * API < 30 因此存在已知盲区:补丁加载首帧后才发生的 native crash / ANR 不会被
32
+ * 检测(Dart 层异常仍由 verifyAfter 窗口内的 Dart 错误钩子捕获)。覆盖率约
33
+ * 5–10% 的存量长尾设备,业务侧若无法接受可显式调高 `maxCrashCount`。
34
+ *
35
+ * 当 crash_count 累计 >= [PatcherConfig.maxCrashCount] 次,自动删除补丁并拒绝加载。
36
+ */
37
+ internal class CrashGuard(private val context: Context) {
38
+
39
+ companion object {
40
+ private const val TAG = "FlutterPatcher/Guard"
41
+
42
+ /**
43
+ * Pure decision: should a real crash reason be charged against the patch?
44
+ *
45
+ * Only crashes within [PatcherConfig.BOOT_CRASH_WINDOW_MS] of boot start
46
+ * count as patch-boot failures. A crash long after a healthy boot is a
47
+ * runtime failure and must NOT trip the breaker (that was the bug causing
48
+ * a working OTA to silently revert hours later).
49
+ *
50
+ * When [bootStartedAt] or [crashTimestamp] is unknown (<= 0) we cannot
51
+ * bound it, so we conservatively charge the patch (preserves the old
52
+ * fail-fast behavior for that edge).
53
+ */
54
+ @JvmStatic
55
+ fun isWithinBootWindow(
56
+ bootStartedAt: Long,
57
+ crashTimestamp: Long,
58
+ windowMs: Long = PatcherConfig.BOOT_CRASH_WINDOW_MS,
59
+ ): Boolean {
60
+ if (bootStartedAt <= 0L || crashTimestamp <= 0L) return true
61
+ val elapsed = crashTimestamp - bootStartedAt
62
+ // Negative elapsed (clock skew / stale record) → treat as in-window.
63
+ if (elapsed < 0L) return true
64
+ return elapsed <= windowMs
65
+ }
66
+ }
67
+
68
+ private val sp = PatcherConfig.prefs(context)
69
+
70
+ /**
71
+ * 启动开始时综合判断是否加载补丁。
72
+ *
73
+ * @param onTrip 当熔断器**本次**触发并丢弃补丁时调用,参数为触发时的真实
74
+ * crash_count(删除前)。供 [BootDiagnosticStore] / [BlacklistStore] 上报使用。
75
+ */
76
+ fun shouldLoadPatch(onTrip: ((crashCount: Int) -> Unit)? = null): Boolean {
77
+ val threshold = PatcherConfig.maxCrashCount(context)
78
+ val patchLoading = sp.getBoolean(PatcherConfig.KEY_PATCH_LOADING, false)
79
+ val lastPid = sp.getInt(PatcherConfig.KEY_LAST_BOOTING_PID, -1)
80
+ val bootStartedAt = sp.getLong(PatcherConfig.KEY_BOOT_STARTED_AT, 0L)
81
+
82
+ val verdict = classifyPreviousSession(patchLoading, lastPid, bootStartedAt)
83
+ if (verdict.isCrash) {
84
+ val count = recordCrashAndMaybeTrip(verdict.reasonName, onTrip)
85
+ if (count >= threshold) return false
86
+ } else if (patchLoading || lastPid > 0) {
87
+ // 非崩溃但状态未清:清掉以免下次冷启动重复处理同一会话
88
+ sp.edit()
89
+ .putBoolean(PatcherConfig.KEY_PATCH_LOADING, false)
90
+ .remove(PatcherConfig.KEY_LAST_BOOTING_PID)
91
+ .remove(PatcherConfig.KEY_BOOT_STARTED_AT)
92
+ .commit()
93
+ Log.i(TAG, "previous session ended without crash (${verdict.reasonName})")
94
+ }
95
+
96
+ return sp.getInt(PatcherConfig.KEY_CRASH_COUNT, 0) < threshold
97
+ }
98
+
99
+ /**
100
+ * 由 Dart 侧通过 MethodChannel 调用:上报一次"补丁加载阶段 Dart 层未捕获异常"。
101
+ *
102
+ * 这种崩溃不算系统层 [ApplicationExitInfo.REASON_CRASH] —— PlatformDispatcher
103
+ * 把异常吞了,进程没死,下次冷启动 ExitInfo 看到的是 USER_REQUESTED 之类的非
104
+ * 崩溃原因。所以需要 Dart 主动汇报,语义等同于一次真崩溃:crash_count += 1,
105
+ * 达到阈值则熔断 + 删补丁 + 黑名单。
106
+ *
107
+ * @param message 异常字符串(透传给日志 / 诊断展示,不做任何业务逻辑判断)
108
+ * @param onTrip 与 [shouldLoadPatch] 同款:触发熔断时调用,上报黑名单 / 诊断
109
+ */
110
+ fun reportDartBootError(message: String?, onTrip: ((crashCount: Int) -> Unit)? = null) {
111
+ recordCrashAndMaybeTrip(message ?: "no msg", onTrip)
112
+ }
113
+
114
+ /**
115
+ * 增计 crash_count、清 patch_loading、必要时触发熔断 + 删补丁。供
116
+ * [shouldLoadPatch] 与 [reportDartBootError] 共用,统一日志格式。
117
+ *
118
+ * @param reason 错误原因(ExitInfo reason name / Dart 异常 toString),仅用于日志
119
+ * @return 累加后的 crash_count(删补丁前的真实值)
120
+ */
121
+ private fun recordCrashAndMaybeTrip(
122
+ reason: String,
123
+ onTrip: ((crashCount: Int) -> Unit)?,
124
+ ): Int {
125
+ val threshold = PatcherConfig.maxCrashCount(context)
126
+ val count = sp.getInt(PatcherConfig.KEY_CRASH_COUNT, 0) + 1
127
+ sp.edit()
128
+ .putInt(PatcherConfig.KEY_CRASH_COUNT, count)
129
+ .putBoolean(PatcherConfig.KEY_PATCH_LOADING, false)
130
+ .commit()
131
+ Log.w(TAG, "patch boot failure recorded ($reason), crash_count=$count")
132
+ if (count >= threshold) {
133
+ Log.w(TAG, "circuit tripped! $count consecutive failures, dropping patch")
134
+ onTrip?.invoke(count)
135
+ deletePatchFiles()
136
+ }
137
+ return count
138
+ }
139
+
140
+ /** 在 Application.attachBaseContext 内调用。commit 同步写入,确保进程崩溃前状态已持久化。 */
141
+ fun markBooting() {
142
+ sp.edit()
143
+ .putBoolean(PatcherConfig.KEY_PATCH_LOADING, true)
144
+ .putInt(PatcherConfig.KEY_LAST_BOOTING_PID, Process.myPid())
145
+ .putLong(PatcherConfig.KEY_BOOT_STARTED_AT, System.currentTimeMillis())
146
+ .commit()
147
+ }
148
+
149
+ /**
150
+ * Dart 首帧渲染完成:补丁可信,立即清 patch_loading + crash_count。
151
+ *
152
+ * 注意:故意 **不清** [PatcherConfig.KEY_LAST_BOOTING_PID] / [PatcherConfig.KEY_BOOT_STARTED_AT]。
153
+ * 下次冷启动 [shouldLoadPatch] 仍可用这个 pid 查 [ApplicationExitInfo],从而捕捉首帧
154
+ * 后短时间内([PatcherConfig.BOOT_CRASH_WINDOW_MS] 以内)才发生的 native crash / ANR —— 那一刻
155
+ * patch_loading 已清,不能再作为崩溃信号。pid + 时间戳在 [shouldLoadPatch]
156
+ * 处理完一次会话后清除。
157
+ */
158
+ fun markBootSuccess() {
159
+ sp.edit()
160
+ .putBoolean(PatcherConfig.KEY_PATCH_LOADING, false)
161
+ .putInt(PatcherConfig.KEY_CRASH_COUNT, 0)
162
+ .commit()
163
+ }
164
+
165
+ /** 清零所有熔断状态(配合补丁安装/回滚调用)。 */
166
+ fun reset() {
167
+ sp.edit()
168
+ .putBoolean(PatcherConfig.KEY_PATCH_LOADING, false)
169
+ .putInt(PatcherConfig.KEY_CRASH_COUNT, 0)
170
+ .remove(PatcherConfig.KEY_LAST_BOOTING_PID)
171
+ .remove(PatcherConfig.KEY_BOOT_STARTED_AT)
172
+ .apply()
173
+ }
174
+
175
+ /**
176
+ * 综合 [patchLoading] 与 [lastPid] 判定上次会话是否崩溃。
177
+ *
178
+ * - API 30+ 且有 pid 记录 → 优先用 [ApplicationExitInfo] 精确分类。**但只把发生在
179
+ * 启动窗口内(距 [bootStartedAt] 不超过 [PatcherConfig.BOOT_CRASH_WINDOW_MS])的
180
+ * 崩溃计入补丁失败**:首帧后长时间运行才发生的 crash/ANR 属于正常运行期故障,
181
+ * 与补丁无关,不应触发熔断回滚。用户主动关闭 / 系统 OOM 等也不计崩溃。
182
+ * - API < 30 或 ExitInfo 拿不到记录 → 退回到 [patchLoading] 兜底信号。
183
+ * patchLoading=true(首帧前死了)→ 崩溃;patchLoading=false(首帧后死的,
184
+ * 或本就没启动过)→ 非崩溃。
185
+ */
186
+ private fun classifyPreviousSession(
187
+ patchLoading: Boolean,
188
+ lastPid: Int,
189
+ bootStartedAt: Long,
190
+ ): ExitVerdict {
191
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && lastPid > 0) {
192
+ val record = queryExitInfo(lastPid)
193
+ if (record != null) {
194
+ val isCrashReason = when (record.reason) {
195
+ ApplicationExitInfo.REASON_CRASH,
196
+ ApplicationExitInfo.REASON_CRASH_NATIVE,
197
+ ApplicationExitInfo.REASON_ANR,
198
+ ApplicationExitInfo.REASON_INITIALIZATION_FAILURE -> true
199
+ else -> false
200
+ }
201
+ val reasonName = reasonNameApi30(record.reason)
202
+ if (!isCrashReason) {
203
+ return ExitVerdict(false, reasonName)
204
+ }
205
+ // A real crash reason — but only charge it against the patch if it
206
+ // happened close to boot. A crash hours into a healthy session is a
207
+ // runtime bug, not a bad patch; counting it would revert a working OTA.
208
+ if (!isWithinBootWindow(bootStartedAt, record.timestamp)) {
209
+ Log.i(
210
+ TAG,
211
+ "crash ($reasonName) at ts=${record.timestamp} is outside the " +
212
+ "${PatcherConfig.BOOT_CRASH_WINDOW_MS}ms boot window " +
213
+ "(bootStartedAt=$bootStartedAt); treating as runtime " +
214
+ "failure, not charging the patch"
215
+ )
216
+ return ExitVerdict(false, "${reasonName}_LATE")
217
+ }
218
+ return ExitVerdict(true, reasonName)
219
+ }
220
+ // ExitInfo 队列已被新进程挤出(罕见):退回到 patchLoading 兜底
221
+ }
222
+
223
+ return if (patchLoading) {
224
+ ExitVerdict(true, "NO_FIRST_FRAME")
225
+ } else {
226
+ ExitVerdict(false, "NO_RECORD")
227
+ }
228
+ }
229
+
230
+ private fun queryExitInfo(pid: Int): ApplicationExitInfo? {
231
+ val am = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
232
+ return try {
233
+ am?.getHistoricalProcessExitReasons(context.packageName, pid, 1)?.firstOrNull()
234
+ } catch (e: Throwable) {
235
+ Log.w(TAG, "getHistoricalProcessExitReasons failed", e)
236
+ null
237
+ }
238
+ }
239
+
240
+ private fun deletePatchFiles() {
241
+ val dir = File(context.filesDir, PatcherConfig.PATCH_DIR)
242
+ if (dir.exists()) dir.deleteRecursively()
243
+ sp.edit()
244
+ .putInt(PatcherConfig.KEY_CRASH_COUNT, 0)
245
+ .putBoolean(PatcherConfig.KEY_PATCH_LOADING, false)
246
+ .remove(PatcherConfig.KEY_LAST_BOOTING_PID)
247
+ .remove(PatcherConfig.KEY_BOOT_STARTED_AT)
248
+ .commit()
249
+ }
250
+
251
+ private data class ExitVerdict(val isCrash: Boolean, val reasonName: String)
252
+ }
253
+
254
+ /** 把 [ApplicationExitInfo] reason 整数翻成可读名,仅在 API 30+ 调用。 */
255
+ private fun reasonNameApi30(reason: Int): String = when (reason) {
256
+ ApplicationExitInfo.REASON_UNKNOWN -> "UNKNOWN"
257
+ ApplicationExitInfo.REASON_EXIT_SELF -> "EXIT_SELF"
258
+ ApplicationExitInfo.REASON_SIGNALED -> "SIGNALED"
259
+ ApplicationExitInfo.REASON_LOW_MEMORY -> "LOW_MEMORY"
260
+ ApplicationExitInfo.REASON_CRASH -> "CRASH"
261
+ ApplicationExitInfo.REASON_CRASH_NATIVE -> "CRASH_NATIVE"
262
+ ApplicationExitInfo.REASON_ANR -> "ANR"
263
+ ApplicationExitInfo.REASON_INITIALIZATION_FAILURE -> "INITIALIZATION_FAILURE"
264
+ ApplicationExitInfo.REASON_PERMISSION_CHANGE -> "PERMISSION_CHANGE"
265
+ ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE -> "EXCESSIVE_RESOURCE_USAGE"
266
+ ApplicationExitInfo.REASON_USER_REQUESTED -> "USER_REQUESTED"
267
+ ApplicationExitInfo.REASON_USER_STOPPED -> "USER_STOPPED"
268
+ ApplicationExitInfo.REASON_DEPENDENCY_DIED -> "DEPENDENCY_DIED"
269
+ ApplicationExitInfo.REASON_OTHER -> "OTHER"
270
+ else -> "REASON_$reason"
271
+ }
@@ -0,0 +1,201 @@
1
+ package com.flutter_patcher.flutter_patcher
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import io.flutter.app.FlutterApplication
6
+
7
+ /**
8
+ * 宿主 App 需在 AndroidManifest.xml 中使用这个 Application(或在自己的 Application
9
+ * 里调 [attachPatcher])。
10
+ *
11
+ * 启动流程:
12
+ * 1. attachBaseContext
13
+ * → CrashGuard.shouldLoadPatch() 熔断检查(API 30+ 走 ApplicationExitInfo
14
+ * 精确识别上次进程是真崩溃还是被用户/系统结束;API < 30 走朴素策略:
15
+ * patch_loading=true 即视为一次崩溃)
16
+ * → PatchManager.getValidPatchPath() 磁盘补丁验签
17
+ * → LoaderHook.install() 反射替换 FlutterLoader
18
+ * → CrashGuard.markBooting() 标记「启动中」并记录 pid(API 30+ 用于 ExitInfo 查询)
19
+ * 2. Dart 首帧 → MethodChannel("flutter_ota_kit#reportBootSuccess")
20
+ * → CrashGuard.markBootSuccess() 重置计数
21
+ *
22
+ * API 30+ 上,用户主动从最近任务划掉 / OOM kill / 强停等「非崩溃退出」不计入
23
+ * crash_count,只有真崩溃(CRASH / CRASH_NATIVE / ANR / INITIALIZATION_FAILURE)
24
+ * 才会触发熔断。API < 30 没有这层分类,对长尾设备做 fail-fast 取舍。
25
+ *
26
+ * 用户如果已经有自己的 Application 基类,直接把 [attachPatcher] 搬过去即可,
27
+ * 不需要继承本类。
28
+ */
29
+ open class FlutterPatcherApplication : FlutterApplication() {
30
+
31
+ companion object {
32
+ private const val TAG = "FlutterPatcher/App"
33
+
34
+ @Volatile
35
+ private var attachAttempted = false
36
+
37
+ @Volatile
38
+ private var attachResult = false
39
+
40
+ /**
41
+ * 在 Application.attachBaseContext(base) 中、super 调用之后调用。
42
+ * 返回补丁是否成功注入。
43
+ */
44
+ @JvmStatic
45
+ @Synchronized
46
+ fun attachPatcher(context: Context): Boolean {
47
+ if (attachAttempted) {
48
+ Log.d(TAG, "attachPatcher already ran in this process, result=$attachResult")
49
+ return attachResult
50
+ }
51
+ attachAttempted = true
52
+ attachResult = doAttachPatcher(context)
53
+ return attachResult
54
+ }
55
+
56
+ private fun doAttachPatcher(context: Context): Boolean {
57
+ // 本次启动是否已往 BootDiagnosticStore 写过精准 status。
58
+ // 用于在收尾阶段判断要不要兜底成 NO_PATCH(避免覆盖更精准的 reason)。
59
+ var diagRecorded = false
60
+ val appVc = PatcherConfig.currentVersionCode(context)
61
+
62
+ return try {
63
+ // 在熔断/校验丢弃前预读 (version, downloadMd5),等会儿入黑名单要用。
64
+ // 即便文件后续被删,这里捕获的字符串仍有效。
65
+ val patchManager = PatchManager(context)
66
+ val crashedMeta = patchManager.currentMeta()
67
+
68
+ val guard = CrashGuard(context)
69
+ val canLoad = guard.shouldLoadPatch { crashCount ->
70
+ // 熔断触发 ⇒ 这个补丁连续没活到 verified,加入黑名单防止重试。
71
+ if (crashedMeta != null) {
72
+ BlacklistStore.add(
73
+ context,
74
+ crashedMeta.first,
75
+ crashedMeta.second,
76
+ BlacklistStore.REASON_BOOT_CRASH,
77
+ )
78
+ }
79
+ BootDiagnosticStore.record(
80
+ context = context,
81
+ status = BootDiagnosticStore.DROPPED_CIRCUIT_BREAKER,
82
+ patchVersion = crashedMeta?.first,
83
+ appVersionCode = appVc,
84
+ crashCount = crashCount,
85
+ message = if (crashedMeta != null)
86
+ "$crashCount boot failure(s); blacklisted (version=${crashedMeta.first})"
87
+ else
88
+ "$crashCount boot failure(s)",
89
+ )
90
+ diagRecorded = true
91
+ }
92
+ if (!canLoad) {
93
+ Log.w(TAG, "circuit breaker tripped, skip patch")
94
+ return false
95
+ }
96
+
97
+ val patch = patchManager.getValidPatch { status, version, extras ->
98
+ // md5 / signature 失败 = 强烈"补丁有问题"信号,连带入黑名单。
99
+ // meta_corrupted / version_code_mismatch 不入黑名单(前者 key 不全,后者
100
+ // 属于正常 APK 升级而非补丁本身有问题)。
101
+ val blacklistMd5 = extras["blacklistMd5"] as? String
102
+ val blacklistReason = when (status) {
103
+ BootDiagnosticStore.DROPPED_MD5_MISMATCH ->
104
+ BlacklistStore.REASON_MD5_MISMATCH
105
+ BootDiagnosticStore.DROPPED_SIGNATURE_INVALID ->
106
+ BlacklistStore.REASON_SIGNATURE_INVALID
107
+ else -> null
108
+ }
109
+ if (blacklistReason != null && version != null && !blacklistMd5.isNullOrEmpty()) {
110
+ BlacklistStore.add(context, version, blacklistMd5, blacklistReason)
111
+ }
112
+
113
+ BootDiagnosticStore.record(
114
+ context = context,
115
+ status = status,
116
+ patchVersion = version,
117
+ patchTargetVersionCode = extras["patchTargetVersionCode"] as? Long,
118
+ appVersionCode = (extras["appVersionCode"] as? Long) ?: appVc,
119
+ message = extras["message"] as? String,
120
+ )
121
+ diagRecorded = true
122
+ }
123
+ if (patch == null) {
124
+ Log.d(TAG, "no usable patch, boot with built-in libapp.so")
125
+ if (!diagRecorded) {
126
+ // DROPPED_* 是用户最关心的"上次为啥被丢弃"信息,不应被这次
127
+ // 无信息量的 NO_PATCH 覆盖。典型场景:上一次冷启动里 Dart 错误
128
+ // 钩子把补丁判崩 + record(DROPPED_CIRCUIT_BREAKER) + 删补丁,
129
+ // 但进程没死、UI 没建出来;用户划掉重开后这一次进入本分支,
130
+ // 写 NO_PATCH 会让 DiagCard 看不到 trip 信息。
131
+ // 后续装上新补丁时,line 127 会写 PATCHED 自然覆盖,sticky 自动解除。
132
+ val existing = BootDiagnosticStore.read(context)
133
+ val existingStatus = existing?.get("status") as? String
134
+ val shouldPreserve = existingStatus != null &&
135
+ existingStatus.startsWith("DROPPED_")
136
+ if (!shouldPreserve) {
137
+ BootDiagnosticStore.record(
138
+ context = context,
139
+ status = BootDiagnosticStore.NO_PATCH,
140
+ appVersionCode = appVc,
141
+ )
142
+ } else {
143
+ Log.d(TAG, "preserved existing $existingStatus over NO_PATCH")
144
+ }
145
+ }
146
+ return false
147
+ }
148
+
149
+ // 走到这里说明补丁已通过校验。预先取版本号,方便后面 record。
150
+ val patchVersion = patchManager.currentVersion().ifEmpty { null }
151
+
152
+ // 标记「启动中」——必须 commit 同步写入,确保进程崩溃前状态已持久化
153
+ guard.markBooting()
154
+
155
+ val attemptedFields = mutableListOf<String>()
156
+ val ok = LoaderHook.install(
157
+ context,
158
+ patch.soPath,
159
+ patch.assetsBundlePath,
160
+ patch.assetsArchivePath,
161
+ attemptedFields
162
+ )
163
+ if (ok) {
164
+ BootDiagnosticStore.record(
165
+ context = context,
166
+ status = BootDiagnosticStore.PATCHED,
167
+ patchVersion = patchVersion,
168
+ appVersionCode = appVc,
169
+ )
170
+ } else {
171
+ // 注入失败:本次启动根本没换 libapp.so,不视为一次崩溃
172
+ guard.reset()
173
+ BootDiagnosticStore.record(
174
+ context = context,
175
+ status = BootDiagnosticStore.HOOK_INSTALL_FAILED,
176
+ patchVersion = patchVersion,
177
+ appVersionCode = appVc,
178
+ attemptedLoaderFields = attemptedFields.toList(),
179
+ message = "FlutterLoader reflection failed; patch retained",
180
+ )
181
+ }
182
+ ok
183
+ } catch (e: Exception) {
184
+ Log.e(TAG, "attachPatcher failed, fallback to built-in", e)
185
+ CrashGuard(context).reset()
186
+ BootDiagnosticStore.record(
187
+ context = context,
188
+ status = BootDiagnosticStore.UNKNOWN,
189
+ appVersionCode = appVc,
190
+ message = e.message ?: e.javaClass.simpleName,
191
+ )
192
+ false
193
+ }
194
+ }
195
+ }
196
+
197
+ override fun attachBaseContext(base: Context) {
198
+ super.attachBaseContext(base)
199
+ attachPatcher(this)
200
+ }
201
+ }
@@ -0,0 +1,93 @@
1
+ package com.flutter_patcher.flutter_patcher
2
+
3
+ import android.content.ContentProvider
4
+ import android.content.ContentValues
5
+ import android.database.Cursor
6
+ import android.net.Uri
7
+ import android.util.Log
8
+
9
+ /**
10
+ * 自动初始化 ContentProvider —— 宿主无需改 Application 即可挂上补丁加载。
11
+ *
12
+ * # 生命周期
13
+ * Android `ActivityThread.handleBindApplication` 的固定顺序:
14
+ *
15
+ * ```
16
+ * Application.attachBaseContext()
17
+ * ↓
18
+ * installContentProviders() ← 本 provider.onCreate 在这里触发
19
+ * ↓
20
+ * Application.onCreate()
21
+ * ↓
22
+ * Activity lifecycle(FlutterActivity.onCreate → 首次 new FlutterEngine)
23
+ * ```
24
+ *
25
+ * 插件反射替换 `FlutterInjector.flutterLoader` 只要求"早于第一次
26
+ * `FlutterInjector.instance()` 真正被使用",ContentProvider 这个时机足够。
27
+ * 这是 Firebase / WorkManager / androidx.startup 都在用的自动初始化模式。
28
+ *
29
+ * # 边界:什么时候走不通
30
+ * 宿主在自己 `Application.attachBaseContext` 里 **预热 FlutterEngine**(罕见,
31
+ * 主要出现在大厂混合工程的冷启动优化)。此时 provider 还没轮到、Engine 已起来,
32
+ * 反射来不及。这类工程应在宿主 Manifest 里关掉自动初始化,改为显式调用:
33
+ *
34
+ * ```xml
35
+ * <provider
36
+ * android:name="com.flutter_patcher.flutter_patcher.FlutterPatcherAutoInitProvider"
37
+ * android:authorities="${applicationId}.flutter_patcher.autoinit"
38
+ * tools:node="remove" />
39
+ * ```
40
+ *
41
+ * 然后在自己的 Application 里:
42
+ *
43
+ * ```kotlin
44
+ * override fun attachBaseContext(base: Context) {
45
+ * super.attachBaseContext(base)
46
+ * FlutterPatcherApplication.attachPatcher(base)
47
+ * }
48
+ * ```
49
+ */
50
+ class FlutterPatcherAutoInitProvider : ContentProvider() {
51
+
52
+ companion object {
53
+ private const val TAG = "FlutterPatcher/AutoInit"
54
+ }
55
+
56
+ override fun onCreate(): Boolean {
57
+ val ctx = context ?: run {
58
+ Log.w(TAG, "context is null, skip auto init")
59
+ return false
60
+ }
61
+ Log.d(TAG, "auto init via ContentProvider.onCreate()")
62
+ FlutterPatcherApplication.attachPatcher(ctx)
63
+ return true
64
+ }
65
+
66
+ // ==================== no-op data surface ====================
67
+ // 本 provider 不对外暴露任何数据;所有 CRUD 都返回空 / 0 即可。
68
+
69
+ override fun query(
70
+ uri: Uri,
71
+ projection: Array<out String>?,
72
+ selection: String?,
73
+ selectionArgs: Array<out String>?,
74
+ sortOrder: String?
75
+ ): Cursor? = null
76
+
77
+ override fun getType(uri: Uri): String? = null
78
+
79
+ override fun insert(uri: Uri, values: ContentValues?): Uri? = null
80
+
81
+ override fun delete(
82
+ uri: Uri,
83
+ selection: String?,
84
+ selectionArgs: Array<out String>?
85
+ ): Int = 0
86
+
87
+ override fun update(
88
+ uri: Uri,
89
+ values: ContentValues?,
90
+ selection: String?,
91
+ selectionArgs: Array<out String>?
92
+ ): Int = 0
93
+ }