react-native-ensys-camera-x 1.0.7

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 (506) hide show
  1. package/CustomCamera.podspec +20 -0
  2. package/LICENSE +20 -0
  3. package/README.md +833 -0
  4. package/android/build.gradle +135 -0
  5. package/android/consumer-rules.pro +47 -0
  6. package/android/src/main/AndroidManifest.xml +72 -0
  7. package/android/src/main/java/android/print/PdfPrint.java +51 -0
  8. package/android/src/main/java/com/customcamera/AnimatedImageViewManager.kt +199 -0
  9. package/android/src/main/java/com/customcamera/AudioPlayerView.kt +322 -0
  10. package/android/src/main/java/com/customcamera/AudioRecorder.kt +506 -0
  11. package/android/src/main/java/com/customcamera/BarcodeScannerActivity.kt +361 -0
  12. package/android/src/main/java/com/customcamera/BlurBackgroundView.kt +199 -0
  13. package/android/src/main/java/com/customcamera/CacheCleanupManager.kt +386 -0
  14. package/android/src/main/java/com/customcamera/CameraAspectRatioMode.kt +77 -0
  15. package/android/src/main/java/com/customcamera/CameraAsset.kt +211 -0
  16. package/android/src/main/java/com/customcamera/CameraTheme.kt +479 -0
  17. package/android/src/main/java/com/customcamera/CompressionCacheStore.kt +504 -0
  18. package/android/src/main/java/com/customcamera/CompressionFileNameUtils.kt +7 -0
  19. package/android/src/main/java/com/customcamera/CompressionProfile.kt +195 -0
  20. package/android/src/main/java/com/customcamera/CustomCameraActivity.kt +7291 -0
  21. package/android/src/main/java/com/customcamera/CustomCameraFileProvider.kt +9 -0
  22. package/android/src/main/java/com/customcamera/CustomCameraModule.kt +2479 -0
  23. package/android/src/main/java/com/customcamera/CustomCameraPackage.kt +53 -0
  24. package/android/src/main/java/com/customcamera/DisplayUtils.kt +9 -0
  25. package/android/src/main/java/com/customcamera/DocumentPreviewView.kt +846 -0
  26. package/android/src/main/java/com/customcamera/DrawingView.kt +214 -0
  27. package/android/src/main/java/com/customcamera/EmojiPickerData.kt +228 -0
  28. package/android/src/main/java/com/customcamera/FilterStripAdapter.kt +158 -0
  29. package/android/src/main/java/com/customcamera/GifSupport.kt +215 -0
  30. package/android/src/main/java/com/customcamera/GlobalAudioPlayer.kt +320 -0
  31. package/android/src/main/java/com/customcamera/ImageExporter.kt +1394 -0
  32. package/android/src/main/java/com/customcamera/ImageFormatSupport.kt +163 -0
  33. package/android/src/main/java/com/customcamera/Localization.kt +206 -0
  34. package/android/src/main/java/com/customcamera/MediaAdapters.kt +758 -0
  35. package/android/src/main/java/com/customcamera/MediaCacheManager.kt +782 -0
  36. package/android/src/main/java/com/customcamera/MediaExportFormats.kt +201 -0
  37. package/android/src/main/java/com/customcamera/MediaFileIo.kt +310 -0
  38. package/android/src/main/java/com/customcamera/MediaMetadataStore.kt +494 -0
  39. package/android/src/main/java/com/customcamera/MediaPreviewEditor.kt +14865 -0
  40. package/android/src/main/java/com/customcamera/MediaPublisher.kt +423 -0
  41. package/android/src/main/java/com/customcamera/MediaStorageWorkflow.kt +722 -0
  42. package/android/src/main/java/com/customcamera/MediaStoreRepository.kt +378 -0
  43. package/android/src/main/java/com/customcamera/MediaThumbnailView.kt +286 -0
  44. package/android/src/main/java/com/customcamera/MediaUtils.kt +37 -0
  45. package/android/src/main/java/com/customcamera/NativeVideoViewManager.kt +845 -0
  46. package/android/src/main/java/com/customcamera/OfficeDocumentConverter.kt +2791 -0
  47. package/android/src/main/java/com/customcamera/PendingCaptureCrops.kt +88 -0
  48. package/android/src/main/java/com/customcamera/PowerPointPreview.kt +4077 -0
  49. package/android/src/main/java/com/customcamera/RecentPickerStore.kt +74 -0
  50. package/android/src/main/java/com/customcamera/SecureFileEraser.kt +411 -0
  51. package/android/src/main/java/com/customcamera/SecureKeyModule.kt +88 -0
  52. package/android/src/main/java/com/customcamera/SecureMediaDeleter.kt +493 -0
  53. package/android/src/main/java/com/customcamera/StickerPickerData.kt +107 -0
  54. package/android/src/main/java/com/customcamera/SvgDrawable.kt +175 -0
  55. package/android/src/main/java/com/customcamera/SvgIconViewManager.kt +77 -0
  56. package/android/src/main/java/com/customcamera/TextEntryController.kt +553 -0
  57. package/android/src/main/java/com/customcamera/ThumbnailLoader.kt +455 -0
  58. package/android/src/main/java/com/customcamera/UploadService.kt +909 -0
  59. package/android/src/main/java/com/customcamera/VideoEditUtils.kt +210 -0
  60. package/android/src/main/java/com/customcamera/VideoRotationTranscoder.kt +1992 -0
  61. package/android/src/main/java/com/customcamera/gif/AnimatedGifEncoder.kt +473 -0
  62. package/android/src/main/java/com/customcamera/gif/GifDecoder.kt +690 -0
  63. package/android/src/main/java/com/customcamera/gif/GifFramesDrawable.kt +160 -0
  64. package/android/src/main/java/com/customcamera/gif/GifHttp.kt +112 -0
  65. package/android/src/main/java/com/customcamera/gif/GifImageLoader.kt +186 -0
  66. package/android/src/main/java/com/customcamera/gif/GifModels.kt +74 -0
  67. package/android/src/main/java/com/customcamera/gif/GifProvider.kt +49 -0
  68. package/android/src/main/java/com/customcamera/gif/GifProviders.kt +27 -0
  69. package/android/src/main/java/com/customcamera/gif/GifRepository.kt +124 -0
  70. package/android/src/main/java/com/customcamera/gif/GifSafety.kt +33 -0
  71. package/android/src/main/java/com/customcamera/gif/GiphyGifProvider.kt +146 -0
  72. package/android/src/main/java/com/customcamera/gif/LZWEncoder.kt +258 -0
  73. package/android/src/main/java/com/customcamera/gif/NeuQuant.kt +435 -0
  74. package/android/src/main/java/com/customcamera/gif/TenorGifProvider.kt +147 -0
  75. package/android/src/main/res/drawable/bg_brush_selected.xml +5 -0
  76. package/android/src/main/res/drawable/bg_color_swatch.xml +5 -0
  77. package/android/src/main/res/drawable/bg_editor_toolbar.xml +6 -0
  78. package/android/src/main/res/drawable/bg_icon_circle.xml +5 -0
  79. package/android/src/main/res/drawable/bg_icon_circle_light.xml +5 -0
  80. package/android/src/main/res/drawable/bg_mode_tab_active.xml +6 -0
  81. package/android/src/main/res/drawable/bg_pill.xml +6 -0
  82. package/android/src/main/res/drawable/bg_recording_dot.xml +6 -0
  83. package/android/src/main/res/drawable/bg_selection_badge.xml +8 -0
  84. package/android/src/main/res/drawable/bg_send_button.xml +5 -0
  85. package/android/src/main/res/drawable/bg_shutter_inner.xml +5 -0
  86. package/android/src/main/res/drawable/bg_shutter_recording.xml +6 -0
  87. package/android/src/main/res/drawable/bg_shutter_ring.xml +8 -0
  88. package/android/src/main/res/drawable/bg_shutter_video_idle.xml +5 -0
  89. package/android/src/main/res/drawable/bg_thumbnail.xml +9 -0
  90. package/android/src/main/res/drawable/ic_aspect_ratio.xml +17 -0
  91. package/android/src/main/res/drawable/ic_audio.xml +10 -0
  92. package/android/src/main/res/drawable/ic_back.xml +10 -0
  93. package/android/src/main/res/drawable/ic_brush_medium.xml +12 -0
  94. package/android/src/main/res/drawable/ic_brush_thick.xml +12 -0
  95. package/android/src/main/res/drawable/ic_brush_thin.xml +12 -0
  96. package/android/src/main/res/drawable/ic_check.xml +17 -0
  97. package/android/src/main/res/drawable/ic_chevron_down.xml +10 -0
  98. package/android/src/main/res/drawable/ic_chevron_up.xml +13 -0
  99. package/android/src/main/res/drawable/ic_clear_drawing.xml +10 -0
  100. package/android/src/main/res/drawable/ic_close.xml +10 -0
  101. package/android/src/main/res/drawable/ic_cover.xml +9 -0
  102. package/android/src/main/res/drawable/ic_crop.xml +10 -0
  103. package/android/src/main/res/drawable/ic_delete.xml +9 -0
  104. package/android/src/main/res/drawable/ic_document.xml +10 -0
  105. package/android/src/main/res/drawable/ic_done.xml +10 -0
  106. package/android/src/main/res/drawable/ic_download.xml +10 -0
  107. package/android/src/main/res/drawable/ic_emoji.xml +10 -0
  108. package/android/src/main/res/drawable/ic_filter.xml +10 -0
  109. package/android/src/main/res/drawable/ic_flash_auto.xml +7 -0
  110. package/android/src/main/res/drawable/ic_flash_off.xml +23 -0
  111. package/android/src/main/res/drawable/ic_flash_on.xml +16 -0
  112. package/android/src/main/res/drawable/ic_flip.xml +15 -0
  113. package/android/src/main/res/drawable/ic_gallery.xml +10 -0
  114. package/android/src/main/res/drawable/ic_gif.xml +24 -0
  115. package/android/src/main/res/drawable/ic_lock.xml +19 -0
  116. package/android/src/main/res/drawable/ic_lock_open.xml +22 -0
  117. package/android/src/main/res/drawable/ic_marker.xml +10 -0
  118. package/android/src/main/res/drawable/ic_more_vert.xml +18 -0
  119. package/android/src/main/res/drawable/ic_overlay_close.xml +19 -0
  120. package/android/src/main/res/drawable/ic_overlay_resize.xml +18 -0
  121. package/android/src/main/res/drawable/ic_pause.xml +10 -0
  122. package/android/src/main/res/drawable/ic_photo_mode.xml +10 -0
  123. package/android/src/main/res/drawable/ic_play.xml +10 -0
  124. package/android/src/main/res/drawable/ic_redo.xml +10 -0
  125. package/android/src/main/res/drawable/ic_rotate.xml +9 -0
  126. package/android/src/main/res/drawable/ic_send.xml +10 -0
  127. package/android/src/main/res/drawable/ic_share.xml +10 -0
  128. package/android/src/main/res/drawable/ic_stat_upload.xml +10 -0
  129. package/android/src/main/res/drawable/ic_sticker.xml +24 -0
  130. package/android/src/main/res/drawable/ic_text.xml +10 -0
  131. package/android/src/main/res/drawable/ic_text_align_center.xml +11 -0
  132. package/android/src/main/res/drawable/ic_text_align_left.xml +11 -0
  133. package/android/src/main/res/drawable/ic_text_align_right.xml +11 -0
  134. package/android/src/main/res/drawable/ic_text_bg.xml +11 -0
  135. package/android/src/main/res/drawable/ic_trim.xml +23 -0
  136. package/android/src/main/res/drawable/ic_undo.xml +10 -0
  137. package/android/src/main/res/drawable/ic_video_mode.xml +10 -0
  138. package/android/src/main/res/drawable/ic_video_note.xml +13 -0
  139. package/android/src/main/res/drawable/ic_volume_off.xml +9 -0
  140. package/android/src/main/res/drawable/ic_volume_on.xml +9 -0
  141. package/android/src/main/res/drawable/sticker_heart.xml +9 -0
  142. package/android/src/main/res/drawable/sticker_smile.xml +9 -0
  143. package/android/src/main/res/drawable/sticker_star.xml +9 -0
  144. package/android/src/main/res/drawable/sticker_thumbsup.xml +9 -0
  145. package/android/src/main/res/values/colors.xml +34 -0
  146. package/android/src/main/res/values/ids.xml +15 -0
  147. package/android/src/main/res/values/strings.xml +15 -0
  148. package/android/src/main/res/values/styles.xml +20 -0
  149. package/android/src/main/res/xml/file_paths.xml +34 -0
  150. package/ios/CustomCamera.h +5 -0
  151. package/ios/CustomCamera.mm +214 -0
  152. package/lib/module/AudioPlayerContext.js +233 -0
  153. package/lib/module/AudioPlayerContext.js.map +1 -0
  154. package/lib/module/CameraPreview.js +304 -0
  155. package/lib/module/CameraPreview.js.map +1 -0
  156. package/lib/module/NativeAnimatedImageView.js +41 -0
  157. package/lib/module/NativeAnimatedImageView.js.map +1 -0
  158. package/lib/module/NativeAudioPlayerView.js +33 -0
  159. package/lib/module/NativeAudioPlayerView.js.map +1 -0
  160. package/lib/module/NativeBlurView.js +35 -0
  161. package/lib/module/NativeBlurView.js.map +1 -0
  162. package/lib/module/NativeCustomCamera.js +128 -0
  163. package/lib/module/NativeCustomCamera.js.map +1 -0
  164. package/lib/module/NativeCustomCamera.web.js +143 -0
  165. package/lib/module/NativeCustomCamera.web.js.map +1 -0
  166. package/lib/module/NativeCustomVideoView.js +5 -0
  167. package/lib/module/NativeCustomVideoView.js.map +1 -0
  168. package/lib/module/NativeDocumentPreviewView.js +156 -0
  169. package/lib/module/NativeDocumentPreviewView.js.map +1 -0
  170. package/lib/module/NativeMediaThumbnailView.js +110 -0
  171. package/lib/module/NativeMediaThumbnailView.js.map +1 -0
  172. package/lib/module/NativeSvgIcon.js +32 -0
  173. package/lib/module/NativeSvgIcon.js.map +1 -0
  174. package/lib/module/ResponsivePreviewContainer.js +171 -0
  175. package/lib/module/ResponsivePreviewContainer.js.map +1 -0
  176. package/lib/module/accessibility.js +61 -0
  177. package/lib/module/accessibility.js.map +1 -0
  178. package/lib/module/config.js +352 -0
  179. package/lib/module/config.js.map +1 -0
  180. package/lib/module/cpuOptimization.js +296 -0
  181. package/lib/module/cpuOptimization.js.map +1 -0
  182. package/lib/module/db/DatabaseManager.js +191 -0
  183. package/lib/module/db/DatabaseManager.js.map +1 -0
  184. package/lib/module/db/MediaMapper.js +284 -0
  185. package/lib/module/db/MediaMapper.js.map +1 -0
  186. package/lib/module/db/MediaRepository.js +242 -0
  187. package/lib/module/db/MediaRepository.js.map +1 -0
  188. package/lib/module/db/index.js +41 -0
  189. package/lib/module/db/index.js.map +1 -0
  190. package/lib/module/db/migrations.js +180 -0
  191. package/lib/module/db/migrations.js.map +1 -0
  192. package/lib/module/db/react-native-sqlcipher.d.js +2 -0
  193. package/lib/module/db/react-native-sqlcipher.d.js.map +1 -0
  194. package/lib/module/db/secureKey.js +87 -0
  195. package/lib/module/db/secureKey.js.map +1 -0
  196. package/lib/module/db/types.js +57 -0
  197. package/lib/module/db/types.js.map +1 -0
  198. package/lib/module/db/useMediaDatabase.js +123 -0
  199. package/lib/module/db/useMediaDatabase.js.map +1 -0
  200. package/lib/module/defaultIcons.js +2259 -0
  201. package/lib/module/defaultIcons.js.map +1 -0
  202. package/lib/module/deviceCapabilities.js +408 -0
  203. package/lib/module/deviceCapabilities.js.map +1 -0
  204. package/lib/module/errors.js +267 -0
  205. package/lib/module/errors.js.map +1 -0
  206. package/lib/module/iconConfig.js +796 -0
  207. package/lib/module/iconConfig.js.map +1 -0
  208. package/lib/module/icons.js +514 -0
  209. package/lib/module/icons.js.map +1 -0
  210. package/lib/module/index.js +251 -0
  211. package/lib/module/index.js.map +1 -0
  212. package/lib/module/jest-globals.d.js +2 -0
  213. package/lib/module/jest-globals.d.js.map +1 -0
  214. package/lib/module/library/MediaLibrary.js +107 -0
  215. package/lib/module/library/MediaLibrary.js.map +1 -0
  216. package/lib/module/library/MediaLibraryScreen.js +2597 -0
  217. package/lib/module/library/MediaLibraryScreen.js.map +1 -0
  218. package/lib/module/library/components/AudioPreview.js +74 -0
  219. package/lib/module/library/components/AudioPreview.js.map +1 -0
  220. package/lib/module/library/components/DocumentPreview.js +331 -0
  221. package/lib/module/library/components/DocumentPreview.js.map +1 -0
  222. package/lib/module/library/components/ExpandableFab.js +343 -0
  223. package/lib/module/library/components/ExpandableFab.js.map +1 -0
  224. package/lib/module/library/components/FileTiles.js +251 -0
  225. package/lib/module/library/components/FileTiles.js.map +1 -0
  226. package/lib/module/library/components/ImagePreview.js +70 -0
  227. package/lib/module/library/components/ImagePreview.js.map +1 -0
  228. package/lib/module/library/components/MediaMetadataHeader.js +175 -0
  229. package/lib/module/library/components/MediaMetadataHeader.js.map +1 -0
  230. package/lib/module/library/components/RecordAudioScreen.js +816 -0
  231. package/lib/module/library/components/RecordAudioScreen.js.map +1 -0
  232. package/lib/module/library/components/VideoPreview.js +453 -0
  233. package/lib/module/library/components/VideoPreview.js.map +1 -0
  234. package/lib/module/library/components/ViewerCounter.js +55 -0
  235. package/lib/module/library/components/ViewerCounter.js.map +1 -0
  236. package/lib/module/library/compressionEstimate.js +507 -0
  237. package/lib/module/library/compressionEstimate.js.map +1 -0
  238. package/lib/module/library/fileMetadata.js +196 -0
  239. package/lib/module/library/fileMetadata.js.map +1 -0
  240. package/lib/module/library/index.js +42 -0
  241. package/lib/module/library/index.js.map +1 -0
  242. package/lib/module/library/libraryIconRegistry.js +69 -0
  243. package/lib/module/library/libraryIconRegistry.js.map +1 -0
  244. package/lib/module/library/libraryIcons.js +858 -0
  245. package/lib/module/library/libraryIcons.js.map +1 -0
  246. package/lib/module/library/localizedFormat.js +181 -0
  247. package/lib/module/library/localizedFormat.js.map +1 -0
  248. package/lib/module/library/palette.js +225 -0
  249. package/lib/module/library/palette.js.map +1 -0
  250. package/lib/module/library/responsive.js +53 -0
  251. package/lib/module/library/responsive.js.map +1 -0
  252. package/lib/module/locales.js +416 -0
  253. package/lib/module/locales.js.map +1 -0
  254. package/lib/module/localization.js +1064 -0
  255. package/lib/module/localization.js.map +1 -0
  256. package/lib/module/logger.js +20 -0
  257. package/lib/module/logger.js.map +1 -0
  258. package/lib/module/lowMemoryCache.js +206 -0
  259. package/lib/module/lowMemoryCache.js.map +1 -0
  260. package/lib/module/mediaHistory.js +33 -0
  261. package/lib/module/mediaHistory.js.map +1 -0
  262. package/lib/module/mediaStorageService.js +402 -0
  263. package/lib/module/mediaStorageService.js.map +1 -0
  264. package/lib/module/memory.js +79 -0
  265. package/lib/module/memory.js.map +1 -0
  266. package/lib/module/nativeActions.js +307 -0
  267. package/lib/module/nativeActions.js.map +1 -0
  268. package/lib/module/nativeComponentSupport.js +61 -0
  269. package/lib/module/nativeComponentSupport.js.map +1 -0
  270. package/lib/module/networkPerformance.js +24 -0
  271. package/lib/module/networkPerformance.js.map +1 -0
  272. package/lib/module/package.json +1 -0
  273. package/lib/module/performance.js +104 -0
  274. package/lib/module/performance.js.map +1 -0
  275. package/lib/module/performanceMonitor.js +155 -0
  276. package/lib/module/performanceMonitor.js.map +1 -0
  277. package/lib/module/permissions.js +370 -0
  278. package/lib/module/permissions.js.map +1 -0
  279. package/lib/module/rendering.js +40 -0
  280. package/lib/module/rendering.js.map +1 -0
  281. package/lib/module/security.js +18 -0
  282. package/lib/module/security.js.map +1 -0
  283. package/lib/module/state.js +266 -0
  284. package/lib/module/state.js.map +1 -0
  285. package/lib/module/storage.js +400 -0
  286. package/lib/module/storage.js.map +1 -0
  287. package/lib/module/theme.js +390 -0
  288. package/lib/module/theme.js.map +1 -0
  289. package/lib/module/types.js +4 -0
  290. package/lib/module/types.js.map +1 -0
  291. package/lib/module/utils.js +115 -0
  292. package/lib/module/utils.js.map +1 -0
  293. package/lib/module/validation.js +288 -0
  294. package/lib/module/validation.js.map +1 -0
  295. package/lib/typescript/package.json +1 -0
  296. package/lib/typescript/src/AudioPlayerContext.d.ts +27 -0
  297. package/lib/typescript/src/AudioPlayerContext.d.ts.map +1 -0
  298. package/lib/typescript/src/CameraPreview.d.ts +13 -0
  299. package/lib/typescript/src/CameraPreview.d.ts.map +1 -0
  300. package/lib/typescript/src/NativeAnimatedImageView.d.ts +33 -0
  301. package/lib/typescript/src/NativeAnimatedImageView.d.ts.map +1 -0
  302. package/lib/typescript/src/NativeAudioPlayerView.d.ts +12 -0
  303. package/lib/typescript/src/NativeAudioPlayerView.d.ts.map +1 -0
  304. package/lib/typescript/src/NativeBlurView.d.ts +12 -0
  305. package/lib/typescript/src/NativeBlurView.d.ts.map +1 -0
  306. package/lib/typescript/src/NativeCustomCamera.d.ts +167 -0
  307. package/lib/typescript/src/NativeCustomCamera.d.ts.map +1 -0
  308. package/lib/typescript/src/NativeCustomCamera.web.d.ts +47 -0
  309. package/lib/typescript/src/NativeCustomCamera.web.d.ts.map +1 -0
  310. package/lib/typescript/src/NativeCustomVideoView.d.ts +30 -0
  311. package/lib/typescript/src/NativeCustomVideoView.d.ts.map +1 -0
  312. package/lib/typescript/src/NativeDocumentPreviewView.d.ts +48 -0
  313. package/lib/typescript/src/NativeDocumentPreviewView.d.ts.map +1 -0
  314. package/lib/typescript/src/NativeMediaThumbnailView.d.ts +54 -0
  315. package/lib/typescript/src/NativeMediaThumbnailView.d.ts.map +1 -0
  316. package/lib/typescript/src/NativeSvgIcon.d.ts +25 -0
  317. package/lib/typescript/src/NativeSvgIcon.d.ts.map +1 -0
  318. package/lib/typescript/src/ResponsivePreviewContainer.d.ts +92 -0
  319. package/lib/typescript/src/ResponsivePreviewContainer.d.ts.map +1 -0
  320. package/lib/typescript/src/accessibility.d.ts +20 -0
  321. package/lib/typescript/src/accessibility.d.ts.map +1 -0
  322. package/lib/typescript/src/config.d.ts +145 -0
  323. package/lib/typescript/src/config.d.ts.map +1 -0
  324. package/lib/typescript/src/cpuOptimization.d.ts +63 -0
  325. package/lib/typescript/src/cpuOptimization.d.ts.map +1 -0
  326. package/lib/typescript/src/db/DatabaseManager.d.ts +48 -0
  327. package/lib/typescript/src/db/DatabaseManager.d.ts.map +1 -0
  328. package/lib/typescript/src/db/MediaMapper.d.ts +77 -0
  329. package/lib/typescript/src/db/MediaMapper.d.ts.map +1 -0
  330. package/lib/typescript/src/db/MediaRepository.d.ts +66 -0
  331. package/lib/typescript/src/db/MediaRepository.d.ts.map +1 -0
  332. package/lib/typescript/src/db/index.d.ts +23 -0
  333. package/lib/typescript/src/db/index.d.ts.map +1 -0
  334. package/lib/typescript/src/db/migrations.d.ts +31 -0
  335. package/lib/typescript/src/db/migrations.d.ts.map +1 -0
  336. package/lib/typescript/src/db/secureKey.d.ts +10 -0
  337. package/lib/typescript/src/db/secureKey.d.ts.map +1 -0
  338. package/lib/typescript/src/db/types.d.ts +115 -0
  339. package/lib/typescript/src/db/types.d.ts.map +1 -0
  340. package/lib/typescript/src/db/useMediaDatabase.d.ts +41 -0
  341. package/lib/typescript/src/db/useMediaDatabase.d.ts.map +1 -0
  342. package/lib/typescript/src/defaultIcons.d.ts +139 -0
  343. package/lib/typescript/src/defaultIcons.d.ts.map +1 -0
  344. package/lib/typescript/src/deviceCapabilities.d.ts +92 -0
  345. package/lib/typescript/src/deviceCapabilities.d.ts.map +1 -0
  346. package/lib/typescript/src/errors.d.ts +95 -0
  347. package/lib/typescript/src/errors.d.ts.map +1 -0
  348. package/lib/typescript/src/iconConfig.d.ts +176 -0
  349. package/lib/typescript/src/iconConfig.d.ts.map +1 -0
  350. package/lib/typescript/src/icons.d.ts +217 -0
  351. package/lib/typescript/src/icons.d.ts.map +1 -0
  352. package/lib/typescript/src/index.d.ts +104 -0
  353. package/lib/typescript/src/index.d.ts.map +1 -0
  354. package/lib/typescript/src/library/MediaLibrary.d.ts +57 -0
  355. package/lib/typescript/src/library/MediaLibrary.d.ts.map +1 -0
  356. package/lib/typescript/src/library/MediaLibraryScreen.d.ts +51 -0
  357. package/lib/typescript/src/library/MediaLibraryScreen.d.ts.map +1 -0
  358. package/lib/typescript/src/library/components/AudioPreview.d.ts +6 -0
  359. package/lib/typescript/src/library/components/AudioPreview.d.ts.map +1 -0
  360. package/lib/typescript/src/library/components/DocumentPreview.d.ts +10 -0
  361. package/lib/typescript/src/library/components/DocumentPreview.d.ts.map +1 -0
  362. package/lib/typescript/src/library/components/ExpandableFab.d.ts +39 -0
  363. package/lib/typescript/src/library/components/ExpandableFab.d.ts.map +1 -0
  364. package/lib/typescript/src/library/components/FileTiles.d.ts +8 -0
  365. package/lib/typescript/src/library/components/FileTiles.d.ts.map +1 -0
  366. package/lib/typescript/src/library/components/ImagePreview.d.ts +5 -0
  367. package/lib/typescript/src/library/components/ImagePreview.d.ts.map +1 -0
  368. package/lib/typescript/src/library/components/MediaMetadataHeader.d.ts +16 -0
  369. package/lib/typescript/src/library/components/MediaMetadataHeader.d.ts.map +1 -0
  370. package/lib/typescript/src/library/components/RecordAudioScreen.d.ts +8 -0
  371. package/lib/typescript/src/library/components/RecordAudioScreen.d.ts.map +1 -0
  372. package/lib/typescript/src/library/components/VideoPreview.d.ts +20 -0
  373. package/lib/typescript/src/library/components/VideoPreview.d.ts.map +1 -0
  374. package/lib/typescript/src/library/components/ViewerCounter.d.ts +13 -0
  375. package/lib/typescript/src/library/components/ViewerCounter.d.ts.map +1 -0
  376. package/lib/typescript/src/library/compressionEstimate.d.ts +313 -0
  377. package/lib/typescript/src/library/compressionEstimate.d.ts.map +1 -0
  378. package/lib/typescript/src/library/fileMetadata.d.ts +33 -0
  379. package/lib/typescript/src/library/fileMetadata.d.ts.map +1 -0
  380. package/lib/typescript/src/library/index.d.ts +40 -0
  381. package/lib/typescript/src/library/index.d.ts.map +1 -0
  382. package/lib/typescript/src/library/libraryIconRegistry.d.ts +35 -0
  383. package/lib/typescript/src/library/libraryIconRegistry.d.ts.map +1 -0
  384. package/lib/typescript/src/library/libraryIcons.d.ts +85 -0
  385. package/lib/typescript/src/library/libraryIcons.d.ts.map +1 -0
  386. package/lib/typescript/src/library/localizedFormat.d.ts +49 -0
  387. package/lib/typescript/src/library/localizedFormat.d.ts.map +1 -0
  388. package/lib/typescript/src/library/palette.d.ts +104 -0
  389. package/lib/typescript/src/library/palette.d.ts.map +1 -0
  390. package/lib/typescript/src/library/responsive.d.ts +15 -0
  391. package/lib/typescript/src/library/responsive.d.ts.map +1 -0
  392. package/lib/typescript/src/locales.d.ts +103 -0
  393. package/lib/typescript/src/locales.d.ts.map +1 -0
  394. package/lib/typescript/src/localization.d.ts +1051 -0
  395. package/lib/typescript/src/localization.d.ts.map +1 -0
  396. package/lib/typescript/src/logger.d.ts +4 -0
  397. package/lib/typescript/src/logger.d.ts.map +1 -0
  398. package/lib/typescript/src/lowMemoryCache.d.ts +64 -0
  399. package/lib/typescript/src/lowMemoryCache.d.ts.map +1 -0
  400. package/lib/typescript/src/mediaHistory.d.ts +3 -0
  401. package/lib/typescript/src/mediaHistory.d.ts.map +1 -0
  402. package/lib/typescript/src/mediaStorageService.d.ts +155 -0
  403. package/lib/typescript/src/mediaStorageService.d.ts.map +1 -0
  404. package/lib/typescript/src/memory.d.ts +21 -0
  405. package/lib/typescript/src/memory.d.ts.map +1 -0
  406. package/lib/typescript/src/nativeActions.d.ts +174 -0
  407. package/lib/typescript/src/nativeActions.d.ts.map +1 -0
  408. package/lib/typescript/src/nativeComponentSupport.d.ts +30 -0
  409. package/lib/typescript/src/nativeComponentSupport.d.ts.map +1 -0
  410. package/lib/typescript/src/networkPerformance.d.ts +12 -0
  411. package/lib/typescript/src/networkPerformance.d.ts.map +1 -0
  412. package/lib/typescript/src/performance.d.ts +32 -0
  413. package/lib/typescript/src/performance.d.ts.map +1 -0
  414. package/lib/typescript/src/performanceMonitor.d.ts +45 -0
  415. package/lib/typescript/src/performanceMonitor.d.ts.map +1 -0
  416. package/lib/typescript/src/permissions.d.ts +132 -0
  417. package/lib/typescript/src/permissions.d.ts.map +1 -0
  418. package/lib/typescript/src/rendering.d.ts +9 -0
  419. package/lib/typescript/src/rendering.d.ts.map +1 -0
  420. package/lib/typescript/src/security.d.ts +6 -0
  421. package/lib/typescript/src/security.d.ts.map +1 -0
  422. package/lib/typescript/src/state.d.ts +102 -0
  423. package/lib/typescript/src/state.d.ts.map +1 -0
  424. package/lib/typescript/src/storage.d.ts +117 -0
  425. package/lib/typescript/src/storage.d.ts.map +1 -0
  426. package/lib/typescript/src/theme.d.ts +15 -0
  427. package/lib/typescript/src/theme.d.ts.map +1 -0
  428. package/lib/typescript/src/types.d.ts +802 -0
  429. package/lib/typescript/src/types.d.ts.map +1 -0
  430. package/lib/typescript/src/utils.d.ts +50 -0
  431. package/lib/typescript/src/utils.d.ts.map +1 -0
  432. package/lib/typescript/src/validation.d.ts +52 -0
  433. package/lib/typescript/src/validation.d.ts.map +1 -0
  434. package/package.json +204 -0
  435. package/react-native.config.js +17 -0
  436. package/src/AudioPlayerContext.tsx +220 -0
  437. package/src/CameraPreview.tsx +352 -0
  438. package/src/NativeAnimatedImageView.tsx +71 -0
  439. package/src/NativeAudioPlayerView.tsx +39 -0
  440. package/src/NativeBlurView.tsx +40 -0
  441. package/src/NativeCustomCamera.ts +305 -0
  442. package/src/NativeCustomCamera.web.ts +192 -0
  443. package/src/NativeCustomVideoView.ts +30 -0
  444. package/src/NativeDocumentPreviewView.tsx +196 -0
  445. package/src/NativeMediaThumbnailView.tsx +167 -0
  446. package/src/NativeSvgIcon.tsx +46 -0
  447. package/src/ResponsivePreviewContainer.tsx +227 -0
  448. package/src/accessibility.ts +80 -0
  449. package/src/config.ts +406 -0
  450. package/src/cpuOptimization.ts +365 -0
  451. package/src/db/DatabaseManager.ts +212 -0
  452. package/src/db/MediaMapper.ts +377 -0
  453. package/src/db/MediaRepository.ts +313 -0
  454. package/src/db/index.ts +73 -0
  455. package/src/db/migrations.ts +222 -0
  456. package/src/db/react-native-sqlcipher.d.ts +76 -0
  457. package/src/db/secureKey.ts +100 -0
  458. package/src/db/types.ts +123 -0
  459. package/src/db/useMediaDatabase.ts +191 -0
  460. package/src/defaultIcons.tsx +2390 -0
  461. package/src/deviceCapabilities.ts +438 -0
  462. package/src/errors.ts +350 -0
  463. package/src/iconConfig.tsx +962 -0
  464. package/src/icons.tsx +788 -0
  465. package/src/index.tsx +763 -0
  466. package/src/jest-globals.d.ts +9 -0
  467. package/src/library/MediaLibrary.tsx +141 -0
  468. package/src/library/MediaLibraryScreen.tsx +3003 -0
  469. package/src/library/components/AudioPreview.tsx +73 -0
  470. package/src/library/components/DocumentPreview.tsx +361 -0
  471. package/src/library/components/ExpandableFab.tsx +342 -0
  472. package/src/library/components/FileTiles.tsx +252 -0
  473. package/src/library/components/ImagePreview.tsx +72 -0
  474. package/src/library/components/MediaMetadataHeader.tsx +208 -0
  475. package/src/library/components/RecordAudioScreen.tsx +845 -0
  476. package/src/library/components/VideoPreview.tsx +478 -0
  477. package/src/library/components/ViewerCounter.tsx +71 -0
  478. package/src/library/compressionEstimate.ts +662 -0
  479. package/src/library/fileMetadata.ts +212 -0
  480. package/src/library/index.ts +66 -0
  481. package/src/library/libraryIconRegistry.tsx +86 -0
  482. package/src/library/libraryIcons.tsx +928 -0
  483. package/src/library/localizedFormat.ts +207 -0
  484. package/src/library/palette.ts +281 -0
  485. package/src/library/responsive.ts +49 -0
  486. package/src/locales.ts +381 -0
  487. package/src/localization.ts +1947 -0
  488. package/src/logger.ts +20 -0
  489. package/src/lowMemoryCache.ts +218 -0
  490. package/src/mediaHistory.ts +42 -0
  491. package/src/mediaStorageService.ts +546 -0
  492. package/src/memory.ts +102 -0
  493. package/src/nativeActions.ts +422 -0
  494. package/src/nativeComponentSupport.ts +60 -0
  495. package/src/networkPerformance.ts +30 -0
  496. package/src/performance.ts +137 -0
  497. package/src/performanceMonitor.ts +219 -0
  498. package/src/permissions.ts +403 -0
  499. package/src/rendering.ts +47 -0
  500. package/src/security.ts +19 -0
  501. package/src/state.ts +333 -0
  502. package/src/storage.ts +453 -0
  503. package/src/theme.tsx +467 -0
  504. package/src/types.ts +946 -0
  505. package/src/utils.ts +177 -0
  506. package/src/validation.ts +372 -0
@@ -0,0 +1,74 @@
1
+ package com.customcamera
2
+
3
+ import android.content.Context
4
+
5
+ /**
6
+ * Persists the recently used emojis and stickers for the preview editor's pickers
7
+ * (most-recent-first, deduplicated, capped) in SharedPreferences, so the "Recent"
8
+ * category survives process restarts. Writes are async (apply()) and the lists are
9
+ * tiny strings, so there is no measurable I/O or memory cost on low-end devices.
10
+ *
11
+ * Stickers are encoded one per line as "res:<drawable-name>" (bundled vectors — the
12
+ * NAME is stored because resource ids are not stable across builds) or
13
+ * "emoji:<glyph>". Emojis are stored one glyph per line. '\n' is a safe separator:
14
+ * it can appear in neither a resource name nor an emoji sequence.
15
+ */
16
+ internal object RecentPickerStore {
17
+
18
+ private const val PREFS = "camera_picker_recents"
19
+ private const val KEY_EMOJIS = "recent_emojis"
20
+ private const val KEY_STICKERS = "recent_stickers"
21
+ private const val MAX_EMOJIS = 32
22
+ private const val MAX_STICKERS = 18
23
+
24
+ private fun prefs(context: Context) =
25
+ context.applicationContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
26
+
27
+ private fun load(context: Context, key: String): List<String> =
28
+ prefs(context).getString(key, null)
29
+ ?.split('\n')
30
+ ?.filter { it.isNotBlank() }
31
+ ?: emptyList()
32
+
33
+ private fun save(context: Context, key: String, entry: String, max: Int) {
34
+ val updated = (listOf(entry) + load(context, key).filter { it != entry }).take(max)
35
+ prefs(context).edit().putString(key, updated.joinToString("\n")).apply()
36
+ }
37
+
38
+ // ----- Emojis -----
39
+
40
+ fun recentEmojis(context: Context): List<String> = load(context, KEY_EMOJIS)
41
+
42
+ fun addEmoji(context: Context, glyph: String) =
43
+ save(context, KEY_EMOJIS, glyph, MAX_EMOJIS)
44
+
45
+ // ----- Stickers -----
46
+
47
+ fun recentStickers(context: Context): List<StickerPickerData.Sticker> =
48
+ load(context, KEY_STICKERS).mapNotNull { entry ->
49
+ when {
50
+ entry.startsWith("res:") -> {
51
+ // Resolve the stored drawable name back to this build's id; drop entries
52
+ // whose drawable no longer exists (e.g. a sticker removed in an update).
53
+ val id = context.resources.getIdentifier(
54
+ entry.removePrefix("res:"), "drawable", context.packageName
55
+ )
56
+ if (id != 0) StickerPickerData.Sticker.Res(id) else null
57
+ }
58
+ entry.startsWith("emoji:") -> StickerPickerData.Sticker.Emoji(entry.removePrefix("emoji:"))
59
+ else -> null
60
+ }
61
+ }
62
+
63
+ fun addSticker(context: Context, sticker: StickerPickerData.Sticker) {
64
+ val entry = when (sticker) {
65
+ is StickerPickerData.Sticker.Res -> {
66
+ val name = runCatching { context.resources.getResourceEntryName(sticker.resId) }.getOrNull()
67
+ ?: return
68
+ "res:$name"
69
+ }
70
+ is StickerPickerData.Sticker.Emoji -> "emoji:${sticker.glyph}"
71
+ }
72
+ save(context, KEY_STICKERS, entry, MAX_STICKERS)
73
+ }
74
+ }
@@ -0,0 +1,411 @@
1
+ package com.customcamera
2
+
3
+ import android.os.ParcelFileDescriptor
4
+ import android.util.Log
5
+ import java.io.File
6
+ import java.io.FileDescriptor
7
+ import java.io.FileOutputStream
8
+ import java.io.RandomAccessFile
9
+ import java.security.SecureRandom
10
+ import javax.crypto.Cipher
11
+ import javax.crypto.spec.IvParameterSpec
12
+ import javax.crypto.spec.SecretKeySpec
13
+
14
+ /**
15
+ * Destroys the CONTENT of a media file before its directory entry is unlinked.
16
+ *
17
+ * `File.delete()` only detaches a name from an inode: every byte of the photo or video stays on
18
+ * the flash until the filesystem happens to reuse those blocks, which on a half-empty device can
19
+ * be never. Anything that can read the raw partition — a forensic tool, a recovery image, a
20
+ * backup of the app's sandbox — recovers the deleted media in full. This class removes that
21
+ * window by writing over the file's own extent first, so the delete that follows unlinks blocks
22
+ * that no longer contain the media.
23
+ *
24
+ * Three properties matter and each one is a deliberate choice here:
25
+ *
26
+ * - **The bytes that land are indistinguishable from noise.** [SecureRandom] supplies
27
+ * cryptographically secure random data, and that data is then run through AES-CTR under a
28
+ * single-use key that is generated in memory and never stored, logged, or derived from
29
+ * anything reproducible. The keystream is discarded with the object, so what remains on the
30
+ * platter cannot be distinguished from — or reduced to — anything meaningful, and carries no
31
+ * residual structure that would hint at the file's former size class or format.
32
+ * - **The overwrite is longer than the file.** The pass runs to `original size + `
33
+ * [PAD_BYTES], so the padding spills past the original end-of-file into freshly allocated
34
+ * blocks. That covers the tail slack of the last original block and denies a recovery tool the
35
+ * single most useful fact it has left after an overwrite: the exact original length.
36
+ * - **Nothing is ever held in RAM at file scale.** Both the random source and the cipher output
37
+ * are fixed-size chunk buffers ([chunkBytes], one buffer each, sized down on small-heap
38
+ * devices). Erasing a 2 GB recording costs the same handful of kilobytes as erasing a
39
+ * thumbnail — there is no path here that allocates against the file's length.
40
+ *
41
+ * The overwrite is issued in place through [RandomAccessFile] rather than by truncating and
42
+ * rewriting. Truncating first would release the original blocks back to the allocator, and the
43
+ * filesystem would then be free to satisfy the rewrite from somewhere else entirely — leaving the
44
+ * media exactly where it was, with a wall of random data written harmlessly beside it.
45
+ *
46
+ * Every entry point reports whether the overwrite actually completed. A caller must never
47
+ * translate a failure here into "securely deleted": see [Result.securelyOverwritten].
48
+ */
49
+ internal object SecureFileEraser {
50
+ private const val TAG = "SecureFileEraser"
51
+
52
+ /**
53
+ * Bytes written PAST the original end of file — 2 MiB.
54
+ *
55
+ * The point is not volume, it is length obfuscation. Overwriting exactly `length` bytes leaves
56
+ * the file's original size recoverable from its metadata and block allocation, which for media
57
+ * is itself disclosure (it identifies the clip, and pins which of several deleted files a
58
+ * recovered fragment belonged to). Running past the end also guarantees the partially used
59
+ * final block of the original file is covered in full rather than up to the old EOF offset.
60
+ */
61
+ const val PAD_BYTES: Long = 2L * 1024L * 1024L
62
+
63
+ /** Head-room left unclaimed so the padding pass cannot be the write that fills the volume. */
64
+ private const val SPACE_MARGIN_BYTES: Long = 8L * 1024L * 1024L
65
+
66
+ /** Transient buffer size on a normal-heap device. Matches the copy path's 1 MiB unit. */
67
+ private const val CHUNK_BYTES: Int = MediaFileIo.COPY_BUFFER_BYTES
68
+
69
+ /**
70
+ * Buffer size on a small-heap device. A deletion can be triggered while the media grid holds a
71
+ * screenful of decoded thumbnails, so the erase path must not be the allocation that pushes a
72
+ * low-RAM device into an OOM. 256 KiB still reduces a 1 GB clip to ~4,000 write calls.
73
+ */
74
+ private const val SMALL_HEAP_CHUNK_BYTES: Int = 256 * 1024
75
+
76
+ /** Outcome of one overwrite, and the only honest basis for reporting a secure deletion. */
77
+ data class Result(
78
+ /**
79
+ * True ONLY when every byte of the original extent was overwritten and the write was flushed
80
+ * to storage. False means some of the original media may still be readable on the device, and
81
+ * the caller MUST NOT describe the deletion as secure.
82
+ */
83
+ val securelyOverwritten: Boolean,
84
+ /** The file's exact length before the pass, measured immediately before writing. */
85
+ val originalBytes: Long,
86
+ /** Bytes actually written and flushed. `originalBytes + `[PAD_BYTES] on a complete pass. */
87
+ val overwrittenBytes: Long,
88
+ /** True when the full padding also landed — see [PAD_BYTES]. */
89
+ val padded: Boolean,
90
+ val error: String? = null
91
+ ) {
92
+ companion object {
93
+ fun failed(originalBytes: Long, error: String) =
94
+ Result(
95
+ securelyOverwritten = false,
96
+ originalBytes = originalBytes,
97
+ overwrittenBytes = 0L,
98
+ padded = false,
99
+ error = error
100
+ )
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Overwrite [file]'s content, then unlink it.
106
+ *
107
+ * The unlink is preceded by a rename to a random name in the same directory. A directory entry
108
+ * outlives the file it names in most filesystems' journals, so the original name — which for
109
+ * this app's media encodes the capture timestamp and container — would otherwise survive an
110
+ * otherwise perfect content wipe.
111
+ *
112
+ * A file that no longer exists is reported as securely erased with a zero length: the caller
113
+ * asked for it to be gone and it is, with nothing left to overwrite. Returning a failure there
114
+ * would make every retry and every already-cleaned duplicate look like a security incident.
115
+ */
116
+ fun erase(file: File, padBytes: Long = PAD_BYTES): Result {
117
+ if (!file.exists()) {
118
+ return Result(
119
+ securelyOverwritten = true,
120
+ originalBytes = 0L,
121
+ overwrittenBytes = 0L,
122
+ padded = false
123
+ )
124
+ }
125
+ if (!file.isFile) return Result.failed(0L, "Not a regular file: ${file.absolutePath}")
126
+
127
+ val result = overwrite(file, padBytes)
128
+
129
+ // Unlink whatever the overwrite achieved. This runs even on a failed overwrite: the caller's
130
+ // instruction was to delete the media, and leaving a half-scrubbed file in place would be
131
+ // both a leak and a phantom entry in the library. The Result still reports the truth about
132
+ // the overwrite, which is what the caller surfaces.
133
+ val scrubbed = renameToRandom(file)
134
+ val deleted = runCatching { scrubbed.delete() }.getOrDefault(false) || !scrubbed.exists()
135
+ if (!deleted) {
136
+ Log.e(TAG, "Overwrote but could not unlink ${scrubbed.absolutePath}")
137
+ return result.copy(
138
+ securelyOverwritten = false,
139
+ error = result.error ?: "File could not be unlinked after overwrite"
140
+ )
141
+ }
142
+
143
+ return result
144
+ }
145
+
146
+ /**
147
+ * Overwrite [file]'s content in place, without deleting it.
148
+ *
149
+ * Separated from [erase] because a content URI cannot be renamed or unlinked as a path — see
150
+ * the [ParcelFileDescriptor] overload — and because the two steps fail independently.
151
+ */
152
+ fun overwrite(file: File, padBytes: Long = PAD_BYTES): Result {
153
+ // The exact size is read once, here, and every later figure derives from it. Reading it
154
+ // inside the loop instead would race against our own writes as the file grows.
155
+ val originalBytes = file.length()
156
+ val pad = effectivePad(file.parentFile, padBytes, originalBytes)
157
+
158
+ return try {
159
+ RandomAccessFile(file, "rw").use { raf ->
160
+ raf.seek(0L)
161
+ writeGarbage(
162
+ write = { buffer, length -> raf.write(buffer, 0, length) },
163
+ sync = {
164
+ // Both barriers matter and they are not the same barrier: force(true) pushes the
165
+ // channel's own buffering plus file metadata, fd.sync() is the fsync that makes the
166
+ // kernel commit the data to the storage device. Without them the "overwrite" can sit
167
+ // in the page cache and be discarded on a crash or battery pull, and the original
168
+ // blocks would come back untouched.
169
+ raf.channel.force(true)
170
+ raf.fd.sync()
171
+ },
172
+ originalBytes = originalBytes,
173
+ padBytes = pad
174
+ )
175
+ }
176
+ } catch (t: Throwable) {
177
+ Log.e(TAG, "Secure overwrite failed for ${file.absolutePath}", t)
178
+ Result.failed(originalBytes, t.message ?: t.javaClass.simpleName)
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Overwrite the content behind an already-open read/write [pfd].
184
+ *
185
+ * This is the path for a `content://` URI, where there is no path to open and no name to
186
+ * rename: whether the bytes can be reached at all is the provider's decision. The descriptor
187
+ * must have been opened `"rw"`; a read-only one throws and is reported as a failed overwrite
188
+ * rather than silently skipped.
189
+ *
190
+ * The descriptor is NOT closed here — its owner opened it and still has to delete the row.
191
+ */
192
+ fun overwrite(pfd: ParcelFileDescriptor, padBytes: Long = PAD_BYTES): Result {
193
+ val originalBytes = pfd.statSize.coerceAtLeast(0L)
194
+
195
+ return try {
196
+ // Wrapping an existing descriptor does NOT truncate it (unlike opening a path for write),
197
+ // so this writes over the provider's blocks from offset 0 exactly as the File path does.
198
+ val output = FileOutputStream(pfd.fileDescriptor)
199
+ val descriptor: FileDescriptor = pfd.fileDescriptor
200
+ writeGarbage(
201
+ write = { buffer, length -> output.write(buffer, 0, length) },
202
+ sync = {
203
+ output.flush()
204
+ descriptor.sync()
205
+ },
206
+ originalBytes = originalBytes,
207
+ padBytes = padBytes
208
+ )
209
+ } catch (t: Throwable) {
210
+ Log.e(TAG, "Secure overwrite failed for descriptor", t)
211
+ Result.failed(originalBytes, t.message ?: t.javaClass.simpleName)
212
+ }
213
+ }
214
+
215
+ /**
216
+ * The single write loop behind every entry point.
217
+ *
218
+ * [write] receives a reused buffer and a length; [sync] is the flush barrier, invoked once the
219
+ * last byte has been handed over. Both are lambdas so the [File] and [ParcelFileDescriptor]
220
+ * paths share this logic exactly — an overwrite that behaved differently between a cache file
221
+ * and a provider-backed one would be the kind of gap that only shows up in the field.
222
+ *
223
+ * Partial progress is graded rather than discarded. Reaching at least [originalBytes] means the
224
+ * media itself is gone even if the padding was cut short by a full volume, which is a materially
225
+ * different outcome from stopping halfway through the file, and callers are told which happened.
226
+ */
227
+ private fun writeGarbage(
228
+ write: (ByteArray, Int) -> Unit,
229
+ sync: () -> Unit,
230
+ originalBytes: Long,
231
+ padBytes: Long
232
+ ): Result {
233
+ val target = originalBytes + padBytes.coerceAtLeast(0L)
234
+ val chunkBytes = chunkBytes()
235
+ val garbage = GarbageSource(chunkBytes)
236
+ var written = 0L
237
+ var failure: String? = null
238
+ var flushed = false
239
+
240
+ try {
241
+ while (written < target) {
242
+ val length = minOf(chunkBytes.toLong(), target - written).toInt()
243
+ write(garbage.next(length), length)
244
+ written += length
245
+ }
246
+ } catch (t: Throwable) {
247
+ failure = t.message ?: t.javaClass.simpleName
248
+ Log.e(TAG, "Secure overwrite stopped after $written/$target bytes", t)
249
+ }
250
+
251
+ // Flushing is graded separately from writing, and a failure here is fatal to the claim even
252
+ // when every byte was handed over: unflushed writes can still be sitting in the page cache,
253
+ // and a crash or battery pull would bring the original blocks back intact. Reporting success
254
+ // on an uncommitted overwrite is exactly the "silently report successful secure deletion"
255
+ // case this class exists to prevent. It runs even after a write failure, so whatever did land
256
+ // is at least durable.
257
+ try {
258
+ sync()
259
+ flushed = true
260
+ } catch (t: Throwable) {
261
+ Log.e(TAG, "Secure overwrite could not be flushed to storage after $written bytes", t)
262
+ if (failure == null) failure = "Overwrite not flushed to storage: ${t.message ?: t.javaClass.simpleName}"
263
+ }
264
+
265
+ garbage.destroy()
266
+
267
+ // "Secure" requires the whole original extent covered AND committed. Losing only the padding
268
+ // to a full volume still means the media itself is unrecoverable, so that grades as secure
269
+ // with `padded = false` rather than as an outright failure.
270
+ val coveredOriginal = written >= originalBytes && flushed
271
+ return Result(
272
+ securelyOverwritten = coveredOriginal,
273
+ originalBytes = originalBytes,
274
+ overwrittenBytes = written,
275
+ padded = written >= target && flushed,
276
+ error = failure
277
+ )
278
+ }
279
+
280
+ /**
281
+ * How far past the end of the file the pass may run, given what the volume can take.
282
+ *
283
+ * The in-place portion needs no free space at all — it reuses the file's own blocks — so a
284
+ * device too full for the padding must still get a complete content wipe. Trimming the pad
285
+ * here, rather than letting the write fail at the ENOSPC boundary, keeps the common
286
+ * nearly-full-device case on the clean path.
287
+ */
288
+ private fun effectivePad(parent: File?, requestedPad: Long, originalBytes: Long): Long {
289
+ if (requestedPad <= 0L) return 0L
290
+ val dir = parent ?: return requestedPad
291
+ val usable = MediaFileIo.usableSpaceBytes(dir)
292
+ if (usable == Long.MAX_VALUE) return requestedPad
293
+
294
+ val affordable = usable - SPACE_MARGIN_BYTES
295
+ if (affordable >= requestedPad) return requestedPad
296
+
297
+ Log.w(
298
+ TAG,
299
+ "Only ${MediaFileIo.formatMegabytes(affordable.coerceAtLeast(0L))} free; " +
300
+ "padding a ${MediaFileIo.formatMegabytes(originalBytes)} file by less than " +
301
+ MediaFileIo.formatMegabytes(requestedPad)
302
+ )
303
+ return affordable.coerceAtLeast(0L)
304
+ }
305
+
306
+ /**
307
+ * Rename [file] to an unpredictable name in its own directory so the original name does not
308
+ * outlive the content. Returns the renamed file, or [file] unchanged when the rename is
309
+ * refused — the delete that follows works either way.
310
+ */
311
+ private fun renameToRandom(file: File): File {
312
+ val parent = file.parentFile ?: return file
313
+ val bytes = ByteArray(8).also { SecureRandom().nextBytes(it) }
314
+ val scrubbed = File(parent, bytes.joinToString("") { "%02x".format(it) })
315
+ return if (runCatching { file.renameTo(scrubbed) }.getOrDefault(false)) scrubbed else file
316
+ }
317
+
318
+ /** Chunk size for this device — see [SMALL_HEAP_CHUNK_BYTES]. */
319
+ private fun chunkBytes(): Int =
320
+ if (Runtime.getRuntime().maxMemory() <= 128L * 1024L * 1024L) {
321
+ SMALL_HEAP_CHUNK_BYTES
322
+ } else {
323
+ CHUNK_BYTES
324
+ }
325
+
326
+ /**
327
+ * A bounded generator of encrypted cryptographically secure random bytes.
328
+ *
329
+ * Two fixed buffers, allocated once and reused for the whole pass: [plain] takes the
330
+ * [SecureRandom] output and [cipherText] takes the AES-CTR result. Neither is ever sized
331
+ * against the file, which is what keeps erasing a gigabyte-scale recording within a few hundred
332
+ * kilobytes of working memory.
333
+ *
334
+ * The cipher is a second, independent layer over the CSPRNG rather than a replacement for it.
335
+ * Random input alone is already unpredictable; passing it through a single-use key means the
336
+ * written stream also cannot be reproduced by anyone who later learns something about the
337
+ * device's random source, and the key is gone the moment [destroy] runs.
338
+ */
339
+ private class GarbageSource(chunkBytes: Int) {
340
+ private val random = SecureRandom()
341
+ private val plain = ByteArray(chunkBytes)
342
+ private val cipherText = ByteArray(chunkBytes)
343
+ private val cipher: Cipher? = createCipher()
344
+
345
+ /**
346
+ * Fill the internal buffer with [length] fresh bytes and return it.
347
+ *
348
+ * The array is REUSED between calls, so callers must consume `[0, length)` before the next
349
+ * call — every caller here writes it straight out.
350
+ *
351
+ * A cipher that cannot be initialised (a stripped-down or FIPS-restricted provider on some
352
+ * OEM builds) degrades to the raw CSPRNG output rather than aborting the erase. Plain
353
+ * SecureRandom bytes are already a sound overwrite; refusing to scrub the file at all
354
+ * because the optional second layer is unavailable would be the worse failure.
355
+ */
356
+ fun next(length: Int): ByteArray {
357
+ random.nextBytes(plain)
358
+ val active = cipher ?: return plain
359
+ return try {
360
+ val produced = active.update(plain, 0, length, cipherText, 0)
361
+ if (produced == length) cipherText else plain
362
+ } catch (t: Throwable) {
363
+ Log.w(TAG, "Cipher stage unavailable mid-pass; continuing with CSPRNG output", t)
364
+ plain
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Wipe both buffers.
370
+ *
371
+ * They hold the exact bytes now sitting on the media's former blocks, so clearing them
372
+ * shortens the window in which a heap dump reveals what was written. The key itself is
373
+ * never retained by this class — it is zeroed the instant the cipher is initialised (see
374
+ * [createCipher]), so there is nothing else here to destroy.
375
+ */
376
+ fun destroy() {
377
+ plain.fill(0)
378
+ cipherText.fill(0)
379
+ }
380
+
381
+ /**
382
+ * Build the single-use AES-CTR cipher, or null when the platform has no usable provider.
383
+ *
384
+ * The key is generated here, handed to the cipher, and zeroed immediately — it is
385
+ * deliberately NOT kept in a field. Retaining a copy so it could be wiped later would only
386
+ * keep live key material on the heap for the whole duration of a multi-second erase, and the
387
+ * cipher already holds its own copy that this class cannot reach anyway.
388
+ *
389
+ * AES-256 first; a provider that rejects the longer key falls back to 128-bit, which is just
390
+ * as unpredictable for this purpose.
391
+ */
392
+ private fun createCipher(): Cipher? {
393
+ for (keyLength in intArrayOf(KEY_BYTES, 16)) {
394
+ runCatching {
395
+ val key = ByteArray(keyLength).also { random.nextBytes(it) }
396
+ val iv = ByteArray(IV_BYTES).also { random.nextBytes(it) }
397
+ return Cipher.getInstance("AES/CTR/NoPadding").apply {
398
+ init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
399
+ }.also { key.fill(0) }
400
+ }
401
+ }
402
+ Log.w(TAG, "AES/CTR unavailable; overwriting with raw CSPRNG output")
403
+ return null
404
+ }
405
+
406
+ private companion object {
407
+ const val KEY_BYTES = 32
408
+ const val IV_BYTES = 16
409
+ }
410
+ }
411
+ }
@@ -0,0 +1,88 @@
1
+ package com.customcamera
2
+
3
+ import android.content.Context
4
+ import androidx.security.crypto.EncryptedSharedPreferences
5
+ import androidx.security.crypto.MasterKey
6
+ import com.facebook.react.bridge.Promise
7
+ import com.facebook.react.bridge.ReactApplicationContext
8
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
9
+ import com.facebook.react.bridge.ReactMethod
10
+ import java.security.SecureRandom
11
+
12
+ /**
13
+ * SecureKeyModule
14
+ *
15
+ * Provisions and persists the SQLCipher database encryption key using the
16
+ * Android Keystore. On first launch a cryptographically random 256-bit key is
17
+ * generated and stored inside [EncryptedSharedPreferences], whose contents are
18
+ * sealed by a [MasterKey] backed by the hardware-protected Android Keystore.
19
+ *
20
+ * The raw key material is therefore never shipped in the JS bundle and never
21
+ * written to disk in plaintext. JavaScript retrieves it once via
22
+ * [getOrCreateDatabaseKey] and passes it to SQLCipher as the passphrase, and
23
+ * the native side reuses the SAME key for its own encrypted stores through the
24
+ * plain-`Context` helper [Companion.getOrCreateDatabaseKey].
25
+ */
26
+ class SecureKeyModule(
27
+ private val reactContext: ReactApplicationContext
28
+ ) : ReactContextBaseJavaModule(reactContext) {
29
+
30
+ override fun getName(): String = NAME
31
+
32
+ /**
33
+ * Returns the persisted database key, generating and storing a new one on
34
+ * first use. Resolves with a hex-encoded 256-bit key string.
35
+ */
36
+ @ReactMethod
37
+ fun getOrCreateDatabaseKey(promise: Promise) {
38
+ try {
39
+ promise.resolve(getOrCreateDatabaseKey(reactContext))
40
+ } catch (e: Exception) {
41
+ promise.reject("SECURE_KEY_ERROR", "Failed to provision database key", e)
42
+ }
43
+ }
44
+
45
+ companion object {
46
+ const val NAME = "SecureKeyModule"
47
+ private const val PREFS_FILE = "rncc_secure_media_keys"
48
+ private const val KEY_PREF = "sqlcipher_db_key"
49
+
50
+ /**
51
+ * The persisted 64-hex-character SQLCipher passphrase, generating and storing a new one on
52
+ * first use. Usable from any component with a plain [Context] — the JS bridge above and the
53
+ * native compression cache ([CompressionCacheStore]) both open their encrypted databases with
54
+ * the exact same key, so there is a single Keystore-sealed secret rather than one per store.
55
+ *
56
+ * Synchronised so two callers racing to provision the key on first launch cannot each mint a
57
+ * different value and clobber one another.
58
+ */
59
+ @Synchronized
60
+ fun getOrCreateDatabaseKey(context: Context): String {
61
+ val prefs = openEncryptedPrefs(context)
62
+ val existing = prefs.getString(KEY_PREF, null)
63
+ if (existing != null && existing.isNotEmpty()) return existing
64
+
65
+ val generated = generateHexKey()
66
+ prefs.edit().putString(KEY_PREF, generated).apply()
67
+ return generated
68
+ }
69
+
70
+ private fun openEncryptedPrefs(context: Context) =
71
+ EncryptedSharedPreferences.create(
72
+ context.applicationContext,
73
+ PREFS_FILE,
74
+ MasterKey.Builder(context.applicationContext)
75
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
76
+ .build(),
77
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
78
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
79
+ )
80
+
81
+ /** Generates a random 256-bit key encoded as 64 lowercase hex characters. */
82
+ private fun generateHexKey(): String {
83
+ val bytes = ByteArray(32)
84
+ SecureRandom().nextBytes(bytes)
85
+ return bytes.joinToString("") { "%02x".format(it) }
86
+ }
87
+ }
88
+ }