0

Je joue un son d'alarme dans mon application. Sur la plupart des appareils, cela fonctionne parfaitement. Certains utilisateurs ont décrit qu'aucun son ne joue. Cela semble affecter principalement les appareils Samsung et Huawei. Ceci est un exemple minimal de l'endroit où les choses échouent:READ_EXTERNAL_STORAGE autorisation nécessaire pour ouvrir le contenu: // media/external/[...] dans MediaPlayer?

alarmUri = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); 
try { 
      if(!mediaPlayer.isPlaying()) { 
       mediaPlayer.setDataSource(this, alarmUri); 
       AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); 
       mediaPlayer.setAudioStreamType(audioManager.STREAM_ALARM); 
       mediaPlayer.setLooping(true); 
       mediaPlayer.prepare(); 
       mediaPlayer.start(); 
      } 
     } catch(Exception e) {     
       Log.d(logtag,"Exception: " + e); 
     } 

Pour les utilisateurs concernés, l'exception suivante est levée:

Exception java.io.IOException: setDataSource échoué .: status = 0x80000000

Cela semble se produire uniquement lorsque alarmURI ressemble à ceci: content://media/external/audio/media/11, c'est-à-dire qu'il réside sur un magasin externe. Je ne l'ai jamais vu se produire quand alarmURI regarde ceci ceci content://media/internal/audio/media/40.

Ai-je besoin de l'autorisation READ_EXTERNAL_STORAGE pour jouer le son, si l'Uri I contient la partie /external/? Ou est-ce que je manque quelque chose d'autre? Normalement, je m'attendrais à une exception SecurityException, si l'autorisation était le problème.

/e: Je pourrais reproduire le bug sur mon appareil. Voici un rapport d'erreur plus détaillé:

Exception java.lang.SecurityException: Permission refusée: lecture com.android.providers.media.MediaProvider uri contenu: // media/externe/audio/médias de pid = 27158, uid = 10177 nécessite android.permission.READ_EXTERNAL_STORAGE ou grantUriPermission()

Répondre

1

Normalement, je me attends à un SecurityException

Vous n'obtenez pas SecurityException, car le code échoue en accédant aux autorisations du système de fichiers Linux (au niveau du système d'exploitation), et non aux autorisations d'infrastructure Android. Il n'y a pas de garanties strictes à propos de RuntimeException s que vous recevez de l'API (comme SequrityException), c'est pourquoi ils sont appelés "exceptions non contrôlées" en premier lieu.

Ai-je besoin READ_EXTERNAL_STORAGE la permission pour jouer le son, si l'Uri j'utilise contient la partie /external/? N'utilisez jamais le contenu d'un URI externe pour prendre des décisions dans votre code. Traitez-les simplement comme des cordes opaques. Cela ne semble se produire que lorsque alarmURI ressemble à ceci ..., c'est-à-dire qu'il réside sur un magasin externe. Je n'ai jamais vu cela se produire quand alarmURI regarde ceci ceci ...

Vous avez raison de poster l'Uris pour le dépannage, mais il est également utile de savoir d'où vient l'Uri. Les versions modernes d'Android utilisent dynamic Uri permission model. C'est-à-dire: votre niveau d'accès à Uri peut différer, selon l'origine d'Uri et si l'appelant a ajouté FLAG_GRANT_READ_URI_PERMISSION à Intent (ou Context#grantUriPermission pour le même résultat), et si vous avez appelé Context#takePersistableUriPermission à la réception de l'Intention avec Uri.

Oui, vous avez peut-être besoin de la permission READ_EXTERNAL_STORAGE. Android utilise FUSE ou une API similaire spécifique au fournisseur pour ajuster les autorisations visibles du système de stockage externe en fonction de la permission de l'application appelante: lorsque votre application a l'autorisation, les fichiers externes lui semblent lisibles (le fichier # isReadable renvoie true). ce n'est pas le cas, les fichiers externes lui semblent inaccessibles (le fichier # isReadable renvoie false). Bien sûr, cela ne devrait être pertinent pour les fichiers, mais que faire si l'appelant vous donne un file:// Uri? En outre, ContentProvider derrière l'Uri peut vérifier si vous avez la permission READ_EXTERNAL_STORAGE avant de vous donner le descripteur de fichier (c'est idiot, mais certainement possible).

En outre, c'est une bonne idée d'ouvrir l'Uri vous-même et de fournir le descripteur de flux/fichier au lecteur à la place d'Uri. La plupart des contrôles de sécurité Linux * se produisent lorsque vous ouvrez un descripteur de fichier. Afin d'obtenir un comportement prévisible, vous devez ouvrir l'Uri dans votre propre code et gérer autant d'erreurs que possible, y compris en interceptant RuntimeException à partir de ContentProvider distant.

Pourquoi le problème se pose-t-il? Il pourrait y avoir plusieurs raisons, mais il est probablement l'un de ceux-ci:

  1. Le MediaStorage ContentProvider sur les appareils coupables est tout simplement bogué (les fabricants peuvent et souvent remplacer ContentProviders du système avec les versions buggy personnalisé)
  2. La mise en œuvre MediaPlayer sur dispositifs coupables est bogué
  3. les autorisations SELinux sur les périphériques coupables sont défectueux (limiter l'accès aux fichiers trop)
  4. votre code reçoit Uris, les stocke quelque part, mais ne remet pas takePersistableUriPermission, de sorte que votre accès est invalidée après application redémarrage .

Il est difficile de diagnostiquer le problème exact sans trace de pile d'exception. Quoi qu'il en soit, si les suggestions ci-dessus ne vous aident pas, il y a peu à faire en dehors de l'utilisation de différentes API pour lire des fichiers audio et/ou forcer l'utilisateur à choisir un son d'alarme sans utiliser Android MediaProvider.


*sauf quelques chèques SELinux, mais vous ne pouvez pas vraiment faire quoi que ce soit au sujet de ces

+1

Merci pour votre réponse. C'était vraiment perspicace. J'ai commencé à lire sur tous les sujets que vous avez mentionnés. En outre, j'ai réussi à reproduire l'erreur et a également été en mesure de le résoudre. Demander l'autorisation READ_EXTERNAL_STORAGE le résout. Cependant, j'aimerais utiliser le moins d'autorisations possible. Par conséquent, j'ai essayé de chercher comment implémenter les permissions dynamiques de l'uri, mais je n'ai pas été capable de le comprendre. Il y a deux façons de recevoir l'uri: – mc51

+0

1. RingtoneManager.getActualDefaultRingtoneUri() et 2. Intention intention = new Intent (RingtoneManager.ACTION_RINGTONE_PICKER); Les documents de RingtoneManager ne mentionnent pas les autorisations dynamiques. Donc, je ne sais pas vraiment comment commencer ... – mc51

+0

@ mc51 Aucun document ne mentionne explicitement les permissions dynamiques (à part les docs des méthodes pertinentes elles-mêmes), car les autorisations dynamiques n'existaient pas lorsque le système ContentProvider a été créé. Ils ont été introduits dans les versions ultérieures d'Android. Lorsque vous utilisez le sélecteur Intent, appelez 'takePersistableUriPermission' après avoir reçu un Uri. Pas sûr, comment faire face à 'getActualDefaultRingtoneUri', vous voudrez peut-être regarder, comment diverses applications open source l'utilisent. – user1643723