如何检查是否给予"android.permission.PACKAGE_USAGE_STATS"权限?

How to check if "android.permission.PACKAGE_USAGE_STATS" permission is given?

背景

我正在尝试获取应用程序启动的统计信息,在 Lollipop 上可以使用 UsageStatsManager class, as such (original post here):

清单:

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

打开 activity 让用户确认授予您此权限:

startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));

获取统计数据,汇总:

 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);

问题

我似乎无法检查是否已授予您需要的权限 ("android.permission.PACKAGE_USAGE_STATS")。到目前为止,我一直都在尝试 returns 它被拒绝了。

代码有效,但权限检查效果不佳。

我试过的

您可以使用以下任一方式检查是否授予了权限:

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

或者这个:

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

两者总是返回被拒绝的消息(即使我已作为用户授予权限)。

查看 UsageStatsManager 的代码,我试图想出这个解决方法:

      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;

它奏效了,但它仍然是一种解决方法。

问题

为什么我没有得到正确的权限检查结果?

应该怎么做才能更好地检查?

用户在系统设置中授予的特殊权限(使用统计访问权限、通知访问权限等)由 AppOpsManager 处理,这是在 Android 4.4 中添加的。

请注意,除了用户在系统设置中授予您访问权限外,您通常需要在 Android 清单(或某些组件)中获得权限,否则您的应用甚至不会显示在系统设置中。对于使用情况统计,您需要 android.permission.PACKAGE_USAGE_STATS 权限。

关于它的文档不多,但您可以随时查看 Android 来源。 该解决方案可能看起来有点老套,因为一些常量是后来添加到 AppOpsManager 中的,而一些常量(例如,用于检查不同的权限)仍然隐藏在私有 API 中。

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;

这会告诉您权限是否由用户授予。 请注意,由于 API 级别 21 存在常量 AppOpsManager.OPSTR_GET_USAGE_STATS = "android:get_usage_stats".

我在 Lollipop(包括 5.1.1)上测试了这个检查,它按预期工作。它告诉我用户是否在没有任何崩溃的情况下给出了明确的许可。 还有方法 appOps.checkOp() 可能会抛出 SecurityException.

checkOpNoThrow的第二个参数是uid,不是pid

更改代码以反映似乎解决了其他人遇到的问题:

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;

根据我们的调查:如果模式是默认的(MODE_DEFAULT),则需要额外的权限检查。感谢维恩的努力

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);
}

除非您正在开发 系统 应用程序,否则 Chris Li's 中的额外检查是不必要的。

当应用首次安装且用户未触及该应用的使用访问权限时,则checkOpNoThrow() returns MODE_DEFAULT

Chris 说我们然后需要检查应用程序是否具有 PACKAGE_USAGE_STATS 清单权限:

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

但是,除非您的应用是系统应用,否则始终检查此权限 returns PERMISSION_DENIED because it's a signature permission。因此,您可以将代码简化为:

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

然后您可以将其简化为:

granted = (mode == AppOpsManager.MODE_ALLOWED);

这让我们回到最初的更简单的解决方案:

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);

在 Android 5、8.1 和 9 中进行了测试和验证。