====== Ús de la càmera amb Android ======
En aquest article veurem com fer ús del //hardware// de la càmera per a capturar imatges i vídeos. També tractarem sobre l'emmagatzematge dels arxius multimèdia.
{{ android-camera.jpg?direct }}
Referències:
* Articles en aquesta wiki:
* [[Android]]
* [[Android threads]]
* [[Android persistència]]
* [[https://ioc.xtec.cat/materials/FP/Recursos/fp_dam_m08_/web/fp_dam_m08_htmlindex/WebContent/u3/a2/continguts.html|Apunts de MP08 UF2 de DAM a l'IOC]].
* [[https://developer.android.com/training/camera-deprecated/photobasics#java|Article "photo basics"]] oficial d'Android. Cal llegir-lo en anglès perquè la versió en castellà conté errors.
* [[https://developer.android.com/studio/debug/device-file-explorer|Device File Explorer]] de l'Android Studio.
{{tag> #Dam #DamMp08 #DamMp08Uf2 #DamMp08Uf02 Android mobile java }}
\\
===== Estratègies per a captura d'imatges =====
Tenim 2 estratègies bàsiques en el què pertoca a l'adquisició d'imatges amb la càmera:
- Cridar la **app Camera externa del propi sistema operatiu**. És la forma més senzilla de treballar. Podria arribar a passar que el SO no tingui cap app Camera instal·lada, però sol ser poc habitual. Emprarem un ''Intent'' per cridar a l'altra app, la qual ens retornarà la foto com a dades (thumbnail) en el retorn del Intent, o en un arxiu que cal habilitar prèviament abans de cridar-la.{{ android-camera-intent.jpg?direct&250 }}
- **Versió amb //thumbnail// (miniatura)**: si no adjuntem cap ''File'' al ''Intent'', la App Camera ens retornarà un //thumbnail// que podem assignar a una ''ImageView''.
- **Versió //full-size//**: per poder disposar de la foto a tamany complert cal donar permís a la App Camera amb un ''FileProvider''. \\
- Implementar **a la nostra pròpia app** la visualització de la càmera i la presa de dades. A nivell de llibreries i codi és més complicat de gestionar. **Per la previsualització necessitarem disposar d'una ''SurfaceView'' o una ''PreviewView''** que podrà monitoritzar el què està captant la càmera i habilitar un botó per a realitzar la captura des del nostre codi. Per aquest mecanisme disposem de diverses llibreries:{{ android:android-surfaceview.webp?direct&300 }}
\\
===== Utilitzant la App Camera externa =====
Podem emprar el [[https://developer.android.com/training/camera-deprecated/photobasics#java|tutorial Photo Basics oficial d'Android]]. A l'inici diu que està //deprecated// però en realitat encara serveix. Indica que a l'article és obsolet perquè s'utilitza la ''class Camera'' (obsoleta) però no s'utilitza pas en tot el tutorial. Utilitzem **la app Camera externa del sistema operatiu, que no té res a veure amb la ''class Camera''**.
El què sí que està obsolet del tutorial és com llençar una altra app externa per rebre dades. Inicialment es feia amb el mètode ''Activity.startActivityForResult'' (complementant-se amb la //callback// ''onActivityResult''). Ara s'ha de fer amb [[https://developer.android.com/training/basics/intents/result#java|Activity Results API]].
Aquest [[https://medium.com/@patelsneh18/startactvivityforresult-deprecated-alternative-and-using-it-outside-activity-class-bc9331cf896|tutorial per a crida d'una app externa]] ens clarifica com utilitzar adequadament la [[https://developer.android.com/training/basics/intents/result#java|Activity Results API]] amb un ''Intent'' que crida, en aquest cas, la galeria d'imatges, i rep una foto.
De fet, l'exemple del tutorial s'assembla més al [[https://developer.android.com/training/basics/intents/result#custom|darrer exemple de l'Activity Results API]], el subtítol diu "Create a custom contract" però acte seguit parla del **genèric StartActivityForResult contract** que és el que serveix per al cas genèric i que s'empra en el tutorial. Li falten alguns avantatges dels nous contractes que milloren algunes situacions, però és més fàcil de treballar.
**Exercici galeria d'imatges**
Implementa el [[https://medium.com/@patelsneh18/startactvivityforresult-deprecated-alternative-and-using-it-outside-activity-class-bc9331cf896|tutorial d'accés a la Galeria d'Imatges]] per veure com ha canviat la manera d'invocar una app externa (en aquest cas la Gallery).
També hi ha una solució indicada al primer exemple de l'[[https://developer.android.com/training/basics/intents/result#java|Activity Results API]] però és lleugerament diferent i pot confondre. Millor utilitzem l'exemple del tutorial.
**Tant si seguim el tutorial com l'article de l'Activity Results API, caldrà que afegeixis al //layout// un ''Button'' per disparar la visualització de la galeria, i un ''ImageView'' per visualitzar la foto que ens tornin**.
Si utilitzes el tutorial com a referència, pensa que:
- Només cal que posis el codi de la ''MainActivity'', no cal el ''AddImageDialog''.
- La líniaImageView imageView = R.id.img;
en realitat ha de ser:ImageView imageView = findViewById(R.id.imageView);
- L'objecte ''ActivityResultLauncher someActivityResultLauncher'' peta si el deixem dins la funció i el cridem des d'un botó amb un ''OnClickListener''. Una solució és posar-lo com a atribut de la classe i crear-lo en el ''onCreate'' de la ''MainActivity''.
**Exercici Take Photo thumbnail**
Segueix la primera part del [[https://developer.android.com/training/camera-deprecated/photobasics#java|tutorial Photo Basics]] oficial. **ULL: ha de ser la versió en anglès**, la traducció al castellà té errors. A més, corregirem la part obsoleta i utilitzarem la **Activity Result API**.
- Inicialment només captarem el //thumbnail// i el mostrarem a una ImageView, similarment al [[https://medium.com/@patelsneh18/startactvivityforresult-deprecated-alternative-and-using-it-outside-activity-class-bc9331cf896|tutorial de la Galeria d'Imatges]].
- Implementa la crida a la app externa amb la [[https://developer.android.com/training/basics/intents/result#java|Activity Results API]], tal i com hem fet a l'anterior exercici de la Galeria (enlloc de l'obsolet ''startActivityForResult'').
**Observa les diverses opcions** per cridar les aplicacions externes de galeria, en particular:
- ''[[https://developer.android.com/reference/android/content/Intent#ACTION_PICK|Intent.ACTION_PICK]]''
- ''[[https://developer.android.com/reference/android/content/Intent#ACTION_GET_CONTENT|Intent.ACTION_GET_CONTENT]]''
- ''[[https://developer.android.com/reference/android/provider/MediaStore#ACTION_PICK_IMAGES|MediaStore.ACTION_PICK_IMAGES]]'' (a partir de la API 33)
[[https://stackoverflow.com/questions/6486716/using-intent-action-pick-for-specific-path/6486827#6486827|Aquest fil parla sobre quina és més convenient]] de les 2 primeres.
En canvi, per a la captura d'imatges, la opció és (tal i com hem vist al tutorial):
* ''[[https://developer.android.com/reference/android/provider/MediaStore#ACTION_IMAGE_CAPTURE|MediaStore.ACTION_IMAGE_CAPTURE]]''
\\
===== Foto full-size i FileProvider : donant accés a l'espai privat =====
Demanar que una altra app faci la foto que nosaltres després utilitzarem té una dificultat: la seguretat d'Android priva que una app pugui veure en els arxius d'una altra. Per tant, la App Camera no podria escriure en el nostre espai intern.
Per solucionar aquest problema, existeix [[https://developer.android.com/reference/androidx/core/content/FileProvider|FileProvider]]. Aquest objecte publicarà temporalment un recurs en un objecte tipus ''Uri'' perquè la App Camera hi pugui escriure.
{{ android:external-camera-app.png }}
Així, els passos que cal fer son:
- Crear un ''FileProvider'' al ''AndroidManifest.xml''
- Definir els directoris a un arxiu de Recursos (res) ''file_paths.xml''
- Crear un arxiu temporal amb l'objecte ''File.createTempFile''.
- Utilitza també les funcions d'emmagatzematge intern o extern (''getFilesDir'' o ''getExternalFilesDir'').
- Transforma el ''File'' en ''Uri'' amb ''FileProvider.getUriForFile''
- Llançar el ''Intent'' per a la App Camera.
- Quan rebem el resultat a ''onActivityResult'', la mateixa ''Uri'' que ja teniem abans ens serveix per assignar-la a la ImageView.
**Exercici take photo full size**
Segueix la **part "Take full size photo"** del [[https://developer.android.com/training/camera-deprecated/photobasics#TaskPath|tutorial Photo Basics]] oficial.
**Recorda ha de ser la versió en anglès**, la traducció al castellà té errors.
**App v0.3** - Afegeix un altre botó a l'app que hem fet a l'exercici previ que permeti agafar fotos //full-size// com s'explica al tutorial. Recorda que ara cal fer servir la ''Activity Results API''. Millor crea un nou objecte ''ActivityLauncher'' per no embolicar el tractament post-camera.
**Exercici MyGallery**
Implementa la recuperació de la imatge a l'inici de l'aplicació. Si existeix ja un arxiu de foto, mostra'l al ImageView.
**App v0.4** - Implementa la visualització de les fotos que anem prenent en l'àrea compartida de l'aplicació. Segueix les instruccions a l'article [[Android RecyclerView]] per visualitzar les fotos amb una //preview// en format //grid//.
\\
===== Imatge de la càmera dins la nostra App =====
Els objectes ''SurfaceView'' i ''PreviewView'' (//widgets// que podem incorporar al nostre //layout// de l'''Activity'') ens permeten veure el què captura la càmera directament sobre la nostra App. Sembla ser, però, que la gestió d'una ''SurfaceView'' és força complicada. Si emprem CameraX, serà més fàcil la gestió amb ''PreviewView''.
{{ android:android-surfaceview.webp?direct&300 }}
Llibreries Android disponibles:
- [[https://developer.android.com/reference/android/hardware/Camera|Camera]]: obsoleta des de la API 21. Cal emprar Camera2. Ull amb els tutorials obsolets.
- [[https://developer.android.com/reference/android/hardware/Camera|Camera2]]: la llibreria oficial actual.
- **CameraX**: una sèria d'utilitats que ens facilita (relativament) el codi. Per sota utilitza Camera2.
* [[https://developer.android.com/training/camerax|Descripció general de CameraX]].
* [[https://developer.android.com/jetpack/androidx/releases/camera?hl=es-419#groovy|Doc oficial de CameraX]].
**Exercici PreviewView amb CameraX**
Farem una captura d'imatge dins la nostra pròpia App sobre una ''PreviewView''. Després afegirem la captura d'imatge sobre un arxiu.
\\
**Primera part: in-app preview**
Referència:
* [[https://medium.com/swlh/introduction-to-androids-camerax-with-java-ca384c522c5|tutorial d'ús de CameraX en Java (medium.com)]].
* Consulteu la [[https://developer.android.com/jetpack/androidx/releases/camera?hl=es-419#groovy|doc oficial de CameraX]] per saber quina és la versió de la llibreria que convé utilitzar.
* Alguns //hacks// que cal fer, ja que el tutorial utilitza la v1.0.0 de CameraX, i les versions successives tenen alguns canvis:
* ''Manifest.permission.CAMERA'' -> ''android.Manifest.permission.CAMERA''
* ''previewView.createSurfaceProvider()'' -> ''previewView.getSurfaceProvider()''
* Probablement al crear el //layout// des del XML no funcioni la vista prèvia en Android Studio, però la App funcionarà.
\\
**Segona part: captura d'imatge**
Implementa la captura d'imatge i enregistr-la en un arxiu. En aquest cas, al treballar internament a la nostra app, no caldrà FileProvider ja que l'accés als arxius interns de la app és directe.
Referència:
* [[https://akhilbattula.medium.com/android-camerax-java-example-aeee884f9102|Tutorial de captura d'imatge amb objecte ImageCapture]], caldrà adaptar codi.
Pistes:
- Primer caldrà afegir un **Button** addicional per prendre la foto, dins el //layout// de la mateixa ''CameraActivity'' creada al tutorial de la 1a part. Hauràs de fer-ho sobre el XML perquè la vista prèvia gràfica no funciona.
- Caldrà crear un objecte ''ImageCapture'' i vincular-lo adientment a l'objecte ''Camera''. Fixeu-vos que el 2n tutorial utilitza un mètode sobrecarregat amb diferents arguments per poder introduir el ''ImageCapture'':Camera camera = cameraProvider.bindToLifecycle(
(LifecycleOwner)this, cameraSelector,
preview, imageAnalysis, imageCapture);
- ''executor'' té a veure amb el MainLoop (explicat a [[Android Threads]]). Enlloc de fer servir ''getMainExecutor(this);'' que requereix la MinSdk = 28 (molt alta, baixa compatibilitat), pots fer servir ''ContextCompat.getMainExecutor(this);'' que et permetrà compatibilitat amb versions anteriors d'Android.
- Canvia l'accés a arxius del tutorial ''getBatchDirectoryName()'' per ''getFilesDir()'' (de moment guardem els fitxers a l'àrea privada).
- Visualitza la foto capturada en un ImageView de la ''MainActivity''.
\\
===== Codis QR =====
Una aplicació interessant de la càmera pot ser amb generació i escaneig de codis QR.
Aquestes llibreries funcionen (a Febrer de 2023):
* [[https://github.com/androidmads/QRGenerator|Generació de codis QR]].
* Si us surt el QR borrós (blurred) caldrà [[https://stackoverflow.com/questions/4837715/how-to-resize-a-bitmap-in-android|escalar el Bitmap amb Bitmap.createScaledBitmap]].
* [[https://github.com/yuriy-budiyev/code-scanner|Escaneig de codis QR]].
\\