How to check if “android.permission.PACKAGE_USAGE_STATS” permission is given?

后端 未结 4 2024
暗喜
暗喜 2020-11-27 17:02

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

m

相关标签:
4条回答
  • 2020-11-27 17:20

    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);
    }
    
    0 讨论(0)
  • 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;
    
    0 讨论(0)
  • 2020-11-27 17:35

    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.

    0 讨论(0)
  • 2020-11-27 17:40

    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.

    0 讨论(0)
提交回复
热议问题