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.
- package/CustomCamera.podspec +20 -0
- package/LICENSE +20 -0
- package/README.md +833 -0
- package/android/build.gradle +135 -0
- package/android/consumer-rules.pro +47 -0
- package/android/src/main/AndroidManifest.xml +72 -0
- package/android/src/main/java/android/print/PdfPrint.java +51 -0
- package/android/src/main/java/com/customcamera/AnimatedImageViewManager.kt +199 -0
- package/android/src/main/java/com/customcamera/AudioPlayerView.kt +322 -0
- package/android/src/main/java/com/customcamera/AudioRecorder.kt +506 -0
- package/android/src/main/java/com/customcamera/BarcodeScannerActivity.kt +361 -0
- package/android/src/main/java/com/customcamera/BlurBackgroundView.kt +199 -0
- package/android/src/main/java/com/customcamera/CacheCleanupManager.kt +386 -0
- package/android/src/main/java/com/customcamera/CameraAspectRatioMode.kt +77 -0
- package/android/src/main/java/com/customcamera/CameraAsset.kt +211 -0
- package/android/src/main/java/com/customcamera/CameraTheme.kt +479 -0
- package/android/src/main/java/com/customcamera/CompressionCacheStore.kt +504 -0
- package/android/src/main/java/com/customcamera/CompressionFileNameUtils.kt +7 -0
- package/android/src/main/java/com/customcamera/CompressionProfile.kt +195 -0
- package/android/src/main/java/com/customcamera/CustomCameraActivity.kt +7291 -0
- package/android/src/main/java/com/customcamera/CustomCameraFileProvider.kt +9 -0
- package/android/src/main/java/com/customcamera/CustomCameraModule.kt +2479 -0
- package/android/src/main/java/com/customcamera/CustomCameraPackage.kt +53 -0
- package/android/src/main/java/com/customcamera/DisplayUtils.kt +9 -0
- package/android/src/main/java/com/customcamera/DocumentPreviewView.kt +846 -0
- package/android/src/main/java/com/customcamera/DrawingView.kt +214 -0
- package/android/src/main/java/com/customcamera/EmojiPickerData.kt +228 -0
- package/android/src/main/java/com/customcamera/FilterStripAdapter.kt +158 -0
- package/android/src/main/java/com/customcamera/GifSupport.kt +215 -0
- package/android/src/main/java/com/customcamera/GlobalAudioPlayer.kt +320 -0
- package/android/src/main/java/com/customcamera/ImageExporter.kt +1394 -0
- package/android/src/main/java/com/customcamera/ImageFormatSupport.kt +163 -0
- package/android/src/main/java/com/customcamera/Localization.kt +206 -0
- package/android/src/main/java/com/customcamera/MediaAdapters.kt +758 -0
- package/android/src/main/java/com/customcamera/MediaCacheManager.kt +782 -0
- package/android/src/main/java/com/customcamera/MediaExportFormats.kt +201 -0
- package/android/src/main/java/com/customcamera/MediaFileIo.kt +310 -0
- package/android/src/main/java/com/customcamera/MediaMetadataStore.kt +494 -0
- package/android/src/main/java/com/customcamera/MediaPreviewEditor.kt +14865 -0
- package/android/src/main/java/com/customcamera/MediaPublisher.kt +423 -0
- package/android/src/main/java/com/customcamera/MediaStorageWorkflow.kt +722 -0
- package/android/src/main/java/com/customcamera/MediaStoreRepository.kt +378 -0
- package/android/src/main/java/com/customcamera/MediaThumbnailView.kt +286 -0
- package/android/src/main/java/com/customcamera/MediaUtils.kt +37 -0
- package/android/src/main/java/com/customcamera/NativeVideoViewManager.kt +845 -0
- package/android/src/main/java/com/customcamera/OfficeDocumentConverter.kt +2791 -0
- package/android/src/main/java/com/customcamera/PendingCaptureCrops.kt +88 -0
- package/android/src/main/java/com/customcamera/PowerPointPreview.kt +4077 -0
- package/android/src/main/java/com/customcamera/RecentPickerStore.kt +74 -0
- package/android/src/main/java/com/customcamera/SecureFileEraser.kt +411 -0
- package/android/src/main/java/com/customcamera/SecureKeyModule.kt +88 -0
- package/android/src/main/java/com/customcamera/SecureMediaDeleter.kt +493 -0
- package/android/src/main/java/com/customcamera/StickerPickerData.kt +107 -0
- package/android/src/main/java/com/customcamera/SvgDrawable.kt +175 -0
- package/android/src/main/java/com/customcamera/SvgIconViewManager.kt +77 -0
- package/android/src/main/java/com/customcamera/TextEntryController.kt +553 -0
- package/android/src/main/java/com/customcamera/ThumbnailLoader.kt +455 -0
- package/android/src/main/java/com/customcamera/UploadService.kt +909 -0
- package/android/src/main/java/com/customcamera/VideoEditUtils.kt +210 -0
- package/android/src/main/java/com/customcamera/VideoRotationTranscoder.kt +1992 -0
- package/android/src/main/java/com/customcamera/gif/AnimatedGifEncoder.kt +473 -0
- package/android/src/main/java/com/customcamera/gif/GifDecoder.kt +690 -0
- package/android/src/main/java/com/customcamera/gif/GifFramesDrawable.kt +160 -0
- package/android/src/main/java/com/customcamera/gif/GifHttp.kt +112 -0
- package/android/src/main/java/com/customcamera/gif/GifImageLoader.kt +186 -0
- package/android/src/main/java/com/customcamera/gif/GifModels.kt +74 -0
- package/android/src/main/java/com/customcamera/gif/GifProvider.kt +49 -0
- package/android/src/main/java/com/customcamera/gif/GifProviders.kt +27 -0
- package/android/src/main/java/com/customcamera/gif/GifRepository.kt +124 -0
- package/android/src/main/java/com/customcamera/gif/GifSafety.kt +33 -0
- package/android/src/main/java/com/customcamera/gif/GiphyGifProvider.kt +146 -0
- package/android/src/main/java/com/customcamera/gif/LZWEncoder.kt +258 -0
- package/android/src/main/java/com/customcamera/gif/NeuQuant.kt +435 -0
- package/android/src/main/java/com/customcamera/gif/TenorGifProvider.kt +147 -0
- package/android/src/main/res/drawable/bg_brush_selected.xml +5 -0
- package/android/src/main/res/drawable/bg_color_swatch.xml +5 -0
- package/android/src/main/res/drawable/bg_editor_toolbar.xml +6 -0
- package/android/src/main/res/drawable/bg_icon_circle.xml +5 -0
- package/android/src/main/res/drawable/bg_icon_circle_light.xml +5 -0
- package/android/src/main/res/drawable/bg_mode_tab_active.xml +6 -0
- package/android/src/main/res/drawable/bg_pill.xml +6 -0
- package/android/src/main/res/drawable/bg_recording_dot.xml +6 -0
- package/android/src/main/res/drawable/bg_selection_badge.xml +8 -0
- package/android/src/main/res/drawable/bg_send_button.xml +5 -0
- package/android/src/main/res/drawable/bg_shutter_inner.xml +5 -0
- package/android/src/main/res/drawable/bg_shutter_recording.xml +6 -0
- package/android/src/main/res/drawable/bg_shutter_ring.xml +8 -0
- package/android/src/main/res/drawable/bg_shutter_video_idle.xml +5 -0
- package/android/src/main/res/drawable/bg_thumbnail.xml +9 -0
- package/android/src/main/res/drawable/ic_aspect_ratio.xml +17 -0
- package/android/src/main/res/drawable/ic_audio.xml +10 -0
- package/android/src/main/res/drawable/ic_back.xml +10 -0
- package/android/src/main/res/drawable/ic_brush_medium.xml +12 -0
- package/android/src/main/res/drawable/ic_brush_thick.xml +12 -0
- package/android/src/main/res/drawable/ic_brush_thin.xml +12 -0
- package/android/src/main/res/drawable/ic_check.xml +17 -0
- package/android/src/main/res/drawable/ic_chevron_down.xml +10 -0
- package/android/src/main/res/drawable/ic_chevron_up.xml +13 -0
- package/android/src/main/res/drawable/ic_clear_drawing.xml +10 -0
- package/android/src/main/res/drawable/ic_close.xml +10 -0
- package/android/src/main/res/drawable/ic_cover.xml +9 -0
- package/android/src/main/res/drawable/ic_crop.xml +10 -0
- package/android/src/main/res/drawable/ic_delete.xml +9 -0
- package/android/src/main/res/drawable/ic_document.xml +10 -0
- package/android/src/main/res/drawable/ic_done.xml +10 -0
- package/android/src/main/res/drawable/ic_download.xml +10 -0
- package/android/src/main/res/drawable/ic_emoji.xml +10 -0
- package/android/src/main/res/drawable/ic_filter.xml +10 -0
- package/android/src/main/res/drawable/ic_flash_auto.xml +7 -0
- package/android/src/main/res/drawable/ic_flash_off.xml +23 -0
- package/android/src/main/res/drawable/ic_flash_on.xml +16 -0
- package/android/src/main/res/drawable/ic_flip.xml +15 -0
- package/android/src/main/res/drawable/ic_gallery.xml +10 -0
- package/android/src/main/res/drawable/ic_gif.xml +24 -0
- package/android/src/main/res/drawable/ic_lock.xml +19 -0
- package/android/src/main/res/drawable/ic_lock_open.xml +22 -0
- package/android/src/main/res/drawable/ic_marker.xml +10 -0
- package/android/src/main/res/drawable/ic_more_vert.xml +18 -0
- package/android/src/main/res/drawable/ic_overlay_close.xml +19 -0
- package/android/src/main/res/drawable/ic_overlay_resize.xml +18 -0
- package/android/src/main/res/drawable/ic_pause.xml +10 -0
- package/android/src/main/res/drawable/ic_photo_mode.xml +10 -0
- package/android/src/main/res/drawable/ic_play.xml +10 -0
- package/android/src/main/res/drawable/ic_redo.xml +10 -0
- package/android/src/main/res/drawable/ic_rotate.xml +9 -0
- package/android/src/main/res/drawable/ic_send.xml +10 -0
- package/android/src/main/res/drawable/ic_share.xml +10 -0
- package/android/src/main/res/drawable/ic_stat_upload.xml +10 -0
- package/android/src/main/res/drawable/ic_sticker.xml +24 -0
- package/android/src/main/res/drawable/ic_text.xml +10 -0
- package/android/src/main/res/drawable/ic_text_align_center.xml +11 -0
- package/android/src/main/res/drawable/ic_text_align_left.xml +11 -0
- package/android/src/main/res/drawable/ic_text_align_right.xml +11 -0
- package/android/src/main/res/drawable/ic_text_bg.xml +11 -0
- package/android/src/main/res/drawable/ic_trim.xml +23 -0
- package/android/src/main/res/drawable/ic_undo.xml +10 -0
- package/android/src/main/res/drawable/ic_video_mode.xml +10 -0
- package/android/src/main/res/drawable/ic_video_note.xml +13 -0
- package/android/src/main/res/drawable/ic_volume_off.xml +9 -0
- package/android/src/main/res/drawable/ic_volume_on.xml +9 -0
- package/android/src/main/res/drawable/sticker_heart.xml +9 -0
- package/android/src/main/res/drawable/sticker_smile.xml +9 -0
- package/android/src/main/res/drawable/sticker_star.xml +9 -0
- package/android/src/main/res/drawable/sticker_thumbsup.xml +9 -0
- package/android/src/main/res/values/colors.xml +34 -0
- package/android/src/main/res/values/ids.xml +15 -0
- package/android/src/main/res/values/strings.xml +15 -0
- package/android/src/main/res/values/styles.xml +20 -0
- package/android/src/main/res/xml/file_paths.xml +34 -0
- package/ios/CustomCamera.h +5 -0
- package/ios/CustomCamera.mm +214 -0
- package/lib/module/AudioPlayerContext.js +233 -0
- package/lib/module/AudioPlayerContext.js.map +1 -0
- package/lib/module/CameraPreview.js +304 -0
- package/lib/module/CameraPreview.js.map +1 -0
- package/lib/module/NativeAnimatedImageView.js +41 -0
- package/lib/module/NativeAnimatedImageView.js.map +1 -0
- package/lib/module/NativeAudioPlayerView.js +33 -0
- package/lib/module/NativeAudioPlayerView.js.map +1 -0
- package/lib/module/NativeBlurView.js +35 -0
- package/lib/module/NativeBlurView.js.map +1 -0
- package/lib/module/NativeCustomCamera.js +128 -0
- package/lib/module/NativeCustomCamera.js.map +1 -0
- package/lib/module/NativeCustomCamera.web.js +143 -0
- package/lib/module/NativeCustomCamera.web.js.map +1 -0
- package/lib/module/NativeCustomVideoView.js +5 -0
- package/lib/module/NativeCustomVideoView.js.map +1 -0
- package/lib/module/NativeDocumentPreviewView.js +156 -0
- package/lib/module/NativeDocumentPreviewView.js.map +1 -0
- package/lib/module/NativeMediaThumbnailView.js +110 -0
- package/lib/module/NativeMediaThumbnailView.js.map +1 -0
- package/lib/module/NativeSvgIcon.js +32 -0
- package/lib/module/NativeSvgIcon.js.map +1 -0
- package/lib/module/ResponsivePreviewContainer.js +171 -0
- package/lib/module/ResponsivePreviewContainer.js.map +1 -0
- package/lib/module/accessibility.js +61 -0
- package/lib/module/accessibility.js.map +1 -0
- package/lib/module/config.js +352 -0
- package/lib/module/config.js.map +1 -0
- package/lib/module/cpuOptimization.js +296 -0
- package/lib/module/cpuOptimization.js.map +1 -0
- package/lib/module/db/DatabaseManager.js +191 -0
- package/lib/module/db/DatabaseManager.js.map +1 -0
- package/lib/module/db/MediaMapper.js +284 -0
- package/lib/module/db/MediaMapper.js.map +1 -0
- package/lib/module/db/MediaRepository.js +242 -0
- package/lib/module/db/MediaRepository.js.map +1 -0
- package/lib/module/db/index.js +41 -0
- package/lib/module/db/index.js.map +1 -0
- package/lib/module/db/migrations.js +180 -0
- package/lib/module/db/migrations.js.map +1 -0
- package/lib/module/db/react-native-sqlcipher.d.js +2 -0
- package/lib/module/db/react-native-sqlcipher.d.js.map +1 -0
- package/lib/module/db/secureKey.js +87 -0
- package/lib/module/db/secureKey.js.map +1 -0
- package/lib/module/db/types.js +57 -0
- package/lib/module/db/types.js.map +1 -0
- package/lib/module/db/useMediaDatabase.js +123 -0
- package/lib/module/db/useMediaDatabase.js.map +1 -0
- package/lib/module/defaultIcons.js +2259 -0
- package/lib/module/defaultIcons.js.map +1 -0
- package/lib/module/deviceCapabilities.js +408 -0
- package/lib/module/deviceCapabilities.js.map +1 -0
- package/lib/module/errors.js +267 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/iconConfig.js +796 -0
- package/lib/module/iconConfig.js.map +1 -0
- package/lib/module/icons.js +514 -0
- package/lib/module/icons.js.map +1 -0
- package/lib/module/index.js +251 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/jest-globals.d.js +2 -0
- package/lib/module/jest-globals.d.js.map +1 -0
- package/lib/module/library/MediaLibrary.js +107 -0
- package/lib/module/library/MediaLibrary.js.map +1 -0
- package/lib/module/library/MediaLibraryScreen.js +2597 -0
- package/lib/module/library/MediaLibraryScreen.js.map +1 -0
- package/lib/module/library/components/AudioPreview.js +74 -0
- package/lib/module/library/components/AudioPreview.js.map +1 -0
- package/lib/module/library/components/DocumentPreview.js +331 -0
- package/lib/module/library/components/DocumentPreview.js.map +1 -0
- package/lib/module/library/components/ExpandableFab.js +343 -0
- package/lib/module/library/components/ExpandableFab.js.map +1 -0
- package/lib/module/library/components/FileTiles.js +251 -0
- package/lib/module/library/components/FileTiles.js.map +1 -0
- package/lib/module/library/components/ImagePreview.js +70 -0
- package/lib/module/library/components/ImagePreview.js.map +1 -0
- package/lib/module/library/components/MediaMetadataHeader.js +175 -0
- package/lib/module/library/components/MediaMetadataHeader.js.map +1 -0
- package/lib/module/library/components/RecordAudioScreen.js +816 -0
- package/lib/module/library/components/RecordAudioScreen.js.map +1 -0
- package/lib/module/library/components/VideoPreview.js +453 -0
- package/lib/module/library/components/VideoPreview.js.map +1 -0
- package/lib/module/library/components/ViewerCounter.js +55 -0
- package/lib/module/library/components/ViewerCounter.js.map +1 -0
- package/lib/module/library/compressionEstimate.js +507 -0
- package/lib/module/library/compressionEstimate.js.map +1 -0
- package/lib/module/library/fileMetadata.js +196 -0
- package/lib/module/library/fileMetadata.js.map +1 -0
- package/lib/module/library/index.js +42 -0
- package/lib/module/library/index.js.map +1 -0
- package/lib/module/library/libraryIconRegistry.js +69 -0
- package/lib/module/library/libraryIconRegistry.js.map +1 -0
- package/lib/module/library/libraryIcons.js +858 -0
- package/lib/module/library/libraryIcons.js.map +1 -0
- package/lib/module/library/localizedFormat.js +181 -0
- package/lib/module/library/localizedFormat.js.map +1 -0
- package/lib/module/library/palette.js +225 -0
- package/lib/module/library/palette.js.map +1 -0
- package/lib/module/library/responsive.js +53 -0
- package/lib/module/library/responsive.js.map +1 -0
- package/lib/module/locales.js +416 -0
- package/lib/module/locales.js.map +1 -0
- package/lib/module/localization.js +1064 -0
- package/lib/module/localization.js.map +1 -0
- package/lib/module/logger.js +20 -0
- package/lib/module/logger.js.map +1 -0
- package/lib/module/lowMemoryCache.js +206 -0
- package/lib/module/lowMemoryCache.js.map +1 -0
- package/lib/module/mediaHistory.js +33 -0
- package/lib/module/mediaHistory.js.map +1 -0
- package/lib/module/mediaStorageService.js +402 -0
- package/lib/module/mediaStorageService.js.map +1 -0
- package/lib/module/memory.js +79 -0
- package/lib/module/memory.js.map +1 -0
- package/lib/module/nativeActions.js +307 -0
- package/lib/module/nativeActions.js.map +1 -0
- package/lib/module/nativeComponentSupport.js +61 -0
- package/lib/module/nativeComponentSupport.js.map +1 -0
- package/lib/module/networkPerformance.js +24 -0
- package/lib/module/networkPerformance.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/performance.js +104 -0
- package/lib/module/performance.js.map +1 -0
- package/lib/module/performanceMonitor.js +155 -0
- package/lib/module/performanceMonitor.js.map +1 -0
- package/lib/module/permissions.js +370 -0
- package/lib/module/permissions.js.map +1 -0
- package/lib/module/rendering.js +40 -0
- package/lib/module/rendering.js.map +1 -0
- package/lib/module/security.js +18 -0
- package/lib/module/security.js.map +1 -0
- package/lib/module/state.js +266 -0
- package/lib/module/state.js.map +1 -0
- package/lib/module/storage.js +400 -0
- package/lib/module/storage.js.map +1 -0
- package/lib/module/theme.js +390 -0
- package/lib/module/theme.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils.js +115 -0
- package/lib/module/utils.js.map +1 -0
- package/lib/module/validation.js +288 -0
- package/lib/module/validation.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/AudioPlayerContext.d.ts +27 -0
- package/lib/typescript/src/AudioPlayerContext.d.ts.map +1 -0
- package/lib/typescript/src/CameraPreview.d.ts +13 -0
- package/lib/typescript/src/CameraPreview.d.ts.map +1 -0
- package/lib/typescript/src/NativeAnimatedImageView.d.ts +33 -0
- package/lib/typescript/src/NativeAnimatedImageView.d.ts.map +1 -0
- package/lib/typescript/src/NativeAudioPlayerView.d.ts +12 -0
- package/lib/typescript/src/NativeAudioPlayerView.d.ts.map +1 -0
- package/lib/typescript/src/NativeBlurView.d.ts +12 -0
- package/lib/typescript/src/NativeBlurView.d.ts.map +1 -0
- package/lib/typescript/src/NativeCustomCamera.d.ts +167 -0
- package/lib/typescript/src/NativeCustomCamera.d.ts.map +1 -0
- package/lib/typescript/src/NativeCustomCamera.web.d.ts +47 -0
- package/lib/typescript/src/NativeCustomCamera.web.d.ts.map +1 -0
- package/lib/typescript/src/NativeCustomVideoView.d.ts +30 -0
- package/lib/typescript/src/NativeCustomVideoView.d.ts.map +1 -0
- package/lib/typescript/src/NativeDocumentPreviewView.d.ts +48 -0
- package/lib/typescript/src/NativeDocumentPreviewView.d.ts.map +1 -0
- package/lib/typescript/src/NativeMediaThumbnailView.d.ts +54 -0
- package/lib/typescript/src/NativeMediaThumbnailView.d.ts.map +1 -0
- package/lib/typescript/src/NativeSvgIcon.d.ts +25 -0
- package/lib/typescript/src/NativeSvgIcon.d.ts.map +1 -0
- package/lib/typescript/src/ResponsivePreviewContainer.d.ts +92 -0
- package/lib/typescript/src/ResponsivePreviewContainer.d.ts.map +1 -0
- package/lib/typescript/src/accessibility.d.ts +20 -0
- package/lib/typescript/src/accessibility.d.ts.map +1 -0
- package/lib/typescript/src/config.d.ts +145 -0
- package/lib/typescript/src/config.d.ts.map +1 -0
- package/lib/typescript/src/cpuOptimization.d.ts +63 -0
- package/lib/typescript/src/cpuOptimization.d.ts.map +1 -0
- package/lib/typescript/src/db/DatabaseManager.d.ts +48 -0
- package/lib/typescript/src/db/DatabaseManager.d.ts.map +1 -0
- package/lib/typescript/src/db/MediaMapper.d.ts +77 -0
- package/lib/typescript/src/db/MediaMapper.d.ts.map +1 -0
- package/lib/typescript/src/db/MediaRepository.d.ts +66 -0
- package/lib/typescript/src/db/MediaRepository.d.ts.map +1 -0
- package/lib/typescript/src/db/index.d.ts +23 -0
- package/lib/typescript/src/db/index.d.ts.map +1 -0
- package/lib/typescript/src/db/migrations.d.ts +31 -0
- package/lib/typescript/src/db/migrations.d.ts.map +1 -0
- package/lib/typescript/src/db/secureKey.d.ts +10 -0
- package/lib/typescript/src/db/secureKey.d.ts.map +1 -0
- package/lib/typescript/src/db/types.d.ts +115 -0
- package/lib/typescript/src/db/types.d.ts.map +1 -0
- package/lib/typescript/src/db/useMediaDatabase.d.ts +41 -0
- package/lib/typescript/src/db/useMediaDatabase.d.ts.map +1 -0
- package/lib/typescript/src/defaultIcons.d.ts +139 -0
- package/lib/typescript/src/defaultIcons.d.ts.map +1 -0
- package/lib/typescript/src/deviceCapabilities.d.ts +92 -0
- package/lib/typescript/src/deviceCapabilities.d.ts.map +1 -0
- package/lib/typescript/src/errors.d.ts +95 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/iconConfig.d.ts +176 -0
- package/lib/typescript/src/iconConfig.d.ts.map +1 -0
- package/lib/typescript/src/icons.d.ts +217 -0
- package/lib/typescript/src/icons.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +104 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/library/MediaLibrary.d.ts +57 -0
- package/lib/typescript/src/library/MediaLibrary.d.ts.map +1 -0
- package/lib/typescript/src/library/MediaLibraryScreen.d.ts +51 -0
- package/lib/typescript/src/library/MediaLibraryScreen.d.ts.map +1 -0
- package/lib/typescript/src/library/components/AudioPreview.d.ts +6 -0
- package/lib/typescript/src/library/components/AudioPreview.d.ts.map +1 -0
- package/lib/typescript/src/library/components/DocumentPreview.d.ts +10 -0
- package/lib/typescript/src/library/components/DocumentPreview.d.ts.map +1 -0
- package/lib/typescript/src/library/components/ExpandableFab.d.ts +39 -0
- package/lib/typescript/src/library/components/ExpandableFab.d.ts.map +1 -0
- package/lib/typescript/src/library/components/FileTiles.d.ts +8 -0
- package/lib/typescript/src/library/components/FileTiles.d.ts.map +1 -0
- package/lib/typescript/src/library/components/ImagePreview.d.ts +5 -0
- package/lib/typescript/src/library/components/ImagePreview.d.ts.map +1 -0
- package/lib/typescript/src/library/components/MediaMetadataHeader.d.ts +16 -0
- package/lib/typescript/src/library/components/MediaMetadataHeader.d.ts.map +1 -0
- package/lib/typescript/src/library/components/RecordAudioScreen.d.ts +8 -0
- package/lib/typescript/src/library/components/RecordAudioScreen.d.ts.map +1 -0
- package/lib/typescript/src/library/components/VideoPreview.d.ts +20 -0
- package/lib/typescript/src/library/components/VideoPreview.d.ts.map +1 -0
- package/lib/typescript/src/library/components/ViewerCounter.d.ts +13 -0
- package/lib/typescript/src/library/components/ViewerCounter.d.ts.map +1 -0
- package/lib/typescript/src/library/compressionEstimate.d.ts +313 -0
- package/lib/typescript/src/library/compressionEstimate.d.ts.map +1 -0
- package/lib/typescript/src/library/fileMetadata.d.ts +33 -0
- package/lib/typescript/src/library/fileMetadata.d.ts.map +1 -0
- package/lib/typescript/src/library/index.d.ts +40 -0
- package/lib/typescript/src/library/index.d.ts.map +1 -0
- package/lib/typescript/src/library/libraryIconRegistry.d.ts +35 -0
- package/lib/typescript/src/library/libraryIconRegistry.d.ts.map +1 -0
- package/lib/typescript/src/library/libraryIcons.d.ts +85 -0
- package/lib/typescript/src/library/libraryIcons.d.ts.map +1 -0
- package/lib/typescript/src/library/localizedFormat.d.ts +49 -0
- package/lib/typescript/src/library/localizedFormat.d.ts.map +1 -0
- package/lib/typescript/src/library/palette.d.ts +104 -0
- package/lib/typescript/src/library/palette.d.ts.map +1 -0
- package/lib/typescript/src/library/responsive.d.ts +15 -0
- package/lib/typescript/src/library/responsive.d.ts.map +1 -0
- package/lib/typescript/src/locales.d.ts +103 -0
- package/lib/typescript/src/locales.d.ts.map +1 -0
- package/lib/typescript/src/localization.d.ts +1051 -0
- package/lib/typescript/src/localization.d.ts.map +1 -0
- package/lib/typescript/src/logger.d.ts +4 -0
- package/lib/typescript/src/logger.d.ts.map +1 -0
- package/lib/typescript/src/lowMemoryCache.d.ts +64 -0
- package/lib/typescript/src/lowMemoryCache.d.ts.map +1 -0
- package/lib/typescript/src/mediaHistory.d.ts +3 -0
- package/lib/typescript/src/mediaHistory.d.ts.map +1 -0
- package/lib/typescript/src/mediaStorageService.d.ts +155 -0
- package/lib/typescript/src/mediaStorageService.d.ts.map +1 -0
- package/lib/typescript/src/memory.d.ts +21 -0
- package/lib/typescript/src/memory.d.ts.map +1 -0
- package/lib/typescript/src/nativeActions.d.ts +174 -0
- package/lib/typescript/src/nativeActions.d.ts.map +1 -0
- package/lib/typescript/src/nativeComponentSupport.d.ts +30 -0
- package/lib/typescript/src/nativeComponentSupport.d.ts.map +1 -0
- package/lib/typescript/src/networkPerformance.d.ts +12 -0
- package/lib/typescript/src/networkPerformance.d.ts.map +1 -0
- package/lib/typescript/src/performance.d.ts +32 -0
- package/lib/typescript/src/performance.d.ts.map +1 -0
- package/lib/typescript/src/performanceMonitor.d.ts +45 -0
- package/lib/typescript/src/performanceMonitor.d.ts.map +1 -0
- package/lib/typescript/src/permissions.d.ts +132 -0
- package/lib/typescript/src/permissions.d.ts.map +1 -0
- package/lib/typescript/src/rendering.d.ts +9 -0
- package/lib/typescript/src/rendering.d.ts.map +1 -0
- package/lib/typescript/src/security.d.ts +6 -0
- package/lib/typescript/src/security.d.ts.map +1 -0
- package/lib/typescript/src/state.d.ts +102 -0
- package/lib/typescript/src/state.d.ts.map +1 -0
- package/lib/typescript/src/storage.d.ts +117 -0
- package/lib/typescript/src/storage.d.ts.map +1 -0
- package/lib/typescript/src/theme.d.ts +15 -0
- package/lib/typescript/src/theme.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +802 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/utils.d.ts +50 -0
- package/lib/typescript/src/utils.d.ts.map +1 -0
- package/lib/typescript/src/validation.d.ts +52 -0
- package/lib/typescript/src/validation.d.ts.map +1 -0
- package/package.json +204 -0
- package/react-native.config.js +17 -0
- package/src/AudioPlayerContext.tsx +220 -0
- package/src/CameraPreview.tsx +352 -0
- package/src/NativeAnimatedImageView.tsx +71 -0
- package/src/NativeAudioPlayerView.tsx +39 -0
- package/src/NativeBlurView.tsx +40 -0
- package/src/NativeCustomCamera.ts +305 -0
- package/src/NativeCustomCamera.web.ts +192 -0
- package/src/NativeCustomVideoView.ts +30 -0
- package/src/NativeDocumentPreviewView.tsx +196 -0
- package/src/NativeMediaThumbnailView.tsx +167 -0
- package/src/NativeSvgIcon.tsx +46 -0
- package/src/ResponsivePreviewContainer.tsx +227 -0
- package/src/accessibility.ts +80 -0
- package/src/config.ts +406 -0
- package/src/cpuOptimization.ts +365 -0
- package/src/db/DatabaseManager.ts +212 -0
- package/src/db/MediaMapper.ts +377 -0
- package/src/db/MediaRepository.ts +313 -0
- package/src/db/index.ts +73 -0
- package/src/db/migrations.ts +222 -0
- package/src/db/react-native-sqlcipher.d.ts +76 -0
- package/src/db/secureKey.ts +100 -0
- package/src/db/types.ts +123 -0
- package/src/db/useMediaDatabase.ts +191 -0
- package/src/defaultIcons.tsx +2390 -0
- package/src/deviceCapabilities.ts +438 -0
- package/src/errors.ts +350 -0
- package/src/iconConfig.tsx +962 -0
- package/src/icons.tsx +788 -0
- package/src/index.tsx +763 -0
- package/src/jest-globals.d.ts +9 -0
- package/src/library/MediaLibrary.tsx +141 -0
- package/src/library/MediaLibraryScreen.tsx +3003 -0
- package/src/library/components/AudioPreview.tsx +73 -0
- package/src/library/components/DocumentPreview.tsx +361 -0
- package/src/library/components/ExpandableFab.tsx +342 -0
- package/src/library/components/FileTiles.tsx +252 -0
- package/src/library/components/ImagePreview.tsx +72 -0
- package/src/library/components/MediaMetadataHeader.tsx +208 -0
- package/src/library/components/RecordAudioScreen.tsx +845 -0
- package/src/library/components/VideoPreview.tsx +478 -0
- package/src/library/components/ViewerCounter.tsx +71 -0
- package/src/library/compressionEstimate.ts +662 -0
- package/src/library/fileMetadata.ts +212 -0
- package/src/library/index.ts +66 -0
- package/src/library/libraryIconRegistry.tsx +86 -0
- package/src/library/libraryIcons.tsx +928 -0
- package/src/library/localizedFormat.ts +207 -0
- package/src/library/palette.ts +281 -0
- package/src/library/responsive.ts +49 -0
- package/src/locales.ts +381 -0
- package/src/localization.ts +1947 -0
- package/src/logger.ts +20 -0
- package/src/lowMemoryCache.ts +218 -0
- package/src/mediaHistory.ts +42 -0
- package/src/mediaStorageService.ts +546 -0
- package/src/memory.ts +102 -0
- package/src/nativeActions.ts +422 -0
- package/src/nativeComponentSupport.ts +60 -0
- package/src/networkPerformance.ts +30 -0
- package/src/performance.ts +137 -0
- package/src/performanceMonitor.ts +219 -0
- package/src/permissions.ts +403 -0
- package/src/rendering.ts +47 -0
- package/src/security.ts +19 -0
- package/src/state.ts +333 -0
- package/src/storage.ts +453 -0
- package/src/theme.tsx +467 -0
- package/src/types.ts +946 -0
- package/src/utils.ts +177 -0
- 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
|
+
}
|