Accéder aux ressources média

Les « contents providers »

Le système Android fournit un système permettant le partage d’information entre différentes applications. Cette fonctionnalité permet, par exemple, à plusieurs applications d’effectuer du travail différent en utilisant les mêmes données. Android fournit cette fonctionnalité via le service de « content provider ». On peut accéder au « content provider » à partir de n’importe quel contexte Android (donc de n’importe quelle Activité) en utilisant la fonction « getContentResolver() ».

Les « contents providers » s’utilisent un peu comme une base de données. On peut accéder aux données (query), ajouter des données (insert), supprimer des données (delete) ou modifier des données (update). Voici des exemples:

    String[] mProjection =
        {
            UserDictionary.Words._ID,
            UserDictionary.Words.WORD,
            UserDictionary.Words.LOCALE
        };

    String selectionClause = null;

    String sortOrder = null;

    String[] selectionArgs = {""};

    public void test() {
        Cursor cursor = getContentResolver().query(UserDictionary.Words.CONTENT_URI,
                    projection, selectionClause, selectionArgs, sortOrder);
        boolean contientProchain = cursor.moveToFirst();
        while(contientProchain){
            // Aller chercher les éléments avec cursor.get...
            contientProchain = cursor.moveToNext();
        }
    }
        ContentValues nouvellesValeurs = new ContentValues();
        nouvellesValeurs.put("cle1", valeur1)
        nouvellesValeurs.put("cle2", valeur2)
        nouvellesValeurs.put(UserDictionary.Words.APP_ID, "exemple.prog");
        nouvellesValeurs.put(UserDictionary.Words.LOCALE, "fr_CA");
        nouvellesValeurs.put(UserDictionary.Words.WORD, "mot");
        
        Uri uri = getContentResolver().insert(
            UserDictionary.Words.CONTENT_URI,
            nouvellesValeurs
        );
        ContentValues updateValeurs = new ContentValues();

        String selectionClause = UserDictionary.Words.LOCALE +  " LIKE ?";
        String[] selectionArgs = {"fr_%"};
        
        updateValeurs.putNull(UserDictionary.Words.LOCALE);
        
        int nombreEnregistrements = getContentResolver().update(
                UserDictionary.Words.CONTENT_URI, updateValeurs,
                selectionClause, selectionArgs);
        String selectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
        String[] selectionArgs = {"prog"};
        
        int nombreEnregistrements = getContentResolver().delete(
            UserDictionary.Words.CONTENT_URI, selectionClause, selectionArgs);

L’utilisation de ces fonctionnalités ne font pas partit du cours. Si vous voulez en savoir plus, je vous recommande d’aller lire la documentation officielle: https://developer.android.com/guide/topics/providers/content-providers

Le MediaStore

Android utilise la mécanique de « content provider » afin de partager certaines informations système. C’est le cas de la liste des informations des fichiers médias contenus dans le système. Les informations qui nous permettront d’utiliser le « content provider » afin d’accéder aux informations de fichiers média se trouvent dans le MediaStore.

Le MediaStore permet d’accéder aux fichiers contenus dans les répertoires de média de l’appareil Android. Voici les types de fichiers qui peuvent être accédés par le MediaStore:

    • Les fichiers audio (avec MediaStore.Audio)
    • Les fichiers vidéo (avec MediaStore.Video)
    • Les fichiers images (avec MediaStore.Images)
    • Les fichiers téléchargés (avec MediaStore.Downloads)

En plus des URI de fichiers permettant d’accéder aux fichiers (voir section sur les URI ci-après), le « content provider » et le MediaStore permettent d’accéder à des informations sur les médias comme l’aperçu d’un vidéo (thumbnail), le nom de l’artiste, le nom de l’album, le titre d’un fichier audio ou bien les informations EXIF d’une image (description, ISO, exposition, etc.)

À propos des URI de média

Afin d’éviter des problématiques de sécurité et de respect de la vie privée, l’accès direct aux fichiers du système est réservé aux applications ayant fait les demandes d’autorisations nécessaires. Pour créer un lecteur de fichiers médias, il n’est pas nécessaire de faire la demande d’accès aux fichiers du système. Par contre, il est important d’éviter qu’une application malveillante puisse avoir accès aux noms de fichier qui pourrait contenir des informations confidentielles.

Android permet donc l’accès à plusieurs ressources dans le système, dont les fichiers médias, en utilisant un système d’adressage protégeant contre une fuite d’information de métadonnées. Ce type d’adressage se nomme URI. Un URI est donc une chaîne de caractères qui représente, de manière unique, une ressource du système et ne contenant aucune information potentiellement sensible de l’utilisateur.

Accéder aux informations du MediaStore

En premier lieu, l’accès aux informations de médias nécessite les permissions « READ_MEDIA_IMAGES », « READ_MEDIA_VIDEO » et « READ_MEDIA_AUDIO » afin d’accéder respectivement aux types de médias images, vidéos et audios.

En plus de ces permissions, jusqu’à la version 32 de l’API d’Android, il est nécessaire de demander la permission « READ_EXTERNAL_STORAGE » afin d’accéder aux informations sur un média. Voici un exemple à inclure dans le fichier Manifest du projet Android:

<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />

Également, il est nécessaire de faire un « requestPermission » afin de demander l’autorisation à l’utilisateur d’utiliser les fichiers média. Également, il y a eu certaines modifications à l’API 33 concernant les fichiers multimédia. Voici un exemple gérant tous les types de média. Cet exemple est à adapter selon le type de média nécessaire dans le programme.

if (Build.VERSION.SDK_INT < 23) {
    // Les permissions sont directement gérée dans le AndroidManifest.xml
    // Lancer le processus d'accès aux fichiers média.
} else if (Build.VERSION.SDK_INT <= 32) {
    requestPermissions(new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
} else {
    requestPermissions(new String[]{Manifest.permission.READ_MEDIA_IMAGES,
            Manifest.permission.READ_MEDIA_AUDIO,
            Manifest.permission.READ_MEDIA_VIDEO}, 2);
}

L’événement « onRequestPermissionsResult » pourrait ressembler à ceci (encore une fois, à adapter selon le type de média utilisable dans l’application):

@Override
public void onRequestPermissionsResult(int aCode,
                                       String aPermission[],
                                       int[] aAccepte) {
    super.onRequestPermissionsResult(aCode, aPermission,
            aAccepte);
    if (aCode == 1 && aAccepte[0] == PackageManager.PERMISSION_GRANTED) {
        // Les permissions d'accès aux fichiers média sont acceptées
        // Lancer le processus d'accès aux fichiers média.
    } else if (aCode == 2) {
        if (aAccepte[0] == PackageManager.PERMISSION_GRANTED) {
            // La permission d'accès aux fichiers image est acceptée
            // Lancer le processus d'accès aux fichiers image.
        } else {
            // La permission d'accès aux fichiers image est refusée par l'utilisateur
        }
        if (aAccepte[1] == PackageManager.PERMISSION_GRANTED) {
            // La permission d'accès aux fichiers audio est acceptée
            // Lancer le processus d'accès aux fichiers audio.
        } else {
            // La permission d'accès aux fichiers audio est refusée par l'utilisateur
        }
        if (aAccepte[2] == PackageManager.PERMISSION_GRANTED) {
            // La permission d'accès aux fichiers vidéo est acceptée
            // Lancer le processus d'accès aux fichiers video.
        } else {
            // La permission d'accès aux fichiers video est refusée par l'utilisateur
        }
    } else {
        // Les permissions ne sont pas acceptées par l'utilisateur.
    }
}

Maintenant, puisque l’accès aux informations sur les médias se fait avec le « ContentResolver », voici un petit rappel des arguments nécessaires à la fonction « getContentResolver().query() ».

query(Uri,projection,selection,selectionArgs,sortOrder)

Le MediaStore nous permet d’obtenir les URI qui représentent les bases de données de médias et également d’obtenir les projections qui représentent les informations que nous voulons avoir accès (titre, auteurs, album, etc.). Les autres arguments (selection, selectionArgs et sortOrder) permettent de faire des tris et de l’ordonnancement des valeurs retournés et s’utilisent de la même manière que pour n’importe quelle utilisation du « content provider » (voir documentation).

L’Uri peut prendre 2 valeurs:

    • EXTERNAL_CONTENT_URI: Pour l’utilisation du répertoire de l’utilisateur (c’est généralement ce que l’on veut)
    • INTERNAL_CONTENT_URI: Pour l’utilisation du répertoire interne de l’application

En général, les lecteurs de médias ont comme mandat de lire les médias de l’utilisateur (et non interne à l’application en cours). Le « EXTERNAL_CONTENT_URI » devrait donc être utilisé.

Pour avoir accès à ces 2 constantes, on utilise la classe « Media » dans la sous-classe correspondant au type de média recherché dans MediaStore. Par exemple, pour les vidéos, on peut accéder à la constante « EXTERNAL_CONTENT_URI » comme ceci:

MediaStore.Video.Media.EXTERNAL_CONTENT_URI

Il est à noter qu’à partir d’Android 10 (API 29 ou code Q), la technique pour accéder à la constante a été modifiée. Il faut maintenant utiliser la méthode du MediaStore « getContentUri » avec la constante « VOLUME_EXTERNAL » pour les médias externes (équivalent à « EXTERNAL_CONTENT_URI ») et « VOLUME_INTERNAL » pour les médias internes à l’application (équivalent à « INTERNAL_CONTENT_URI »). Par exemple, pour les vidéos, on peut accéder à la constante « VOLUME_EXTERNAL » de la manière suivante:

MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);

La projection correspond à un tableau (« Array ») de colonne à utiliser. Voici quelques exemples de colonnes pouvant être utilisés:

    • _ID: L’identificateur du média (permet d’avoir accès au URI du média)
      • ex: MediaStore.Video.VideoColumns._ID
    • ARTIST: Le nom de l’artiste ayant créé le média:
      • ex: MediaStore.Audio.ArtistColumns.ARTIST
    • TITLE: Le titre du média:
      • ex: MediaStore.Audio.AudioColumns.TITLE
    • DESCRIPTION: une description du média:
      • ex: MediaStore.Video.VideoColumns.DESCRIPTION
    • GENRE: Le style musical du fichier audio:
      • ex: MediaStore.Audio.AudioColumns.GENRE
    • etc.

Voici un exemple d’une méthode qui affiche dans le Logcat une liste de tous les fichiers audio avec l’artiste et le titre:

final String[] MEDIA_COLUMN = {
        MediaStore.Audio.AudioColumns._ID,
        MediaStore.Audio.ArtistColumns.ARTIST,
        MediaStore.Audio.AudioColumns.TITLE
};

private void showAudio(){
    int lCount;
    boolean lContientProchain;
    Cursor lCursor;
    Uri lCollection;
    if (Build.VERSION.SDK_INT >= 29) {
        lCollection = MediaStore.Audio.Media.getContentUri(
                MediaStore.VOLUME_EXTERNAL);
    } else {
        lCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    }
    lCursor = getContentResolver().query(
            lCollection, MEDIA_COLUMN, null, null, null
    );
    lCount = 1;
    lContientProchain = lCursor.moveToFirst();
    while(lContientProchain) {
        Log.d("Mon_Application", "Audio " + lCount +
                " | Artiste: " + lCursor.getString(1) +
                " | Titre: " + lCursor.getString(2) +
                " | Identificateur: " + lCursor.getString(0));
        lContientProchain = lCursor.moveToNext();
        lCount = lCount + 1;
    }
}

Prendre en note que les index de « getString » dans l’exemple ci-haut correspond à l’index de la colonne en question dans le tableau « MEDIA_COLUMN » (soit 0 pour _ID, 1 pour artiste et 2 pour titre).

Accéder au URI d’un média

Lorsque la lecture d’un média sera nécessaire, il sera important d’utiliser le URI du média. Pour accéder à l’URI d’un média à partir d’un « query » du « ContentResolver », on utilise la méthode « ContentUris.withAppendedId » avec le « _ID » du média. Voici l’exemple précédent qui affiche également le URI du média:

final String[] MEDIA_COLUMN = {
        MediaStore.Audio.AudioColumns._ID,
        MediaStore.Audio.ArtistColumns.ARTIST,
        MediaStore.Audio.AudioColumns.TITLE
};

private void showAudio(){
    int lCount;
    boolean lContientProchain;
    Cursor lCursor;
    Uri lCollection;
    if (Build.VERSION.SDK_INT >= 29) {
        lCollection = MediaStore.Audio.Media.getContentUri(
                MediaStore.VOLUME_EXTERNAL);
    } else {
        lCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    }
    lCursor = getContentResolver().query(
            lCollection, MEDIA_COLUMN, null, null, null
    );
    lCount = 1;
    lContientProchain = lCursor.moveToFirst();
    while(lContientProchain) {
        Uri lMediaURI = ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                lCursor.getInt(0));
        Log.d("Mon_Application", "Audio " + lCount +
                " | Artiste: " + lCursor.getString(1) +
                " | Titre: " + lCursor.getString(2) +
                " | URI: " + lMediaURI);
        lContientProchain = lCursor.moveToNext();
        lCount = lCount + 1;
    }
}

Accéder à l’image aperçu d’un fichier vidéo

Il est important de comprendre que la mécanique utilisée pour accéder aux images aperçues de média vidéo (« Thumbnails ») dans les systèmes Android a été modifiée lors de la sortie de l’API 29 (Android 10). Lors des versions précédentes d’Android, il fallait créer l’aperçu avec la classe « ThumbnailUtils » ainsi qu’un nom de fichier. Depuis l’API 29, il est devenu nécessaire d’utiliser la méthode « loadThumbnail » du « ContentResolver » ainsi que l’URI du média. Voici donc un exemple permettant d’afficher un aperçu de vidéo dans un « ImageView », autant les versions précédentes de l’API 29 que l’API 29 et les suivantes:

final String[] MEDIA_COLUMN = {
        MediaStore.Video.VideoColumns._ID,
        MediaStore.Video.VideoColumns.DATA
};

private void chargeApercu(){
    boolean lContientPremier;
    Cursor lCursor;
    Uri lCollection;
    if (Build.VERSION.SDK_INT >= 29) {
        lCollection = MediaStore.Video.Media.getContentUri(
                MediaStore.VOLUME_EXTERNAL);
    } else {
        lCollection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
    }
    lCursor = getContentResolver().query(
            lCollection, MEDIA_COLUMN, null, null, null
    );
    lContientPremier = lCursor.moveToFirst();
    if(lContientPremier) {
        Uri lMediaURI = ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                lCursor.getInt(0));
        String lNomFichier = lCursor.getString(1);
        afficherApercu(lMediaURI, lNomFichier);
    } else {
        Toast lToast = Toast.makeText(this,
                "L'appareil ne semble contenir aucun vidéo",
                Toast.LENGTH_SHORT);
        lToast.show();
    }
}

private void afficherApercu(Uri aUri, String aNomFichier) {
    try{
        Bitmap bitmap;
        if (Build.VERSION.SDK_INT >= 29) {
            bitmap = getContentResolver().loadThumbnail(
                    aUri, new Size(300, 300), null);
        } else {
            bitmap = ThumbnailUtils.createVideoThumbnail(
                    aNomFichier,  0);
            bitmap = ThumbnailUtils.extractThumbnail(bitmap, 300, 300);
        }
        ImageView imageVue = findViewById(R.id.image_vue);
        imageVue.setImageBitmap(bitmap);
    }catch (IOException exception) {
        Toast lToast = Toast.makeText(this,
                "Ne peut créer l'aperçu", Toast.LENGTH_SHORT);
        lToast.show();
    }
}

Envoyer un Uri d’une activité à une autre

Premièrement, il est important de constater qu’il n’existe pas de méthode « Intent.putExtra(String name, Uri value) » ni de méthode « Intent.getUriExtra(String name) ». Il en résulte donc qu’il n’est pas possible d’envoyer directement un Uri d’une activité vers une autre avec le système d’extra de la classe « Intent ». Vous avez donc deux possibilités. Utiliser la méthode « Intent.setData(Uri) » avec « Intent.getData() » ou utiliser un String extra.

La première possibilité (et celle que vous devriez privilégier lorsque possible), est d’utiliser « Intent.setData(Uri) ». Voici un exemple:

private void lancerSecondeActivite(Uri aUri) {
    Intent lIntent = new Intent(this, SecondeActivite.class);
    lIntent.setData(aUri);
    startActivity(lIntent);
}

Pour aller chercher l’Uri dans la nouvelle activité, on utiliser « Intent.getData() ». Comme ceci:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_seconde_activite);
    Intent lIntent = getIntent();
    Uri lUri = lIntent.getData();
    Log.e("Le Uri: ", lUri.toString());
}

Cette mécanique fonctionne bien, pourvu que vous n’ayez qu’un seul Uri à transférer vers la seconde activité. Puisque les « setData » ne sont pas nommés (contrairement au « putExtra »), il serait impossible de transmettre plus d’un « data » sans écraser les « datas » précédents.

Ainsi, si vous avez plus qu’un Uri à transférer vers une nouvelle activité, vous pouvez le transférer en String. Un Uri n’est rien d’autre qu’une classe qui encapsule un String. Pour transférer l’Uri en String, vous pouvez simplement utiliser « toString » et pour créer un Uri à partir d’un String, utiliser « Uri.parse ». Voici un exemple pour l’envoi de l’Uri:

private void lancerSecondeActivite(Uri aUri) {
    Intent lIntent = new Intent(this, SecondeActivite.class);
    lIntent.putExtra("LeUri", aUri.toString());
    startActivity(lIntent);
}

Et voici un exemple pour la réception de l’Uri:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_seconde_activite);
    Intent lIntent = getIntent();
    String lUriString = lIntent.getStringExtra("LeUri");
    Uri lUri = Uri.parse(lUriString);
    Log.e("Le Uri: ", lUri.toString());
}

Retour


Auteur: Louis Marchand
Creative Commons License
Sauf pour les sections spécifiées autrement, ce travail est sous licence Creative Commons Attribution 4.0 International.