31

I'm using the new UsageStatsManager API to get current foreground application in Android 5.0 Lollipop. In order to use this API, the user must enable the application in the Settings->Security->Apps with usage access screen.

I send the user directly to this screen with this Intent:


startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));

Now, I want to validate the user enabled my application. I wanted to do so like I validate the user enabled my application to use the NotificationListenerService but I have no idea what is the String key, if it even exists.


Settings.Secure.getString(contentResolver, "enabled_notification_listeners");
// Tried Settings.ACTION_USAGE_ACCESS_SETTINGS as key but it returns null

Second approach was to query the usage stats and check if it returns results (it returns an empty array when the app is not enabled) and it works most of the times but sometimes it returns 0 results even when my app is enabled.


UsageStatsManager mUsageStatsManager = (UsageStatsManager) context.getSystemService("usagestats");
long time = System.currentTimeMillis();
List stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 10, time);

if (stats == null || stats.isEmpty()) {
    // Usage access is not enabled
}

Is there a way to check if my application has usage access enabled?

Community
  • 1
  • 1
Lior Iluz
  • 26,213
  • 16
  • 65
  • 114
  • lluz: Do you know how can I open it automatically? – Jame Sep 23 '16 at 02:04
  • @user8430 it's written in the OP. startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)); You can only send the user to the screen, not enable automatically for him. – Lior Iluz Sep 24 '16 at 15:08
  • I got it. Thank you. I found another issue that is how can we close the Intent when user correctly select application. Because, affter I select application, I have to press back button to returns my Activity. I am finding a automatic way to auto return the Activity when user select the target app – Jame Sep 24 '16 at 15:12
  • @user8430 use a boolean flag before you send the user to the settings screen and check he's back onResume -> check if the permission is granted. – Lior Iluz Sep 24 '16 at 15:20
  • I found one guy has same issue as my issue at http://stackoverflow.com/questions/39651734/how-to-close-action-usage-access-settings-intent-when-user-click-correct-target?noredirect=1&lq=1 – Jame Sep 24 '16 at 15:24

10 Answers10

35

Received a great answer by someone on Twitter, tested working:

try {
   PackageManager packageManager = context.getPackageManager();
   ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0);
   AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
   int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
   return (mode == AppOpsManager.MODE_ALLOWED);

} catch (PackageManager.NameNotFoundException e) {
   return false;
}
Lior Iluz
  • 26,213
  • 16
  • 65
  • 114
  • 1
    if user clicks on Allow can we get any call back in ActivityResult – Ramesh Kanuganti Feb 20 '17 at 06:30
  • @Umar, you said this doesn't work on Lollipop, but did you mean KitKat? It doesn't work on KitKat because AppOpsManager.OPSTR_GET_USAGE_STATS is only supported at API level 21. Replace that constant with "android:get_usage_stats" and this will work down to KitKat. – Tyler Dec 19 '18 at 14:08
  • The only thing is that checkOpNoThrow is deprecated. Use unsafeCheckOpNoThrow – aleksandrbel Mar 10 '23 at 14:12
6

I previously used the same code as Bao Le, but I've run into the problem that certain devices (e.g. VF-895N) report usage stats as enabled even when they're not. As a workaround I've modified my code like this:

public static boolean hasPermission(@NonNull final Context context) {
    // Usage Stats is theoretically available on API v19+, but official/reliable support starts with API v21.
    if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
        return false;
    }

    final AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);

    if (appOpsManager == null) {
        return false;
    }

    final int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(), context.getPackageName());
    if (mode != AppOpsManager.MODE_ALLOWED) {
        return false;
    }

    // Verify that access is possible. Some devices "lie" and return MODE_ALLOWED even when it's not.
    final long now = System.currentTimeMillis();
    final UsageStatsManager mUsageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
    final List<UsageStats> stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, now - 1000 * 10, now);
    return (stats != null && !stats.isEmpty());
}

Successfully tested on multiple devices.

Markus
  • 180
  • 1
  • 8
6

Here's my all-around solution for this (based on similar question and answer here) :

public static PermissionStatus getUsageStatsPermissionsStatus(Context context) {
    if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP)
        return PermissionStatus.CANNOT_BE_GRANTED;
    AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
    final int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(), context.getPackageName());
    boolean granted = mode == AppOpsManager.MODE_DEFAULT ?
            (context.checkCallingOrSelfPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) == PackageManager.PERMISSION_GRANTED)
            : (mode == AppOpsManager.MODE_ALLOWED);
    return granted ? PermissionStatus.GRANTED : PermissionStatus.DENIED;
}

public enum PermissionStatus {
    GRANTED, DENIED, CANNOT_BE_GRANTED
}
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • I investigated the extra check in this code and found that it isn't actually needed: https://stackoverflow.com/a/54847723/238753 – Sam Feb 24 '19 at 00:56
  • Why the downvote exactly? It's a correct way to do it, and it handles all cases. All Android versions, and user/system apps alike... – android developer Feb 24 '19 at 07:54
5

Detecting when the usage access changes

Use this class to be notified when your app is granted or revoked usage access.

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class UsagePermissionMonitor {

    private final Context context;
    private final AppOpsManager appOpsManager;
    private final Handler handler;
    private boolean isListening;
    private Boolean lastValue;

    public UsagePermissionMonitor(Context context) {
        this.context = context;
        appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        handler = new Handler();
    }

    public void startListening() {
        appOpsManager.startWatchingMode(AppOpsManager.OPSTR_GET_USAGE_STATS, context.getPackageName(), usageOpListener);
        isListening = true;
    }

    public void stopListening() {
        lastValue = null;
        isListening = false;
        appOpsManager.stopWatchingMode(usageOpListener);
        handler.removeCallbacks(checkUsagePermission);
    }

    private final AppOpsManager.OnOpChangedListener usageOpListener = new AppOpsManager.OnOpChangedListener() {
        @Override
        public void onOpChanged(String op, String packageName) {
            // Android sometimes sets packageName to null
            if (packageName == null || context.getPackageName().equals(packageName)) {
                // Android actually notifies us of changes to ops other than the one we registered for, so filtering them out
                if (AppOpsManager.OPSTR_GET_USAGE_STATS.equals(op)) {
                    // We're not in main thread, so post to main thread queue
                    handler.post(checkUsagePermission);
                }
            }
        }
    };

    private final Runnable checkUsagePermission = new Runnable() {
        @Override
        public void run() {
            if (isListening) {
                int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, Process.myUid(), context.getPackageName());
                boolean enabled = mode == AppOpsManager.MODE_ALLOWED;

                // Each change to the permission results in two callbacks instead of one.
                // Filtering out the duplicates.
                if (lastValue == null || lastValue != enabled) {
                    lastValue = enabled;

                    // TODO: Do something with the result
                    Log.i(UsagePermissionMonitor.class.getSimpleName(), "Usage permission changed: " + enabled);
                }
            }
        }
    };

}

Credits

Based on code from epicality in another answer.

Sam
  • 40,644
  • 36
  • 176
  • 219
2

This is an alternative solutions:

AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
                    android.os.Process.myUid(), context.getPackageName());
return mode == AppOpsManager.MODE_ALLOWED;
Bao Le
  • 16,643
  • 9
  • 65
  • 68
0

This works down to KitKat (API 19)

    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;
Tyler
  • 6,741
  • 3
  • 22
  • 19
  • Just out of curiosity, does `UsageStatsManager` actually work on KitKat? I thought it was only available from Lollipop. – Sam Feb 24 '19 at 00:55
  • Yes usage status manager will work from API 22 only. But the above code is not related to usage status manager. – Jarin Rocks Mar 19 '19 at 11:30
0

None of the answer worked for me so i made this

public boolean permissiontodetectapp(Context context) {
    try {
        ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
        return ((AppOpsManager) context.getSystemService(APP_OPS_SERVICE)).checkOpNoThrow("android:get_usage_stats", applicationInfo.uid, applicationInfo.packageName) != 0;
    } catch (PackageManager.NameNotFoundException unused) {
        return true;
    }
}
Sagar gujarati
  • 152
  • 1
  • 15
-1

this code working in lollipop and marshmallow i used this code in my app

if (Build.VERSION.SDK_INT >= 21) {
            UsageStatsManager mUsageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
            long time = System.currentTimeMillis();
            List stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 10, time);

            if (stats == null || stats.isEmpty()) {
                Intent intent = new Intent();
                intent.setAction(Settings.ACTION_USAGE_ACCESS_SETTINGS);
                context.startActivity(intent);
            }
    }
Manish Godhani
  • 189
  • 1
  • 5
-1

If they are using an Amazon Fire tablet (and possibly other Fire OS devices) the user can download the application from a user installed Google Play Store then not have the option you want activated available in their OS. I know this because as a Fire OS user this happened to me a few minutes ago. Detecting whether a user has Fire OS and, if so, offering an option which actually exists would be fantastic for both user and dev.

gphx
  • 1
-1

try this ,

public boolean check_UsgAccs(){
    long tme = System.currentTimeMillis();
    UsageStatsManager usm = (UsageStatsManager)getApplicationContext().getSystemService(Context.USAGE_STATS_SERVICE);
    List<UsageStats> al= usm.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY, tme - (1000 * 1000), tme);
        return  al.size()>0;

    }
Harsh
  • 363
  • 4
  • 14