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

4

46

Background

I'm trying to get app-launched statistics, and on Lollipop it's possible by using the UsageStatsManager class, as such (original post here):

manifest:

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

opening the activity that will let the user confirm giving you this permission:

startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));

getting the stats, aggregated :

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

The problem

I can't seem to check if the permission that you need ("android.permission.PACKAGE_USAGE_STATS") is granted. All I've tried so far always returns that it is denied.

The code works, but the permission check doesn't work well.

What I've tried

you can check for a permission being granted using either this:

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

or this:

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

Both always returned that it got denied (even when I've granted the permission as a user).

Looking at the code of UsageStatsManager, I've tried to come up with this workaround:

      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;

It worked, but it's still a workaround.

The question

How come I don't get the correct result of the permission check?

What should be done to check it better?

Kedge answered 7/3, 2015 at 23:5 Comment(2)
Possible duplicate of Check if my application has usage access enabledStettin
@Stettin It's quite old and too late for that, but just in case, because I see the answer here is more updated, I've added my answer that I use myself on your link : https://mcmap.net/q/89289/-check-if-my-application-has-usage-access-enabledKedge
E
23

By our investigation: if MODE is default (MODE_DEFAULT), extra permission checking is needed. Thanks to Weien's examination effort.

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);
}
Estimation answered 22/2, 2017 at 11:51 Comment(13)
What's the difference between using this, and the one I've accepted?Kedge
The only difference is in mode: MODE_DEFAULT, permission could be granted or not. Further checking is needed.Estimation
Is there any way the original code from what I've accepted won't work, then? What's the use case ?Kedge
The mode: MODE_DEFAULT could return in the very first time apps check it (the permission is not modified yet). But this mode doesn't mean the permission is not granted. We need to further check permission by using context.checkCallingOrSelfPermission() function.Estimation
But when I tried checkCallingOrSelfPermission , it always wrote that it's denied, which is why I asked this question thread (see the part of "what I've tried" ) . Can you please tell what to do to see it work differently (steps)?Kedge
As our solution showed, only use context.checkCallingOrSelfPermission() when mode is MODE_DEFAULT. Other cases should use original logic as you mentioned.Estimation
getting crashed on checkOpNoThrow so i wanna ask that the permission "android.permission.PACKAGE_USAGE_STATS" is work below lollipop or notMelessa
OPSTR_GET_USAGE_STATS - Added in API level 21 (Lollipop).Estimation
I don't believe checkCallingOrSelfPermission will return true when MODE_DEFAULT and permission is granted. Can you confirm that you have actually saw such case, at all?Mittiemittimus
Please specify your question in detail to let community members help.Estimation
android.Manifest.permission.PACKAGE_USAGE_STATS is available from API level 23 , is this permission is already given for third party apps for API level 21 and 22?Owenowena
@Nainal, that permission is never granted to non-System apps.Stettin
The extra check in this answer is only needed if you're developing a system app. See my explanation.Stettin
B
35

Special permissions that are granted by the user in the system settings (usage stats access, notification access, …) are handled by AppOpsManager, which was added in Android 4.4.

Note that besides user granting you access in the system settings you typically need a permission in the Android manifest (or some component), without that your app doesn't even show in the system settings. For usage stats you need android.permission.PACKAGE_USAGE_STATS permission.

There's not much documentation about that but you can always check Android sources for it. The solution might seem bit hacky because some constants were added later to the AppOpsManager, and some constants (e.g. for checking different permissions) are still hidden in private APIs.

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;

This tells you if the permission was granted by the user. Note that since API level 21 there is constant AppOpsManager.OPSTR_GET_USAGE_STATS = "android:get_usage_stats".

I tested this check on Lollipop (including 5.1.1) and it works as expected. It tells me whether the user gave the explicit permission without any crash. There's also method appOps.checkOp() which might throw a SecurityException.

Brambly answered 8/3, 2015 at 0:4 Comment(4)
BTW, for some reason, Android-Studio doesn't like the usage of "Context.APP_OPS_SERVICE" .Kedge
If it helped you, consider accepting the answer. I checked Android sources for the solution. If you look into UsageStatsService you'll find how it checks the permission. It first checks explicit user consent with AppOpsManager and then it fallbacks to standard permission check. It's important to know that this check only checks user granted permissions like this, it doesn't check manifest permissions.Brambly
I've tested the code again, on Android 5.1.1, and it crashes with the SecurityException .Why is that?Kedge
appOps.checkOpNoThrow is deprecatedCaftan
E
23

By our investigation: if MODE is default (MODE_DEFAULT), extra permission checking is needed. Thanks to Weien's examination effort.

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);
}
Estimation answered 22/2, 2017 at 11:51 Comment(13)
What's the difference between using this, and the one I've accepted?Kedge
The only difference is in mode: MODE_DEFAULT, permission could be granted or not. Further checking is needed.Estimation
Is there any way the original code from what I've accepted won't work, then? What's the use case ?Kedge
The mode: MODE_DEFAULT could return in the very first time apps check it (the permission is not modified yet). But this mode doesn't mean the permission is not granted. We need to further check permission by using context.checkCallingOrSelfPermission() function.Estimation
But when I tried checkCallingOrSelfPermission , it always wrote that it's denied, which is why I asked this question thread (see the part of "what I've tried" ) . Can you please tell what to do to see it work differently (steps)?Kedge
As our solution showed, only use context.checkCallingOrSelfPermission() when mode is MODE_DEFAULT. Other cases should use original logic as you mentioned.Estimation
getting crashed on checkOpNoThrow so i wanna ask that the permission "android.permission.PACKAGE_USAGE_STATS" is work below lollipop or notMelessa
OPSTR_GET_USAGE_STATS - Added in API level 21 (Lollipop).Estimation
I don't believe checkCallingOrSelfPermission will return true when MODE_DEFAULT and permission is granted. Can you confirm that you have actually saw such case, at all?Mittiemittimus
Please specify your question in detail to let community members help.Estimation
android.Manifest.permission.PACKAGE_USAGE_STATS is available from API level 23 , is this permission is already given for third party apps for API level 21 and 22?Owenowena
@Nainal, that permission is never granted to non-System apps.Stettin
The extra check in this answer is only needed if you're developing a system app. See my explanation.Stettin
R
4

The second argument of checkOpNoThrow is uid, not pid.

Changing the code to reflect that seems to fix the issues others were having:

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;
Razorbill answered 18/8, 2015 at 14:3 Comment(2)
I don't have the reputation to write a comment yet. Feel free to add a comment, and I'll delete my answer.Razorbill
How odd. I'm very sure I've tried it before, and that it didn't work. I even have the exact same code in the POC app I've made for this, and reported about it to Google. I have no idea what's going on. Maybe I had a bad rom ?Kedge
S
3

The extra check in Chris Li's answer is unnecessary unless you're developing a system app.

When an app is first installed and the user hasn't touched the app's usage access permission, then checkOpNoThrow() returns MODE_DEFAULT.

Chris says we then need to check if the app has the PACKAGE_USAGE_STATS manifest permission:

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

However, unless your app is a system app, checking this permission always returns PERMISSION_DENIED because it's a signature permission. So you can simplify the code down to this:

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

Which you can then simplify down to this:

granted = (mode == AppOpsManager.MODE_ALLOWED);

Which brings us back to the original, simpler solution:

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

Tested and verified in Android 5, 8.1 and 9.

Stettin answered 24/2, 2019 at 0:48 Comment(2)
Since this answer doesn't cover all cases, as you even written (not for system apps), I think my answer is better: https://mcmap.net/q/89289/-check-if-my-application-has-usage-access-enabled . It will cover all cases, and even if the user has converted the app to be a system app, it would work. I don't see any point in writing a new answer that is identical and that says that it's better just because it's shorter, while saying that it will work only in some (granted, most) cases...Kedge
Thanks to mention my answer. Indeed I tried it in a pre-installed app.Estimation

© 2022 - 2024 — McMap. All rights reserved.