Как проверить, разрешено ли разрешение "android.permission.PACKAGE_USAGE_STATS"?

Фон

Я пытаюсь получить запущенную приложением статистику, и в Lollipop это возможно с помощью класса UsageStatsManager, как такового (оригинальный пост здесь):

проявляются:

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

открытие действия, которое позволит пользователю подтвердить предоставление вам этого разрешения:

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"). Все, что я пробовал до сих пор, всегда возвращает, что это отрицается.

Код работает, но проверка разрешений не работает.

Что я пробовал

Вы можете проверить разрешение, используя это:

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;

Это сработало, но это все еще обходной путь.

Вопрос

Почему я не получил правильный результат проверки разрешений?

Что нужно сделать, чтобы проверить это лучше?

Ответ 1

По нашему исследованию: если MODE по умолчанию (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);
}

Ответ 2

Специальные разрешения, предоставленные пользователем в системных настройках (использование статистики, доступ к уведомлениям,...) обрабатываются 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.

Ответ 3

Второй аргумент 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;

Ответ 4

Дополнительная проверка в ответе Криса Ли не нужна, если вы не разрабатываете системное приложение.

Когда приложение впервые установлено, и пользователь не коснулся разрешения на доступ к использованию приложения, тогда checkOpNoThrow() возвращает MODE_DEFAULT.

Крис говорит, что нам нужно проверить, есть ли у приложения разрешение на манифест 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);
}

Однако, если ваше приложение не является системным приложением, проверка этого разрешения всегда возвращает PERMISSION_DENIED потому что это разрешение подписи. Таким образом, вы можете упростить код до этого:

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.