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,493 @@
|
|
|
1
|
+
package com.customcamera
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.net.Uri
|
|
5
|
+
import android.provider.MediaStore
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import kotlinx.coroutines.Dispatchers
|
|
8
|
+
import kotlinx.coroutines.withContext
|
|
9
|
+
import java.io.File
|
|
10
|
+
import java.util.Locale
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The single native entry point for destroying one media item and every trace of it.
|
|
14
|
+
*
|
|
15
|
+
* Deleting a photo or video in this app is not one delete. A single captured clip can exist as the
|
|
16
|
+
* published file under `media_documents/`, the cache file it was published from, a compressed
|
|
17
|
+
* variant under `compressed/`, a format-converted copy under `exported/`, an edited render and a
|
|
18
|
+
* chosen cover frame under `edited/`, a downloaded copy under `downloads/`, a decoded bitmap in
|
|
19
|
+
* [ThumbnailLoader]'s LruCache, an entry in [MediaCacheManager]'s metadata, another in
|
|
20
|
+
* [MediaMetadataStore], and a row in SQLCipher. Unlinking only the file the UI happened to be
|
|
21
|
+
* showing leaves the media fully recoverable from any of the others โ and leaves the library
|
|
22
|
+
* counting an item whose file is gone.
|
|
23
|
+
*
|
|
24
|
+
* So the order here is deliberate and each step depends on the one before it:
|
|
25
|
+
*
|
|
26
|
+
* 1. Resolve every on-disk copy of this asset โ the primary file plus each derived variant.
|
|
27
|
+
* 2. [SecureFileEraser] overwrites each one with encrypted CSPRNG garbage `2 MiB` longer than
|
|
28
|
+
* the file, flushes it to storage, then unlinks it. The primary file's outcome is what the
|
|
29
|
+
* caller is told about; a variant that resists deletion is logged and reported separately
|
|
30
|
+
* rather than being allowed to fail the whole operation silently.
|
|
31
|
+
* 3. Only then are the in-memory and on-disk REFERENCES dropped (thumbnail bitmaps, cache
|
|
32
|
+
* metadata, storage metadata). Dropping them first would lose the very paths step 1 needs,
|
|
33
|
+
* which is how an orphaned copy survives a delete.
|
|
34
|
+
*
|
|
35
|
+
* The SQLCipher row is deliberately NOT touched here โ it is owned by the JS repository layer
|
|
36
|
+
* ([MediaRepository]), which removes it once this reports the file is gone. Native and JS writing
|
|
37
|
+
* to the same encrypted database from two directions is how rows and files drift apart.
|
|
38
|
+
*
|
|
39
|
+
* Every method is `suspend` on [Dispatchers.IO]. A 2 GB clip takes real seconds to overwrite, and
|
|
40
|
+
* none of it may run on the main thread.
|
|
41
|
+
*/
|
|
42
|
+
internal object SecureMediaDeleter {
|
|
43
|
+
private const val TAG = "SecureMediaDeleter"
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Cache subdirectories that can hold a copy โ or a derivative โ of a media file.
|
|
47
|
+
*
|
|
48
|
+
* Every one of these is swept for files belonging to the asset being deleted, because each is a
|
|
49
|
+
* place the editor, the compressor, the exporter or the viewer is known to write a second copy
|
|
50
|
+
* of the same pixels. Kept as a list of names (rather than resolved paths) so it stays readable
|
|
51
|
+
* next to `file_paths.xml`, which declares the same set.
|
|
52
|
+
*/
|
|
53
|
+
private val DERIVED_CACHE_DIRS = listOf(
|
|
54
|
+
"camera_media_cache", // MediaCacheManager's own store
|
|
55
|
+
"media_cache", // CacheCleanupManager's sweep target
|
|
56
|
+
"media",
|
|
57
|
+
"compressed", // Compress Video / Compress Image variants
|
|
58
|
+
"compression_cache", // persistent CompressionCacheStore master outputs
|
|
59
|
+
"exported", // "Export By" format conversions
|
|
60
|
+
"edited", // baked edits + chosen video cover frames
|
|
61
|
+
"downloads", // viewer "download a copy"
|
|
62
|
+
"shared" // copies staged for the share sheet
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
/** What one secure deletion actually achieved. */
|
|
66
|
+
data class Report(
|
|
67
|
+
/** True when the primary file is gone from the filesystem. */
|
|
68
|
+
val deleted: Boolean,
|
|
69
|
+
/**
|
|
70
|
+
* True ONLY when the primary file's bytes were overwritten and flushed before it was
|
|
71
|
+
* unlinked. When this is false the caller must NOT tell the user the media was securely
|
|
72
|
+
* erased, even if [deleted] is true โ see [SecureFileEraser.Result.securelyOverwritten].
|
|
73
|
+
*/
|
|
74
|
+
val securelyOverwritten: Boolean,
|
|
75
|
+
/** Exact size of the primary file before the overwrite. */
|
|
76
|
+
val originalBytes: Long,
|
|
77
|
+
/** Bytes written over the primary file, including the padding past its end. */
|
|
78
|
+
val overwrittenBytes: Long,
|
|
79
|
+
/** Extra copies (variants, thumbnails on disk, temp files) securely erased alongside it. */
|
|
80
|
+
val derivedFilesErased: Int,
|
|
81
|
+
/** Extra copies that could NOT be erased. Non-zero means media may still be on the device. */
|
|
82
|
+
val derivedFilesFailed: Int,
|
|
83
|
+
/** Present when any part of the operation failed. */
|
|
84
|
+
val error: String? = null
|
|
85
|
+
) {
|
|
86
|
+
/** Rolled-up truth for the UI: everything found was overwritten, nothing was left behind. */
|
|
87
|
+
val fullySecure: Boolean get() = deleted && securelyOverwritten && derivedFilesFailed == 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Securely delete the media behind [uriString], plus every cached copy, derived variant and
|
|
92
|
+
* metadata reference the app holds for it.
|
|
93
|
+
*
|
|
94
|
+
* [assetId] and [fileName], when known, widen the sweep in step 1: derived variants are named
|
|
95
|
+
* after the asset id (`cmp_<id>_p60.mp4`, `edit_<id>_webp.webp`, `cover_<id>_<ts>.jpg`) rather
|
|
96
|
+
* than after the primary file, so without the id those copies are invisible to a path-based
|
|
97
|
+
* search and would survive the delete.
|
|
98
|
+
*
|
|
99
|
+
* [extraUris] is for copies the CALLER knows about that name-matching cannot find โ most
|
|
100
|
+
* importantly the cache file of an item whose published document is the primary. Each is erased
|
|
101
|
+
* with the same guarantees as the primary and counted among the derived copies.
|
|
102
|
+
*/
|
|
103
|
+
suspend fun secureDelete(
|
|
104
|
+
context: Context,
|
|
105
|
+
uriString: String,
|
|
106
|
+
assetId: String? = null,
|
|
107
|
+
fileName: String? = null,
|
|
108
|
+
coverUri: String? = null,
|
|
109
|
+
extraUris: List<String> = emptyList()
|
|
110
|
+
): Report = withContext(Dispatchers.IO) {
|
|
111
|
+
val appContext = context.applicationContext
|
|
112
|
+
val primary = resolveFile(appContext, uriString)
|
|
113
|
+
|
|
114
|
+
// Collect derived copies BEFORE the primary is erased: the primary's own name is one of the
|
|
115
|
+
// things that identifies them, and once it is renamed-and-unlinked that link is gone.
|
|
116
|
+
val derived = collectDerivedFiles(
|
|
117
|
+
appContext,
|
|
118
|
+
primary = primary,
|
|
119
|
+
assetId = assetId,
|
|
120
|
+
fileName = fileName ?: primary?.name,
|
|
121
|
+
coverUri = coverUri,
|
|
122
|
+
extraUris = extraUris
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
val primaryResult = erasePrimary(appContext, uriString, primary)
|
|
126
|
+
|
|
127
|
+
var erased = 0
|
|
128
|
+
var failed = 0
|
|
129
|
+
for (file in derived) {
|
|
130
|
+
val result = SecureFileEraser.erase(file)
|
|
131
|
+
if (result.securelyOverwritten && !file.exists()) {
|
|
132
|
+
erased++
|
|
133
|
+
} else {
|
|
134
|
+
failed++
|
|
135
|
+
Log.e(
|
|
136
|
+
TAG,
|
|
137
|
+
"Derived copy survived secure deletion: ${file.absolutePath} (${result.error ?: "unlink failed"})"
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// References last โ see the class doc. These are best-effort by design: a stale metadata entry
|
|
143
|
+
// is a cosmetic bug, whereas failing the whole delete over one would leave the caller unsure
|
|
144
|
+
// whether the media is gone.
|
|
145
|
+
dropReferences(appContext, uriString, assetId, coverUri)
|
|
146
|
+
|
|
147
|
+
val report = Report(
|
|
148
|
+
deleted = primaryResult.deleted,
|
|
149
|
+
securelyOverwritten = primaryResult.securelyOverwritten,
|
|
150
|
+
originalBytes = primaryResult.originalBytes,
|
|
151
|
+
overwrittenBytes = primaryResult.overwrittenBytes,
|
|
152
|
+
derivedFilesErased = erased,
|
|
153
|
+
derivedFilesFailed = failed,
|
|
154
|
+
error = primaryResult.error
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if (report.fullySecure) {
|
|
158
|
+
Log.i(
|
|
159
|
+
TAG,
|
|
160
|
+
"Securely deleted $uriString: ${MediaFileIo.formatMegabytes(report.originalBytes)} " +
|
|
161
|
+
"overwritten with ${MediaFileIo.formatMegabytes(report.overwrittenBytes)} of garbage, " +
|
|
162
|
+
"$erased derived copies erased"
|
|
163
|
+
)
|
|
164
|
+
} else {
|
|
165
|
+
// Deliberately ERROR level, and deliberately explicit about which guarantee was missed. A
|
|
166
|
+
// failed secure-erase that reads like a success in logcat is how this silently degrades to
|
|
167
|
+
// an ordinary delete.
|
|
168
|
+
Log.e(
|
|
169
|
+
TAG,
|
|
170
|
+
"INSECURE DELETION for $uriString โ deleted=${report.deleted}, " +
|
|
171
|
+
"overwritten=${report.securelyOverwritten}, derivedFailures=$failed, " +
|
|
172
|
+
"error=${report.error ?: "none"}"
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
report
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Outcome of the primary file/URI, before derived copies are considered. */
|
|
180
|
+
private data class PrimaryResult(
|
|
181
|
+
val deleted: Boolean,
|
|
182
|
+
val securelyOverwritten: Boolean,
|
|
183
|
+
val originalBytes: Long,
|
|
184
|
+
val overwrittenBytes: Long,
|
|
185
|
+
val error: String? = null
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Erase whatever [uriString] actually points at.
|
|
190
|
+
*
|
|
191
|
+
* A path-backed URI takes the [File] route โ overwrite in place, rename, unlink. A
|
|
192
|
+
* `content://` URI cannot be renamed or unlinked as a path, so its bytes are overwritten
|
|
193
|
+
* through a read/write descriptor obtained from the provider and the row is then deleted
|
|
194
|
+
* through the resolver. Providers may refuse `"rw"` (many are read-only); that refusal is
|
|
195
|
+
* reported as a failed overwrite rather than hidden, because the row delete that follows would
|
|
196
|
+
* otherwise look like a secure erase while leaving the file's blocks intact.
|
|
197
|
+
*/
|
|
198
|
+
private fun erasePrimary(context: Context, uriString: String, primary: File?): PrimaryResult {
|
|
199
|
+
if (primary != null) {
|
|
200
|
+
val result = SecureFileEraser.erase(primary)
|
|
201
|
+
return PrimaryResult(
|
|
202
|
+
deleted = !primary.exists(),
|
|
203
|
+
securelyOverwritten = result.securelyOverwritten,
|
|
204
|
+
originalBytes = result.originalBytes,
|
|
205
|
+
overwrittenBytes = result.overwrittenBytes,
|
|
206
|
+
error = result.error
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
val uri = runCatching { Uri.parse(uriString) }.getOrNull()
|
|
211
|
+
?: return PrimaryResult(false, false, 0L, 0L, "Unparseable uri: $uriString")
|
|
212
|
+
|
|
213
|
+
if (uri.scheme != "content") {
|
|
214
|
+
// http/https or anything else with no local bytes: nothing to overwrite and nothing to
|
|
215
|
+
// delete. Reported as not-deleted so the caller does not claim a wipe it never performed.
|
|
216
|
+
return PrimaryResult(false, false, 0L, 0L, "No local file behind $uriString")
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
val resolver = context.contentResolver
|
|
220
|
+
var overwrite: SecureFileEraser.Result? = null
|
|
221
|
+
val openError = runCatching {
|
|
222
|
+
resolver.openFileDescriptor(uri, "rw")?.use { pfd ->
|
|
223
|
+
overwrite = SecureFileEraser.overwrite(pfd)
|
|
224
|
+
} ?: throw IllegalStateException("Provider returned no descriptor")
|
|
225
|
+
}.exceptionOrNull()
|
|
226
|
+
|
|
227
|
+
if (openError != null) {
|
|
228
|
+
Log.e(TAG, "Could not open $uriString read/write for secure overwrite", openError)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
val rowsDeleted = runCatching { resolver.delete(uri, null, null) }.getOrDefault(0)
|
|
232
|
+
val result = overwrite
|
|
233
|
+
|
|
234
|
+
return PrimaryResult(
|
|
235
|
+
deleted = rowsDeleted > 0,
|
|
236
|
+
securelyOverwritten = result?.securelyOverwritten == true,
|
|
237
|
+
originalBytes = result?.originalBytes ?: 0L,
|
|
238
|
+
overwrittenBytes = result?.overwrittenBytes ?: 0L,
|
|
239
|
+
error = when {
|
|
240
|
+
result?.error != null -> result.error
|
|
241
|
+
openError != null -> "Provider refused read/write access: ${openError.message}"
|
|
242
|
+
rowsDeleted <= 0 -> "Provider deleted no rows for $uriString"
|
|
243
|
+
else -> null
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The on-disk file behind [uriString], or null when there is not one we can address as a path.
|
|
250
|
+
*
|
|
251
|
+
* `file://` and bare paths resolve directly. A `content://` URI from this app's own FileProvider
|
|
252
|
+
* is mapped back to its path through [providerRoot], which mirrors `file_paths.xml` exactly โ
|
|
253
|
+
* that turns a provider-backed delete into the far stronger path-based erase (overwrite, rename,
|
|
254
|
+
* unlink) instead of depending on the provider honouring an `"rw"` open.
|
|
255
|
+
*
|
|
256
|
+
* The mapping is a lookup, deliberately NOT a search of candidate directories: this path feeds
|
|
257
|
+
* an irreversible overwrite, and a URI resolved to the wrong directory would destroy an
|
|
258
|
+
* unrelated file that happened to share a name. A foreign provider's URI stays null and is
|
|
259
|
+
* handled through the descriptor route, since only that provider knows how its authority maps
|
|
260
|
+
* to storage.
|
|
261
|
+
*/
|
|
262
|
+
private fun resolveFile(context: Context, uriString: String): File? {
|
|
263
|
+
if (uriString.isBlank()) return null
|
|
264
|
+
|
|
265
|
+
val uri = runCatching { Uri.parse(uriString) }.getOrNull() ?: return null
|
|
266
|
+
val scheme = uri.scheme
|
|
267
|
+
|
|
268
|
+
if (scheme == null || scheme == "file") {
|
|
269
|
+
val path = if (scheme == null) uriString else uri.path
|
|
270
|
+
return path?.takeIf { it.isNotBlank() }?.let { File(it) }?.takeIf { it.isFile }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (scheme != "content") return null
|
|
274
|
+
// Only OUR authority may be mapped back to a path.
|
|
275
|
+
if (uri.authority != "${context.packageName}.camera.fileprovider") return null
|
|
276
|
+
|
|
277
|
+
val segments = uri.pathSegments
|
|
278
|
+
if (segments.size < 2) return null
|
|
279
|
+
|
|
280
|
+
// FileProvider URIs are /<declared-name>/<path/under/that/root>.
|
|
281
|
+
val rootDir = providerRoot(context, segments[0]) ?: return null
|
|
282
|
+
val relative = segments.drop(1).joinToString("/")
|
|
283
|
+
val candidate = File(rootDir, relative)
|
|
284
|
+
|
|
285
|
+
// Confine the result to the declared root: a URI carrying `..` segments must not be able to
|
|
286
|
+
// steer an overwrite outside the directory its provider entry exposes.
|
|
287
|
+
val rootPath = runCatching { rootDir.canonicalPath }.getOrNull() ?: return null
|
|
288
|
+
val candidatePath = runCatching { candidate.canonicalPath }.getOrNull() ?: return null
|
|
289
|
+
if (candidatePath != rootPath && !candidatePath.startsWith("$rootPath${File.separator}")) {
|
|
290
|
+
Log.w(TAG, "Refusing to resolve $uriString outside its provider root")
|
|
291
|
+
return null
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return candidate.takeIf { it.isFile }
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Directory behind one `file_paths.xml` entry name, or null for a name this app never declared.
|
|
299
|
+
*
|
|
300
|
+
* Kept as a `when` over the declared names rather than a map so it stays obviously
|
|
301
|
+
* side-by-side-comparable with the XML โ if an entry is added there and not here, a provider URI
|
|
302
|
+
* for it resolves to null and falls back to the descriptor route rather than resolving wrongly.
|
|
303
|
+
*/
|
|
304
|
+
private fun providerRoot(context: Context, name: String): File? {
|
|
305
|
+
val externalFiles = context.getExternalFilesDir(null)
|
|
306
|
+
return when (name) {
|
|
307
|
+
"camera_media_cache" -> File(context.cacheDir, "camera_media_cache")
|
|
308
|
+
"edited_images" -> File(context.cacheDir, "edited")
|
|
309
|
+
"compressed_videos" -> File(context.cacheDir, "compressed")
|
|
310
|
+
"exported_media" -> File(context.cacheDir, "exported")
|
|
311
|
+
"shared_media" -> File(context.cacheDir, "shared")
|
|
312
|
+
"documents" -> externalFiles
|
|
313
|
+
"media_documents" -> externalFiles?.let { File(it, "media_documents") }
|
|
314
|
+
"photos" -> externalFiles?.let { File(it, "media_documents/photos") }
|
|
315
|
+
"videos" -> externalFiles?.let { File(it, "media_documents/videos") }
|
|
316
|
+
else -> null
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Every OTHER file on disk that holds this asset's content.
|
|
322
|
+
*
|
|
323
|
+
* Matching is by file name against two keys โ the asset id and the primary file's base name โ
|
|
324
|
+
* because that is how each producer in this codebase names its output
|
|
325
|
+
* ([CompressionFileNameUtils.createUniqueFileName] prefixes with `<verb>_<assetId>`, and
|
|
326
|
+
* [MediaCacheManager.generateFileName] names the cache file after the id itself). The primary
|
|
327
|
+
* file is excluded so it is never erased twice, and directory entries are skipped.
|
|
328
|
+
*
|
|
329
|
+
* A blank/short id is ignored rather than matched loosely: a two-character key would match
|
|
330
|
+
* unrelated media and this function's output is fed straight into an irreversible overwrite.
|
|
331
|
+
*/
|
|
332
|
+
private fun collectDerivedFiles(
|
|
333
|
+
context: Context,
|
|
334
|
+
primary: File?,
|
|
335
|
+
assetId: String?,
|
|
336
|
+
fileName: String?,
|
|
337
|
+
coverUri: String?,
|
|
338
|
+
extraUris: List<String>
|
|
339
|
+
): List<File> {
|
|
340
|
+
val keys = mutableSetOf<String>()
|
|
341
|
+
assetId?.trim()?.lowercase(Locale.US)?.takeIf { it.length >= MIN_MATCH_KEY_LENGTH }?.let(keys::add)
|
|
342
|
+
fileName?.let { name ->
|
|
343
|
+
File(name).nameWithoutExtension
|
|
344
|
+
.trim()
|
|
345
|
+
.lowercase(Locale.US)
|
|
346
|
+
.takeIf { it.length >= MIN_MATCH_KEY_LENGTH }
|
|
347
|
+
?.let(keys::add)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
val primaryPath = primary?.absolutePath
|
|
351
|
+
val found = LinkedHashMap<String, File>()
|
|
352
|
+
|
|
353
|
+
fun consider(file: File) {
|
|
354
|
+
if (!file.isFile) return
|
|
355
|
+
val path = file.absolutePath
|
|
356
|
+
if (path == primaryPath) return
|
|
357
|
+
if (!found.containsKey(path)) found[path] = file
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// The chosen cover frame is a real JPEG of a video frame โ an image of the deleted media โ
|
|
361
|
+
// so it is erased with the same guarantees rather than merely unlinked.
|
|
362
|
+
coverUri?.let { cover -> resolveFile(context, cover)?.let(::consider) }
|
|
363
|
+
|
|
364
|
+
// Copies the caller named explicitly (e.g. the cache file behind a published document).
|
|
365
|
+
for (extra in extraUris) {
|
|
366
|
+
resolveFile(context, extra)?.let(::consider)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// The cache manager's own record is authoritative for the cached copy, and survives even when
|
|
370
|
+
// the published file is what the UI handed us.
|
|
371
|
+
assetId?.takeIf { it.isNotBlank() }?.let { id ->
|
|
372
|
+
MediaCacheManager.getInstance(context).getMetadata(id)?.cachePath
|
|
373
|
+
?.let { File(it) }
|
|
374
|
+
?.let(::consider)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (keys.isEmpty()) return found.values.toList()
|
|
378
|
+
|
|
379
|
+
val roots = listOfNotNull(context.cacheDir, context.getExternalFilesDir(null), context.filesDir)
|
|
380
|
+
for (root in roots) {
|
|
381
|
+
for (dirName in DERIVED_CACHE_DIRS) {
|
|
382
|
+
val dir = File(root, dirName)
|
|
383
|
+
if (!dir.isDirectory) continue
|
|
384
|
+
// listFiles (one level) rather than a recursive walk: every producer writes variants flat
|
|
385
|
+
// into these directories, and a deep walk over the external files dir would drag in
|
|
386
|
+
// unrelated app data on a device with a large media library.
|
|
387
|
+
dir.listFiles()?.forEach { file ->
|
|
388
|
+
val name = file.name.lowercase(Locale.US)
|
|
389
|
+
if (keys.any { key -> containsDelimitedKey(name, key) }) consider(file)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return found.values.toList()
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* True when [name] contains [key] as a whole token rather than as an arbitrary substring.
|
|
399
|
+
*
|
|
400
|
+
* Every producer builds its output name as `<verb>_<key>_<suffix>` or `<key>.<ext>`, so a match
|
|
401
|
+
* must begin at the start of the name or immediately after a `_`/`-`. A bare `contains` would
|
|
402
|
+
* let a short-but-legal key like `picture` also match `mypicture.jpg`, and the caller feeds this
|
|
403
|
+
* straight into an irreversible overwrite โ a false positive here destroys an unrelated file.
|
|
404
|
+
*/
|
|
405
|
+
private fun containsDelimitedKey(name: String, key: String): Boolean {
|
|
406
|
+
var from = 0
|
|
407
|
+
while (true) {
|
|
408
|
+
val at = name.indexOf(key, from)
|
|
409
|
+
if (at < 0) return false
|
|
410
|
+
if (at == 0 || name[at - 1] == '_' || name[at - 1] == '-') return true
|
|
411
|
+
from = at + 1
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Drop every REFERENCE to the deleted media: cached bitmaps, cache metadata, storage metadata,
|
|
417
|
+
* and any MediaStore thumbnail rows.
|
|
418
|
+
*
|
|
419
|
+
* All of it is best-effort and none of it can fail the deletion โ the bytes are already gone by
|
|
420
|
+
* the time this runs. What it prevents is the deleted item continuing to appear: a thumbnail
|
|
421
|
+
* served from the LruCache renders a photo whose file no longer exists, and a surviving metadata
|
|
422
|
+
* entry keeps it in the cache-size accounting and the duplicate index forever.
|
|
423
|
+
*/
|
|
424
|
+
private suspend fun dropReferences(
|
|
425
|
+
context: Context,
|
|
426
|
+
uriString: String,
|
|
427
|
+
assetId: String?,
|
|
428
|
+
coverUri: String?
|
|
429
|
+
) {
|
|
430
|
+
// Decoded bitmaps first: this is the only reference that can still be DISPLAYED.
|
|
431
|
+
runCatching { MediaThumbnailView.evictFromSharedCache(uriString) }
|
|
432
|
+
.onFailure { Log.w(TAG, "Failed to evict thumbnail for $uriString", it) }
|
|
433
|
+
coverUri?.let { cover ->
|
|
434
|
+
runCatching { MediaThumbnailView.evictFromSharedCache(cover) }
|
|
435
|
+
.onFailure { Log.w(TAG, "Failed to evict cover thumbnail", it) }
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
assetId?.takeIf { it.isNotBlank() }?.let { id ->
|
|
439
|
+
// deleteCachedMedia also unlinks the cache file, which by now is already erased and gone โ
|
|
440
|
+
// it treats a missing file as deleted, so this just clears the metadata entry.
|
|
441
|
+
runCatching { MediaCacheManager.getInstance(context).deleteCachedMedia(id) }
|
|
442
|
+
.onFailure { Log.w(TAG, "Failed to clear cache metadata for $id", it) }
|
|
443
|
+
runCatching { MediaMetadataStore.getInstance(context).removeMedia(id) }
|
|
444
|
+
.onFailure { Log.w(TAG, "Failed to clear storage metadata for $id", it) }
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// MediaStore keeps its own generated thumbnails for anything that was ever published to the
|
|
448
|
+
// gallery. Deleting the media row does not always take them with it, leaving a recoverable
|
|
449
|
+
// downscaled copy of a "securely deleted" photo in the system's thumbnail store.
|
|
450
|
+
runCatching { deleteMediaStoreThumbnails(context, uriString) }
|
|
451
|
+
.onFailure { Log.w(TAG, "Failed to clear MediaStore thumbnails for $uriString", it) }
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Remove MediaStore's own thumbnail rows for a published file.
|
|
456
|
+
*
|
|
457
|
+
* Only meaningful pre-Android Q: from Q onward thumbnails live in a system-managed store that
|
|
458
|
+
* is keyed to the media row and removed with it, and the legacy `Images.Thumbnails` table is no
|
|
459
|
+
* longer writable by apps.
|
|
460
|
+
*/
|
|
461
|
+
private fun deleteMediaStoreThumbnails(context: Context, uriString: String) {
|
|
462
|
+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) return
|
|
463
|
+
|
|
464
|
+
val path = resolveFile(context, uriString)?.absolutePath
|
|
465
|
+
?: Uri.parse(uriString).path
|
|
466
|
+
?: return
|
|
467
|
+
|
|
468
|
+
val resolver = context.contentResolver
|
|
469
|
+
@Suppress("DEPRECATION")
|
|
470
|
+
val imageId = resolver.query(
|
|
471
|
+
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
|
472
|
+
arrayOf(MediaStore.Images.Media._ID),
|
|
473
|
+
"${MediaStore.Images.Media.DATA} = ?",
|
|
474
|
+
arrayOf(path),
|
|
475
|
+
null
|
|
476
|
+
)?.use { cursor ->
|
|
477
|
+
if (cursor.moveToFirst()) cursor.getLong(0) else null
|
|
478
|
+
} ?: return
|
|
479
|
+
|
|
480
|
+
@Suppress("DEPRECATION")
|
|
481
|
+
resolver.delete(
|
|
482
|
+
MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI,
|
|
483
|
+
"${MediaStore.Images.Thumbnails.IMAGE_ID} = ?",
|
|
484
|
+
arrayOf(imageId.toString())
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Shortest name fragment accepted as an identity match in [collectDerivedFiles]. Anything
|
|
490
|
+
* shorter risks matching unrelated files, and the match feeds an irreversible overwrite.
|
|
491
|
+
*/
|
|
492
|
+
private const val MIN_MATCH_KEY_LENGTH = 6
|
|
493
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
package com.customcamera
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Paint
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Categorized sticker collection for the preview editor's sticker picker.
|
|
8
|
+
*
|
|
9
|
+
* A sticker is either:
|
|
10
|
+
* - [Sticker.Res] โ a bundled vector drawable (the classic hand-authored set, listed in
|
|
11
|
+
* R.array.editor_stickers so the collection stays resource-extensible), or
|
|
12
|
+
* - [Sticker.Emoji] โ an emoji glyph rendered to a high-resolution bitmap at pick time.
|
|
13
|
+
*
|
|
14
|
+
* Emoji-backed stickers are what make the collection complete and scalable: adding a
|
|
15
|
+
* sticker is a one-line data change with zero new binary assets, and nothing is
|
|
16
|
+
* rasterized until the user actually picks it (nothing but text is held in memory while
|
|
17
|
+
* browsing โ important on low-end devices).
|
|
18
|
+
*
|
|
19
|
+
* Like [EmojiPickerData], glyphs are filtered once per process through [Paint.hasGlyph]
|
|
20
|
+
* so devices with older emoji fonts never show tofu boxes.
|
|
21
|
+
*/
|
|
22
|
+
internal object StickerPickerData {
|
|
23
|
+
|
|
24
|
+
sealed class Sticker {
|
|
25
|
+
data class Res(val resId: Int) : Sticker()
|
|
26
|
+
data class Emoji(val glyph: String) : Sticker()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
data class Category(val icon: String, val label: String, val stickers: List<Sticker>)
|
|
30
|
+
|
|
31
|
+
private val emojiCategories: List<Pair<Pair<String, String>, List<String>>> = listOf(
|
|
32
|
+
("๐" to "Faces") to listOf(
|
|
33
|
+
"๐", "๐", "๐คฃ", "๐ฅฐ", "๐", "๐", "๐คฉ", "๐", "๐คช", "๐ฅณ",
|
|
34
|
+
"๐", "๐ค", "๐ค", "๐", "๐ด", "๐ฅบ", "๐ญ", "๐ฑ", "๐คฏ", "๐ฅถ",
|
|
35
|
+
"๐", "๐ค", "๐", "๐ป", "๐คก", "๐ฝ", "๐ฉ", "๐บ", "๐", "๐", "๐"
|
|
36
|
+
),
|
|
37
|
+
("โค๏ธ" to "Love") to listOf(
|
|
38
|
+
"โค๏ธ", "๐งก", "๐", "๐", "๐", "๐", "๐ค", "๐ค", "๐", "๐",
|
|
39
|
+
"๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐น", "๐ฅ",
|
|
40
|
+
"๐", "๐", "๐", "๐๏ธ"
|
|
41
|
+
),
|
|
42
|
+
("๐" to "Celebration") to listOf(
|
|
43
|
+
"๐", "๐", "๐", "๐", "๐", "๐", "๐ง", "๐ฐ", "๐ฅ", "๐พ",
|
|
44
|
+
"๐", "๐ฅ", "๐ฅ", "๐ฅ", "๐
", "โจ", "๐", "๐", "๐", "๐",
|
|
45
|
+
"๐
", "๐ช", "๐ฏ", "๐ฎ", "๐ฒ", "๐ธ", "๐ค", "๐ง"
|
|
46
|
+
),
|
|
47
|
+
("๐ผ" to "Animals") to listOf(
|
|
48
|
+
"๐ถ", "๐ฑ", "๐ผ", "๐จ", "๐ฆ", "๐ฏ", "๐ฆ", "๐ฐ", "๐ธ", "๐ต",
|
|
49
|
+
"๐ฆ", "๐ท", "๐ฎ", "๐", "๐ง", "๐ฆ", "๐ฆ", "๐ข", "๐", "๐ฆ",
|
|
50
|
+
"๐ฆ", "๐ณ", "๐", "๐", "๐ฆ", "๐ฆฉ"
|
|
51
|
+
),
|
|
52
|
+
("๐" to "Food") to listOf(
|
|
53
|
+
"๐", "๐", "๐", "๐ญ", "๐ฟ", "๐ฎ", "๐ฉ", "๐ช", "๐ฆ", "๐ซ",
|
|
54
|
+
"๐ญ", "๐", "๐", "๐", "๐", "๐ฅ", "๐", "๐ฅ", "๐", "๐ฃ",
|
|
55
|
+
"โ", "๐ง", "๐ฅค", "๐บ"
|
|
56
|
+
),
|
|
57
|
+
("๐" to "Vibes") to listOf(
|
|
58
|
+
"๐", "โญ", "๐", "๐", "โ๏ธ", "โก", "๐ฅ", "โ๏ธ", "๐", "๐ธ",
|
|
59
|
+
"๐บ", "๐ป", "๐", "๐ต", "๐", "๐", "๐ฏ", "โ
", "โ", "๐ค",
|
|
60
|
+
"๐ฅ", "๐ซ", "๐", "๐ธ", "โฝ", "๐", "๐", "๐ฆธ"
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@Volatile
|
|
65
|
+
private var cache: List<Category>? = null
|
|
66
|
+
|
|
67
|
+
fun invalidateCache() {
|
|
68
|
+
cache = null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The full categorized collection: the classic drawable stickers (resolved from
|
|
73
|
+
* R.array.editor_stickers) followed by the emoji-backed categories, filtered to
|
|
74
|
+
* glyphs this device's emoji font can render. Computed once per process.
|
|
75
|
+
*/
|
|
76
|
+
fun categories(context: Context): List<Category> {
|
|
77
|
+
cache?.let { return it }
|
|
78
|
+
synchronized(this) {
|
|
79
|
+
cache?.let { return it }
|
|
80
|
+
|
|
81
|
+
val result = mutableListOf<Category>()
|
|
82
|
+
|
|
83
|
+
val names = context.resources.getStringArray(R.array.editor_stickers)
|
|
84
|
+
val classic = names.mapNotNull { name ->
|
|
85
|
+
val id = context.resources.getIdentifier(name, "drawable", context.packageName)
|
|
86
|
+
if (id != 0) Sticker.Res(id) else null
|
|
87
|
+
}
|
|
88
|
+
if (classic.isNotEmpty()) {
|
|
89
|
+
result.add(Category(icon = "โญ", label = Localization.getString("categoryClassic", "Classic"), stickers = classic))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
val paint = Paint()
|
|
93
|
+
for ((header, glyphs) in emojiCategories) {
|
|
94
|
+
val (icon, defaultLabel) = header
|
|
95
|
+
val labelKey = "category" + defaultLabel.replace(" ", "")
|
|
96
|
+
val label = Localization.getString(labelKey, defaultLabel)
|
|
97
|
+
val supported = glyphs.filter { paint.hasGlyph(it) }
|
|
98
|
+
if (supported.isNotEmpty()) {
|
|
99
|
+
result.add(Category(icon, label, supported.map { Sticker.Emoji(it) }))
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
cache = result
|
|
104
|
+
return result
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|