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

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?

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

Get a free NHS test today to check if you have coronavirus (COVID , You can have a swab test to check if you have coronavirus (COVID-19) now. You can choose to take the test: at a test site near you today and get your result� Check if a cell has any text in it. To do this task, use the ISTEXT function. Check if a cell matches specific text. Use the IF function to return results for the condition that you specify. Check if part of a cell matches specific text. To do this task, use the IF, SEARCH, and ISNUMBER functions.

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.

Check if a vehicle is taxed, Online scams helper. We'll use your answers to the questions to give you advice on: how to check whether something might be a scam; what to do if you've been� How to Spot a Fake Check. Make sure the check is issued by a legitimate bank and doesn't have a fake bank name. If the check includes the bank's number and address, then Look for check security features, such as microprinting on the signature line , a security screen on the back of the check ,

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;

Get a free NHS test today to check if you have coronavirus, Garmin's status page offers an easy way to check if Connect is back online yet. I'm going to check if she closed the door after leaving - OK 2. I'm going to check whether she closed the door or not after leaving - wether + or not 3. I'm going to check that she closed the door after leaving. - Incorrect Attend confirmation d'un natif quand même, mais je crois que c'est ca.

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.

Check if you're eligible for help, Whether you owe taxes or you're expecting a refund, you If you file your taxes by mail, using USPS� Before purchasing a meter, check with your insurance company to see if the cost of the device (and test strips) will be covered: Some insurance companies only provide coverage for particular meters. The Diabetes Supplies to Pack When You Leave Home

Check if something might be a scam, Have I Been Pwned allows you to search across multiple data breaches to see if your email address has been compromised. is there a difference between "check if" and "check whether" If this is your first visit, be sure to check out the FAQ by clicking the link above. You may have to register before you can post: click the register link above to proceed.

How To Check If Garmin Connect Is Back Online, To determine if a 100 dollar bill is real, rub it between your fingers to see if you can feel any raised ink. If you can't, the bill is probably fake. Make sure the portrait of Ben Franklin looks realistic, sharp, and highly-detailed. You can also hold the bill up to a light to see if it's real.

Where's My Refund? Check the Status of My Tax Return, You've just been sent a verification email, all you need to do now is confirm your address by clicking on the link when it hits your mailbox and you'll be automatically notified of future pwnage. In case it doesn't show up, check your junk mail and if you still can't find it, you can always repeat this process.

Comments
  • Possible duplicate of Check if my application has usage access enabled
  • @Sam 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 : stackoverflow.com/a/54839499/878126
  • What's the difference between using this, and the one I've accepted?
  • The only difference is in mode: MODE_DEFAULT, permission could be granted or not. Further checking is needed.
  • 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)?
  • 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?
  • The extra check in this answer is only needed if you're developing a system app. See my explanation.
  • For some reason it crashed for me. Could you please provide a more detailed code? Maybe I got it wrong somewhere...
  • What exception did it throw?
  • I think it's: java.lang.SecurityException: uid 10046 does not have android.permission.UPDATE_APP_OPS_STATS. You mean that it needs an additional permission? BTW, I think it's better to write "android.os.Process.myPid()" than "myUid" .
  • I updated the answer, if you use checkOpNoThrow() method, is should not throw the exception. However, the check doesn't seem to be working on Lollipop :(
  • 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.
  • I don't have the reputation to write a comment yet. Feel free to add a comment, and I'll delete my answer.
  • 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 ?