web-dev-qa-db-fra.com

Comment vérifier si l'autorisation "Android.permission.PACKAGE_USAGE_STATS" est accordée?

Contexte

J'essaie d'obtenir des statistiques lancées par l'application, et sur Lollipop, c'est possible en utilisant la classe UsageStatsManager , en tant que telle ( message d'origine ( ici ):

manifeste:

<uses-permission
    Android:name="Android.permission.PACKAGE_USAGE_STATS"
    tools:ignore="ProtectedPermissions"/>

ouvrir l'activité qui permettra à l'utilisateur de confirmer vous donner cette autorisation:

startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));

obtenir les statistiques, agrégées:

 private static final String USAGE_STATS_SERVICE ="usagestats"; // Context.USAGE_STATS_SERVICE);
 ...
 final UsageStatsManager usageStatsManager=(UsageStatsManager)context.getSystemService(USAGE_STATS_SERVICE);
 final Map<String,UsageStats> queryUsageStats=usageStatsManager.queryAndAggregateUsageStats(fromTime,toTime);

Le problème

Je n'arrive pas à vérifier si l'autorisation dont vous avez besoin ("Android.permission.PACKAGE_USAGE_STATS") est accordée. Tout ce que j'ai essayé jusqu'à présent revient toujours à refuser.

Le code fonctionne, mais la vérification des autorisations ne fonctionne pas bien.

Ce que j'ai essayé

vous pouvez vérifier si une autorisation est accordée en utilisant ceci:

String permission = "Android.permission.PACKAGE_USAGE_STATS";
boolean granted=getContext().checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;   

ou ca:

String permission = "Android.permission.PACKAGE_USAGE_STATS";
boolean granted=getPackageManager().checkPermission(permission,getPackageName())== PackageManager.PERMISSION_GRANTED;   

Les deux ont toujours répondu que cela avait été refusé (même lorsque j'ai accordé l'autorisation en tant qu'utilisateur).

En regardant le code de UsageStatsManager, j'ai essayé de trouver cette solution de contournement:

      UsageStatsManager usm=(UsageStatsManager)getSystemService("usagestats");
      Calendar calendar=Calendar.getInstance();
      long toTime=calendar.getTimeInMillis();
      calendar.add(Calendar.YEAR,-1);
      long fromTime=calendar.getTimeInMillis();
      final List<UsageStats> queryUsageStats=usm.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY,fromTime,toTime);
      boolean granted=queryUsageStats!=null&&queryUsageStats!=Collections.EMPTY_LIST;

Cela a fonctionné, mais c'est toujours une solution de contournement.

La question

Comment se fait-il que je n'obtienne pas le résultat correct de la vérification des autorisations?

Que faut-il faire pour mieux le vérifier?

38

Par notre enquête: si MODE est par défaut (MODE_DEFAULT), une vérification supplémentaire des autorisations est nécessaire. Merci à l'effort d'examen de Weien.

boolean granted = false;
AppOpsManager appOps = (AppOpsManager) context
        .getSystemService(Context.APP_OPS_SERVICE);
int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, 
        Android.os.Process.myUid(), context.getPackageName());

if (mode == AppOpsManager.MODE_DEFAULT) {
    granted = (context.checkCallingOrSelfPermission(Android.Manifest.permission.PACKAGE_USAGE_STATS) == PackageManager.PERMISSION_GRANTED);
} else {
    granted = (mode == AppOpsManager.MODE_ALLOWED);
}
14
Chris Li

Les autorisations spéciales accordées par l'utilisateur dans les paramètres système (accès aux statistiques d'utilisation, accès aux notifications,…) sont gérées par AppOpsManager , qui a été ajouté dans Android 4.4.

Notez qu'en plus de l'utilisateur vous accordant l'accès dans les paramètres système, vous avez généralement besoin d'une autorisation dans le manifeste Android (ou un composant)), sans que votre application n'apparaisse même pas dans les paramètres système. statistiques d'utilisation dont vous avez besoin de l'autorisation Android.permission.PACKAGE_USAGE_STATS.

Il n'y a pas beaucoup de documentation à ce sujet, mais vous pouvez toujours vérifier les sources Android pour cela. La solution peut sembler un peu hacky car certaines constantes ont été ajoutées plus tard dans le AppOpsManager, et certaines constantes ( par exemple pour vérifier différentes autorisations) sont toujours cachés dans les API privées.

AppOpsManager appOps = (AppOpsManager) context
        .getSystemService(Context.APP_OPS_SERVICE);
int mode = appOps.checkOpNoThrow("Android:get_usage_stats", 
        Android.os.Process.myUid(), context.getPackageName());
boolean granted = mode == AppOpsManager.MODE_ALLOWED;

Cela vous indique si l'autorisation a été accordée par l'utilisateur. Notez que depuis le niveau 21 de l'API, il y a AppOpsManager.OPSTR_GET_USAGE_STATS = "Android:get_usage_stats" Constant.

J'ai testé cette vérification sur Lollipop (y compris 5.1.1) et cela fonctionne comme prévu. Il me dit si l'utilisateur a donné l'autorisation explicite sans aucun plantage. Il y a aussi la méthode appOps.checkOp() qui pourrait lancer un SecurityException.

26
Tomik

Le deuxième argument de checkOpNoThrow est uid, pas pid.

Changer le code pour refléter cela semble résoudre les problèmes rencontrés par d'autres:

AppOpsManager appOps = (AppOpsManager) context
    .getSystemService(Context.APP_OPS_SERVICE);
int mode = appOps.checkOpNoThrow("Android:get_usage_stats", 
    Android.os.Process.myUid(), context.getPackageName());
boolean granted = mode == AppOpsManager.MODE_ALLOWED;
4
kingjason611

La vérification supplémentaire dans Chris Liréponse est inutile, sauf si vous développez un système app.

Lorsqu'une application est installée pour la première fois et que l'utilisateur n'a pas touché l'autorisation d'accès de l'application, alors checkOpNoThrow() renvoie MODE_DEFAULT .

Chris dit que nous devons ensuite vérifier si l'application a le PACKAGE_USAGE_STATS autorisation manifeste:

if (mode == AppOpsManager.MODE_DEFAULT) {
    granted = (context.checkCallingOrSelfPermission(Manifest.permission.PACKAGE_USAGE_STATS) == PackageManager.PERMISSION_GRANTED);
} else {
    granted = (mode == AppOpsManager.MODE_ALLOWED);
}

Cependant, à moins que votre application ne soit une application système, la vérification de cette autorisation renvoie toujours PERMISSION_DENIED car c'est une autorisation de signature . Vous pouvez donc simplifier le code comme suit:

if (mode == AppOpsManager.MODE_DEFAULT) {
    granted = false;
} else {
    granted = (mode == AppOpsManager.MODE_ALLOWED);
}

Que vous pouvez ensuite simplifier:

granted = (mode == AppOpsManager.MODE_ALLOWED);

Ce qui nous ramène à la solution originale et plus simple:

AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, Process.myUid(), context.getPackageName());
boolean granted = (mode == AppOpsManager.MODE_ALLOWED);

Testé et vérifié en Android 5, 8.1 et 9.

1
Sam