From d22af8f25686ea2c3ce2c1df253973b8f78f9fe4 Mon Sep 17 00:00:00 2001 From: caspar Date: Mon, 12 Oct 2026 14:07:04 -0400 Subject: [PATCH] Show an explicit camera/gallery choice for photo uploads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Image file inputs went straight to the gallery picker when the camera was hidden by the system chooser or its permission was missing. They now show a Tomar foto / Elegir de la galería dialog (capture fields open the camera directly), and a denied camera permission explains how to enable it in Settings instead of silently falling back to the picker. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 2 +- .../java/mx/joykoo/scanner/FileTransfers.kt | 78 +++++++++++++++---- app/src/main/res/values/strings.xml | 7 ++ 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 034e18b..d2a2c02 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,7 +89,7 @@ All configurable values are in `AppConfig.kt`: - Permissions are requested at first launch (camera; storage on Android ≤ 9) and again when a feature needs one. - Downloads: https on allowed hosts via `DownloadManager` (session cookie forwarded); `data:` decoded natively; `blob:` read in-page and returned via `evaluateJavascript` polling (no JS interface). A document-start hook keeps blobs readable if the site revokes the URL immediately. Saved to Downloads, with a toast on completion. -- Uploads: `` image fields offer the camera; `capture` opens it directly. Photos are saved to Pictures/Joykoo. +- Uploads: `` image fields show an in-app "Tomar foto / Elegir de la galería" dialog (the system chooser hid the camera behind the gallery picker); `capture` opens the camera directly. If camera permission is denied, a dialog offers app Settings or the file picker. Photos are saved to Pictures/Joykoo. - In-page camera (`getUserMedia`) is granted to allowed hosts only, video only. ## Security diff --git a/app/src/main/java/mx/joykoo/scanner/FileTransfers.kt b/app/src/main/java/mx/joykoo/scanner/FileTransfers.kt index 9c07064..b8962d7 100644 --- a/app/src/main/java/mx/joykoo/scanner/FileTransfers.kt +++ b/app/src/main/java/mx/joykoo/scanner/FileTransfers.kt @@ -1,6 +1,7 @@ package mx.joykoo.scanner import android.app.Activity +import android.app.AlertDialog import android.app.DownloadManager import android.content.ActivityNotFoundException import android.content.BroadcastReceiver @@ -15,6 +16,7 @@ import android.os.Environment import android.os.Handler import android.os.Looper import android.provider.MediaStore +import android.provider.Settings import android.util.Base64 import android.util.Log import android.webkit.CookieManager @@ -271,31 +273,77 @@ class FileTransfers( // ── Uploads () ───────────────────────────────────── - /** Wire to WebChromeClient.onShowFileChooser. */ + /** + * Wire to WebChromeClient.onShowFileChooser. Image fields get an explicit + * "Tomar foto / Elegir archivo" choice (or the camera directly for `capture` fields) + * instead of the system chooser, which on many devices opens the gallery picker + * and hides the camera. + */ fun onShowFileChooser(callback: ValueCallback>, params: FileChooserParams): Boolean { uploadCallback?.onReceiveValue(null) uploadCallback = callback - val offerCamera = acceptsImages(params.acceptTypes) - val needed = if (offerCamera) Permissions.CAMERA + Permissions.STORAGE else emptyArray() - permissions.withPermissions(needed) { launchChooser(params, offerCamera) } + if (!acceptsImages(params.acceptTypes)) { + launchPicker(params) + return true + } + val needed = Permissions.CAMERA + Permissions.STORAGE + permissions.withPermissions(needed) { + when { + !permissions.allGranted(needed) -> showCameraDenied(params) + params.isCaptureEnabled -> launchCamera(params) + else -> showPhotoSourceDialog(params) + } + } return true } - private fun launchChooser(params: FileChooserParams, offerCamera: Boolean) { - // Launching the camera without the CAMERA grant throws, so it is only offered when granted. - val camera = if (offerCamera && permissions.allGranted(Permissions.CAMERA + Permissions.STORAGE)) - cameraIntent() else null - val picker = params.createIntent() - val intent = when { - camera != null && params.isCaptureEnabled -> camera - camera != null -> Intent.createChooser(picker, null) - .putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(camera)) - else -> picker + private fun showPhotoSourceDialog(params: FileChooserParams) { + AlertDialog.Builder(activity) + .setTitle(R.string.upload_title) + .setItems(arrayOf( + activity.getString(R.string.upload_take_photo), + activity.getString(R.string.upload_pick_file), + )) { _, which -> if (which == 0) launchCamera(params) else launchPicker(params) } + .setOnCancelListener { onChooserResult(Activity.RESULT_CANCELED, null) } + .show() + } + + /** Camera permission was refused; Android stops prompting after repeated denials, + * so offer the app's settings page as the way to turn it back on. */ + private fun showCameraDenied(params: FileChooserParams) { + AlertDialog.Builder(activity) + .setTitle(R.string.camera_denied_title) + .setMessage(R.string.camera_denied_message) + .setPositiveButton(R.string.camera_open_settings) { _, _ -> + onChooserResult(Activity.RESULT_CANCELED, null) + activity.startActivity( + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + .setData(Uri.fromParts("package", activity.packageName, null)) + ) + } + .setNeutralButton(R.string.upload_pick_file) { _, _ -> launchPicker(params) } + .setOnCancelListener { onChooserResult(Activity.RESULT_CANCELED, null) } + .show() + } + + private fun launchCamera(params: FileChooserParams) { + val camera = cameraIntent() + if (camera == null) { + toast(R.string.camera_failed) + launchPicker(params) + return } + launch(camera) + } + + private fun launchPicker(params: FileChooserParams) = launch(params.createIntent()) + + private fun launch(intent: Intent) { try { chooserLauncher.launch(intent) } catch (e: ActivityNotFoundException) { - Log.e(TAG, "No app to pick files", e) + Log.e(TAG, "No app for ${intent.action}", e) + toast(R.string.camera_failed) onChooserResult(Activity.RESULT_CANCELED, null) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 96518d1..26a6d51 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,13 @@ Guardado en Descargas: %1$s No se pudo descargar el archivo Se necesita permiso de almacenamiento para guardar archivos + Agregar foto + Tomar foto + Elegir de la galería o archivos + No se pudo abrir la cámara + Permiso de cámara + La app no tiene permiso para usar la cámara. Actívelo en Ajustes → Permisos → Cámara y vuelva a intentarlo. + Abrir Ajustes Sin Wi-Fi sin internet %1$d%%