Per multimèdia s'entenen els diferents mitjans que permeten a la persona humana percebre l'entorn. Els més destacats son la visió i la oïda, però també podrien incloure's el tacte, el gust i l'olfacte. I també la orientació, sabem si estem cap per avall mercès als canals semicirculars de la oïda (no és ni una brúixola ni un GPS però algo és algo).
Tot el relacionat amb sensors és susceptible d'entrar en aquesta categoria, però per raons d'extensió ens centrarem en:
Apunts de referència:
Els sistemes operatius cada cop es tornen més exigents amb la seguretat, i per tant més restrictius. Això fa que totes les operacions tinguin força passes prèvies abans de poder-se realitzar, per impedir que aplicacions malicioses vulnerin els nostres drets de privacitat de les dades.
Podeu llegir més sobre el model de seguretat dels dispostius mòbils.
Els accessos als arxius compartits requereixen diversos nivells de permisos:
'AndroidManifest.xml que declaren abans d'instal·lar els recursos del dispositiu als que es voldrà accedir.
Moltes gestions en la nostra app es poden delegar en una aplicació externa, com accedir a la galeria multimèdia, o permetre que l'aplicació de fotos del sistema operatiu faci la foto per nosaltres.
Aquesta delegació es fa amb un Intent, similarment a quan canviem d'Activity.
Inicialment aquesta operació era més senzilla i implicava una simple crida startActivityForResult() i una callback onActivityResult(), però amb el reforçament de les directives de seguretat s'ha passat a emprar la anomenada Activity Result API que afegeix algunes sofisticacions (i complicacions) més.
Necessitarem algunes fotos dins el dispositiu de l'emulador quan treballem amb Android Studio. Ves a l'aplicació de fotos i fes algunes perquè el carret de la càmera tingui alguns exemples.
Per simular la captura de fotos podem ajustar les càmeres frontal i interna de l'emulador amb diverses opcions:
Accés a galeria multimèdia
Mireu el codi "Launch an Activity for result" de la documentació oficial d'Android. En realitat és un codi molt curt, tot i que conté implícit el Intent i d'altres recursos.
Caldrà afegir l'acció de la callback on diu «Handle the returned Uri», i aplicar la imatge que ens arriba en forma de Uri al ImageView que tinguem.
Captura de imatge en miniatura amb la càmera (versió thumbnail)
El mateix mecanisme que heu emprat per demanar la càrrega d'imatge des de la galeria multimèdia es pot aplicar per a capturar imatges en versió thumbnail.
Enlloc del contracte ActivityResultContracts.GetContent() que hem emprat, investiga com aplicar ActivityResultContracts.TakePicturePreview().
Afegeix un botó a l'aplicació anterior amb el què capturis el thumbnail directament de l'aplicació càmera del sistema operatiu.
Demanar una miniatura (thumbnail) a una altra Activity és senzill perquè no son gaires dades que s'han de passar d'una a l'altra a través del Intent. En canvi, demanar que facin una foto d'alta resolució o un vídeo ja és més complicat, ja que el Intent no permet transferir tanta informació.
Un mètode força segur per a fer la foto en alta resolució és obrir els permisos temporalment d'un arxiu privat de l'aplicació que demana la foto perquè l'App Càmera externa la pugui escriure. Això es fa a través del FileProvider.
Per a realitzar la foto full-size i guardar-la a l'espai privat caldrà:
res/xml/file_paths.xml on s'indiquen carpetes i arxius compartibles.FileProvider al AndroidManifest.xml.'Activity transformar el File estàndard a Uri (recurs compartible) amb el FileProvider.Activity Result API (similar al fet fins ara per la galeria i el thumbnail).<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="my_images" path="fotos" /> <!-- si volguessim una carpeta de l'espai compartit seria external-files-path --> </paths>
<application ...> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data> </provider> </application>
Preparació del recurs compartit i inici de l'App Camera:
val photoButton = findViewById<Button>(R.id.photoButton) photoButton.setOnClickListener { // Assegurem que existeix la carpeta de fotos (igual que al FileProvider) val carpeta = File(filesDir.toString(),"fotos") carpeta.mkdirs() // Preparem l'arxiu pq hi pugui escriure l'App Camera val file = File(filesDir.toString(),"fotos/tmpimage.jpg") fotoUri = FileProvider.getUriForFile(this, "com.enric.galleryk2.fileprovider",file) // engeguem l'App Camera takeFullPic.launch(fotoUri) }
I la callback implementada amb la Results Activity API:
val takeFullPic = registerForActivityResult( ActivityResultContracts.TakePicture()) { success -> if (success) { // netejar imatge i pintar nova imageView.setImageDrawable(null) imageView.setImageURI(fotoUri) } else { // notifiquem error Toast.makeText(this, "Error al guardar la foto", Toast.LENGTH_SHORT).show() } }
L'exemple anterior s'ha fet compartint un arxiu a la carpeta «fotos» de l'espai privat de l'aplicació, que està a /data/data/{AppId}/files.
L'àrea compartida d'arxius està a /storage/emulated/0 on trobarem carpetes conegudes com:
On {AppId} és l'identificador d'aplicació corresponent al package, per exemple com.ieti.multimedia
Els directoris on s'emmagatzema la info son diferents:
| Funció d'accés | Directori | XML | |
|---|---|---|---|
| Arxius privats de l'app | filesDir | /data/data/{AppId}/files | files-path |
| Arxius compartits de l'app | getExternalFilesDir( null ) | /storage/emulated/0/Android/data/{AppId}/files | external-files-path |
| Arxius compartits de fotos | Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES ) | /storage/emulated/0/Pictures/ | external-path |
Si volem fer servir l'àrea compartida de l'aplicació, caldrà fer algunes modificacions.
Al file_paths.xml caldrà emprar external-files-path enlloc de files-path:
<external-files-path name="my_images" path="fotos" />
A MainActivity.kt caldrà emprar getExternalFilesDir(…) enlloc de filesDir. Amb el paràmetre null ens escriurà en la carpeta compartida d'aplicacions.
val carpeta = File(getExternalFilesDir(null).toString(),"fotos") ... val file = File(getExternalFilesDir(null).toString(),"fotos/tmpimage.jpg")
Utilitzeu el Device Explorer del Android Studio per visualitzar les carpetes i arxius.
Si volem fer servir l'àrea compartida de l'aplicació, caldrà fer algunes modificacions.
Al file_paths.xml caldrà emprar external-path enlloc de files-path o external-files-path que hem emprat anteriorment:
<external-path name="my_images" path="Pictures" />
A MainActivity.kt caldrà emprar Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES)) :
val carpeta = File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES).toString(),"fotos") ... val file = File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES).toString(),"fotos/tmpimage.jpg")
Captura de fotos full-size
Implementa la captura de fotos full-size en l'app de la galeria i el thumb, afegint un nou botó per la captura actual.
Exercici MyGallery
Implementa una app de captura de fotos i conserva tots els arxius amb un nom que eviti col·lisions, com afegir un timestamp.
Visualització les fotos que anem prenent en l'àrea compartida de l'aplicació i visualitza-les en una nova Activity. Segueix les instruccions a l'article Android RecyclerView per visualitzar les fotos amb una preview en format grid.
Android Galeria per accedir als arxius multimèdia a través de l'Activity Result API.
Android Camera per utilitzar la càmera, fer fotos i enregistrar-les. Cal tenir en compte que hi ha 2 maneres principals de tractar aquest tema:
Android Sensors per utilitzar l'acceleròmetre.
Android Speech per realitzar síntesi i reconeixement de veu.