检查我的应用程序是否启用了使用权限访问

31

我正在使用新的UsageStatsManager API 来在 Android 5.0 Lollipop 中获取当前前台应用程序。点击这里查看具体实现。

为了使用此API,用户必须在设置->安全->有权查看使用情况的应用屏幕中启用该应用程序访问权限。

我使用以下 Intent 将用户直接导航到此设置页面:


startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));

现在,我想验证用户是否启用了我的应用程序。 我希望像验证用户是否启用了我的应用程序使用NotificationListenerService一样这样做,但我不知道String键是什么,即使存在也不知道。


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
}

有没有办法检查我的应用程序是否已启用使用访问权限?


lluz:你知道怎么让它自动打开吗? - Jame
@user8430,这在 OP 中已经写明了。startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)); 您只能将用户发送到屏幕,而不能自动启用它。 - Lior Iluz
我明白了,谢谢。我发现另一个问题,就是当用户正确选择应用程序后,我们如何关闭Intent。因为在我选择应用程序后,我必须按返回按钮才能返回我的Activity。我正在寻找一种自动返回Activity的方法,以便用户选择目标应用程序后自动返回。 - Jame
在将用户发送到设置屏幕之前,请使用布尔标志,并在 onResume() 方法中检查用户是否已返回并检查权限是否已授予。 - Lior Iluz
10个回答

37

在Twitter上收到了一个非常好的答案,经测试可行:

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

1
如果用户点击“允许”,我们能否在ActivityResult中获得任何回调? - Ramesh Kanuganti
@Umar,你说这在Lollipop上不起作用,但是你是指KitKat吗?它在KitKat上不起作用,因为AppOpsManager.OPSTR_GET_USAGE_STATS仅支持API级别21。将该常量替换为“android:get_usage_stats”,这将适用于KitKat及以下版本。 - Tyler
唯一的问题是 checkOpNoThrow 已被弃用,请使用 unsafeCheckOpNoThrow。 - aleksandrbel

6

以下是我针对这个问题的全面解决方案(基于类似问题和答案在这里):

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
}

我调查了这段代码中的额外检查,并发现它实际上并不需要:https://dev59.com/dF4b5IYBdhLWcg3wnCpy#54847723 - Sam
为什么会被踩呢?这是一种正确的方法,可以处理所有情况。适用于所有 Android 版本和用户/系统应用程序... - android developer

6

我之前使用了和Bao Le相同的代码,但是我遇到了这样一个问题:某些设备(例如VF-895N)即使没有启用使用统计信息,也会报告已启用。为了解决这个问题,我对我的代码进行了修改:

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

已经在多个设备上成功测试。


5

检测使用权限更改的时间

使用此类来通知您的应用程序何时被授予或撤销了使用权限。

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

}

致谢

本代码基于epicality另一个回答中的代码。


5
请注意,unsafeCheckOpNoThrow是checkOpNoThrow的新名称(从Android Q开始使用),因为checkOpNoThrow现已弃用。 - android developer
为什么它不安全?为什么要弃用它?只是为了重命名吗? - android developer

2
这是一种替代方案:
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;

0

这个可以在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;

只是出于好奇,UsageStatsManager 在 KitKat 上真的可用吗?我认为它只在 Lollipop 及以上版本中可用。 - Sam
是的,使用情况状态管理器仅适用于API 22及以上版本。但上述代码与使用情况状态管理器无关。 - Jarin Rocks

0

对我来说,这些答案都没有起作用,所以我自己写了这个。

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

-1

这段代码在Lollipop和Marshmallow上运行良好,我在我的应用程序中使用了这段代码

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

-1
如果他们正在使用亚马逊Fire平板电脑(以及可能的其他Fire OS设备),用户可以从用户安装的Google Play商店下载应用程序,然后在其操作系统中没有激活所需选项。我知道这一点,因为作为一个Fire OS用户,几分钟前我也遇到了这个问题。检测用户是否使用Fire OS,并在此情况下提供实际存在的选项,对于用户和开发人员来说都是非常棒的。

-1

试试这个,

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;

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接