web-dev-qa-db-fra.com

Pouvez-vous demander des autorisations de manière synchrone dans le modèle d'autorisations d'exécution de Android Marshmallow (API 23))?

Disons que vous avez une méthode comme celle-ci:

public boolean saveFile (Url url, String content) {

   // save the file, this can be done a lot of different ways, but
   // basically the point is...

   return save_was_successful;
}

Tout au long de votre application, si vous souhaitez enregistrer un fichier sur un stockage externe, vous faites quelque chose comme ...

if (saveFile(external_storage_url, "this is a test")) {
   // yay, success!
} else { // notify the user something was wrong or handle the error }

Ceci est un exemple simplifié, alors ne vous attardez pas sur le blocage de l'interface utilisateur, la gestion correcte des exceptions, etc. Si vous n'aimez pas l'enregistrement de fichiers, vous pouvez imaginer une fonction getContact() ou getPhoneState() ou peu importe. Le fait est que c'est une opération nécessitant une autorisation qui renvoie une ou plusieurs valeurs et qui est utilisée dans toute l'application.

Dans Android <= Lollipop, si l'utilisateur avait installé et accepté d'accorder Android.permission.WRITE_EXTERNAL_STORAGE Ou autre, tout serait bien.

Mais dans le nouveau Marshmallow (API 23) modèle d'autorisation d'exécution , avant de pouvoir enregistrer le fichier sur un stockage externe, vous êtes censé (1) vérifier si l'autorisation a été accordée. Sinon , éventuellement (2) montrer la justification de la demande (si le le système pense que c'est une bonne idée ) avec un toast ou autre chose et (3) demander à l'utilisateur d'accorder l'autorisation via une boîte de dialogue, puis en gros, attendez un rappel ...

(pour que votre application reste assise, en attendant ...)

(4) Lorsque l'utilisateur répond finalement à la boîte de dialogue, la méthode onRequestPermissionsResult () se déclenche, et votre code maintenant (5) doit passer au crible QUI permission demande qu'ils soient en fait répondant à , si l'utilisateur a dit oui ou non (à ma connaissance, il n'y a aucun moyen de gérer "non" vs "non et ne plus demander"), (6) comprendre ce qu'ils essayaient d'accomplir en premier lieu ce qui a incité tout le processus de demande d'autorisations, afin que le programme puisse enfin (7) aller de l'avant et faire cette chose.

Pour savoir ce que l'utilisateur tentait de faire à l'étape (6), il faut avoir préalablement passé un code (la "réponse de la demande d'autorisation") qui est décrit dans les documents comme identifiant du type de - demande de permission (camera/contact/etc.) mais me semble plus comme un code "spécifiquement, ce que vous essayiez de faire quand vous avez réalisé que vous auriez besoin de demander des permissions", étant donné que le même l'autorisation/le groupe peut être utilisé à plusieurs fins dans votre code, vous devez donc utiliser ce code pour renvoyer l'exécution à l'endroit approprié après avoir obtenu l'autorisation.

Je pourrais être totalement incompréhensible sur la façon dont cela est censé fonctionner - alors s'il vous plaît, faites-moi savoir si je suis loin - mais le point le plus important est que je suis vraiment ne sais pas comment penser de faire tout ce qui précède avec la méthode saveFile() décrite précédemment en raison de la partie asynchrone "attendre que l'utilisateur réponde". Les idées que j'ai envisagées sont assez hackeuses et certainement fausses.

Aujourd'hui Android Developer Podcast a laissé entendre qu'il pourrait y avoir une solution synchrone au coin de la rue, et il a même été question d'un outil magique, en une seule étape de type alt-enter "ajouter une demande d'autorisations" dans Android Studio. Pourtant, comment le processus d'autorisation d'exécution pourrait être poussé dans une saveFile() ou autre chose - je pense à quelque chose comme:

public boolean saveFile(Url url, String content) {
   //   this next line will check for the permission, ask the user
   //   for permission if required, maybe even handle the rationale
   //   situation
   if (!checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        R.string.permission_storage_rationale))
       {
           return false; // or throw an exception or whatever
       } else {

   // try to save the file

   return save_was_successful;

   }
}

Ainsi, la checkPermission() ci-dessus échouerait si l'utilisateur n'en avait pas et refusait également d'accorder l'autorisation. Peut-être que l'on pourrait utiliser une boucle autour de checkPermission() pour essayer de demander jusqu'à 3 fois ou quelque chose, ou mieux serait si une politique saine de ne pas être ennuyeux était gérée par la méthode.

Une telle chose est possible? Souhaitable? Une telle solution bloquerait-elle le thread d'interface utilisateur? D'après le podcast, cela ressemblait à Google - mai avoir une solution comme celle-ci à venir, mais je voulais savoir s'il y avait quelque chose - une classe de commodité, un modèle, quelque chose - - cela n'implique pas que tout le monde doive refactoriser toutes les opérations nécessitant une autorisation, ce qui, je suppose, pourrait devenir assez compliqué.

Désolé pour la longue question, mais je voulais être aussi complète que possible. Je vais retirer ma réponse de l'antenne. Merci!


Mettre à jour: Voici la transcription du podcast mentionné ci-dessus.

Écoutez environ 41:2 . Dans cette discussion:

Transcription grossière:

Tor Norbye (équipe Outils): " Il ne semble donc pas que cela devrait être beaucoup de travail pour les développeurs. Mais je comprends qu'une partie du problème est que ce ne sont pas des appels synchrones, non?. devez- en fait changer la façon dont votre activité est écrite pour avoir un rappel - donc c'est vraiment un peu comme une machine à états où ... pour cet état, vous - "

Poiesz (chef de produit): " Ah- je pensais- il y en avait un - il pourrait y avoir une option pour une réponse synchrone -"

Norbye: " Oh. Ça ferait des choses -"

Poiesz: " Je peux parler avec des gens en interne. Je me souviens d'une discussion sur synchrone - mais nous pouvons le découvrir."

Norbye: " Ouais. En fait, nous devrions probablement le faire dans les outils. Où vous avez une refactorisation facile ..."

Ensuite, il parle d'utiliser des annotations dans les outils pour déterminer quelles API nécessitent des autorisations .. (ce qui ne fonctionne pas si bien que ça à l'heure actuelle) et comment il veut qu'un jour les outils génèrent réellement le code requis s'il trouve un "dangereux" non vérifié. "appel de méthode:

Norbye: "... alors si vous êtes également sur M, il dira:" Hé, vérifiez-vous réellement cette autorisation ou attrapez-vous des exceptions de sécurité? ", Et si vous ne l'êtes pas, nous dirons "vous devez probablement faire quelque chose pour demander la permission ici." Ce que je voudrais cependant, c'est que cela ait une solution rapide où vous pouvez aller 'CHING!' et il insère toutes les bonnes choses pour demander, mais la façon dont les choses se passaient quand je regardais, cela nécessitait de restructurer beaucoup de choses - ajouter des interfaces et des rappels, et changer le flux, et ce que nous ne pouvions pas faire. Mais s'il y a un mode synchrone facile comme une chose temporaire ou une chose permanente, ce serait [génial]. "

41
fattire

En ce qui concerne Marshmallow, je crois comprendre que vous ne pouvez pas.

J'ai dû résoudre le même problème dans mon application. Voici comment je l'ai fait:

  1. Refactoring : Déplacez chaque morceau de code qui dépend de certaines autorisations dans une méthode qui lui est propre.
  2. Plus de refactoring : Identifiez le déclencheur pour chaque méthode (comme démarrer une activité, appuyer sur un contrôle, etc.) et regrouper les méthodes si elles ont le même déclencheur . (Si deux méthodes finissent par avoir le même déclencheur ET nécessitent le même ensemble d'autorisations, envisagez de les fusionner.)
  3. Encore plus de refactoring : Découvrez ce qui dépend des nouvelles méthodes ayant été appelées auparavant, et assurez-vous qu'il est flexible quand ces méthodes sont appelées: dans la méthode elle-même, ou du moins assurez-vous que quoi que vous fassiez ne lève pas d'exceptions si votre méthode n'a pas été appelée auparavant, et qu'elle commence à se comporter comme prévu dès que la méthode est appelée.
  4. Codes de demande : pour chacune de ces méthodes (ou des groupes de celles-ci), définissez une constante entière à utiliser comme code de demande.
  5. Vérification et demande d'autorisations : encapsulez chacune de ces méthodes/groupes de méthodes dans le code suivant:

.

if (ContextCompat.checkSelfPermission(this, Manifest.permission.SOME_PERMISSION) == PackageManager.PERMISSION_GRANTED)
    doStuffThatRequiresPermission();
else
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SOME_PERMISSION}, Const.PERM_REQUEST_DO_STUFF_THAT_REQUIRES_PERMISSION);
  1. Gestion des réponses : Écrivez une implémentation pour onRequestPermissionsResult() dans chaque Activity qui demande des autorisations. Vérifiez si les autorisations demandées ont été accordées et utilisez le code de demande pour déterminer la méthode à appeler.

Sachez que l'API nécessite un Activity pour les demandes d'autorisation d'exécution. Si vous avez des composants non interactifs (comme un Service), jetez un œil à Comment demander des autorisations à un service dans Android Marshmallow for des conseils sur la façon de résoudre ce problème. Fondamentalement, le moyen le plus simple consiste à afficher une notification qui fera apparaître un Activity qui ne fait que présenter la boîte de dialogue des autorisations d'exécution. Ici est la façon dont je abordé cela dans mon application.

10
user149408

Réponse courte: non, il n'y a pas d'opération de synchronisation aujourd'hui. Vous devez vérifier si vous avez la bonne autorisation avant de terminer l'opération ou, comme dernière option, vous pouvez mettre un bloc try/catch pour l'exception de sécurité. Dans le bloc catch, vous pouvez informer l'utilisateur que l'opération a échoué en raison de problèmes d'autorisation. De plus, il y a un autre point: lorsqu'une autorisation est révoquée, l'application ne redémarre pas à partir de l'activité principale, vous devez donc vérifier l'autorisation même dans votre onResume ().

2
greywolf82

Voici comment j'ai résolu le problème de "synchronicité" sans avoir à bloquer explicitement (et à attendre), ou sans nécessiter une activité distincte de "chargeur de démarrage". Je refactorisé l'activité de l'écran de démarrage comme suit :

mise à jour: Un exemple plus complet peut être trouvé ici.


remarque: Parce que l'API requestPermissions() appelle startActivityForResult()

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
}

la logique de création de vue principale a été déplacée de OnCreate() vers OnCreate2() et OnCreate() gère désormais la vérification des autorisations. Si RequestPermissions() doit être appelée, alors la OnRequestPermissionsResult() associée redémarre cette activité (transfert d'une copie du bundle d'origine).


[Activity(Label = "MarshmellowActivated",
    MainLauncher = true,      
    Theme = "@style/Theme.Transparent",
    Icon = "@drawable/icon"        
    ////////////////////////////////////////////////////////////////
    // THIS PREVENTS OnRequestPermissionsResult() from being called
    //NoHistory = true
)]
public class MarshmellowActivated : Activity
{
    private const int Android_PERMISSION_REQUEST_CODE__SDCARD = 112;
    private Bundle _savedInstanceState;

    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
    {
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode)
        {
            case Android_PERMISSION_REQUEST_CODE__SDCARD:
                if (grantResults.Length > 0 && grantResults[0] == Permission.Granted)
                {       
                    Intent restartThisActivityIntent = new Intent(this, this.GetType());
                    if (_savedInstanceState != null)
                    {
                        // *ref1: Forward bundle from one intent to another
                        restartThisActivityIntent.PutExtras(_savedInstanceState);
                    }
                    StartActivity(restartThisActivityIntent);
                }
                else
                {                   
                    throw new Exception("SD Card Write Access: Denied.");
                }
                break;
        }
    }

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Android v6 requires explicit permission granting from user at runtime for extra sweet security goodness
        Permission extStoragePerm = ApplicationContext.CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);

        //if(extStoragePerm == Permission.Denied)
        if (extStoragePerm != Permission.Granted)
        {
            _savedInstanceState = savedInstanceState;
            // **calls startActivityForResult()**
            RequestPermissions(new[] { Android.Manifest.Permission.WriteExternalStorage }, Android_PERMISSION_REQUEST_CODE__SDCARD);
        }
        else
        {
            OnCreate2(savedInstanceState);
        }
    }

    private void OnCreate2(Bundle savedInstanceState)
    {
        //...
    }
}

ref1: Transférer le bundle d'une intention à une autre

note: Cela peut être refactorisé pour gérer plus d'autorisations en général. Il gère actuellement uniquement la permission d'écriture de la carte SD, qui devrait transmettre la logique pertinente avec suffisamment de clarté.

1
samis

Je déteste donc répondre à ma propre question spécifiquement en ce qui concerne l'autorisation Android.permission.WRITE_EXTERNAL_STORAGE Utilisée dans mon exemple, mais que diable.

Pour lire et/ou écrire un fichier, il existe en fait un moyen d'éviter complètement d'avoir à demander et ensuite vérifier l'autorisation et ainsi contourner tout le flux que j'ai décrit ci-dessus. De cette façon, la méthode saveFile (Url url, String content) que j'ai donnée comme exemple pourrait continuer à fonctionner de manière synchrone.

La solution, qui, je crois, fonctionne dans l'API 19+, élimine le besoin de l'autorisation WRITE_EXTERNAL_STORAGE En ayant un DocumentsProvider agissant comme un "intermédiaire" pour demander essentiellement à l'utilisateur au nom de votre application "s'il vous plaît sélectionnez le fichier spécifique dans lequel écrire "(c'est-à-dire un" sélecteur de fichiers "), puis une fois que l'utilisateur a choisi un fichier (ou a tapé un nouveau nom de fichier), l'application est désormais autorisée par magie à le faire pour cet Uri, car l'utilisateur l'a expressément accordée.

Aucune autorisation "officielle" WRITE_EXTERNAL_STORAGE Requise.

Ce type de permission d'emprunt fait partie du Storage Access Framework , et une discussion à ce sujet a été faite au Big Android BBQ par Ian Lake. Voici une vidéo appelé Oubliez l'autorisation de stockage: Alternatives de partage et de collaboration qui passe en revue les bases et comment vous l'utilisez spécifiquement pour contourner le Exigence d'autorisation WRITE_EXTERNAL_STORAGE Entièrement.

Cela ne résout pas complètement le problème des autorisations de synchronisation/async dans tous les cas, mais pour tout type de document externe, ou même celui proposé par un fournisseur (tel que gDrive, Box.net, Dropbox, etc.), cela peut être une solution à découvrir.

1
fattire

Vous pouvez ajouter une méthode d'aide au blocage comme celle-ci:

@TargetApi(23) 
public static void checkForPermissionsMAndAboveBlocking(Activity act) {
    Log.i(Prefs.TAG, "checkForPermissions() called");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        // Here, thisActivity is the current activity
        if (act.checkSelfPermission(
                Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {


            // No explanation needed, we can request the permission.
            act.requestPermissions(
                    new String[]{
                          Manifest.permission.WRITE_EXTERNAL_STORAGE
                    },
                    0);

            while (true) {
                if (act.checkSelfPermission(
                        Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        == PackageManager.PERMISSION_GRANTED) {

                    Log.i(Prefs.TAG, "Got permissions, exiting block loop");
                    break;
                }
                Log.i(Prefs.TAG, "Sleeping, waiting for permissions");
                try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            }

        }
        // permission already granted
        else {
            Log.i(Prefs.TAG, "permission already granted");
        }
    }
    else {
        Log.i(Prefs.TAG, "Below M, permissions not via code");
    }

}
0
Eli