web-dev-qa-db-ja.com

「Android.permission.PACKAGE_USAGE_STATS」権限が付与されているかどうかを確認するにはどうすればよいですか?

バックグラウンド

アプリ起動の統計を取得しようとしていますが、Lollipopでは UsageStatsManager クラスを使用することで可能です(元の投稿 here ):

マニフェスト:

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

この許可を与えることをユーザーに確認させるアクティビティを開く:

startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));

集計された統計情報の取得:

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

問題

必要なアクセス許可( "Android.permission.PACKAGE_USAGE_STATS")が許可されているかどうかを確認できないようです。私がこれまでに試したすべてのことは、それが拒否されたことを常に返します。

コードは機能しますが、権限チェックはうまく機能しません。

私が試したこと

次のいずれかを使用して、付与されている許可を確認できます。

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

またはこれ:

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

どちらも、拒否されたことを常に返しました(ユーザーとして許可を与えた場合でも)。

UsageStatsManagerのコードを見て、私はこの回避策を考え出そうとしました:

      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;

うまくいきましたが、まだ回避策です。

質問

許可チェックの正しい結果が得られないのはなぜですか?

それをより良くチェックするために何をすべきですか?

38

調査によると、MODEがデフォルト(MODE_DEFAULT)の場合、追加の許可チェックが必要です。 Weienの試験努力に感謝します。

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);
}
14
Chris Li

システム設定でユーザーによって付与された特別なアクセス許可(使用統計アクセス、通知アクセスなど)は、 AppOpsManager によって処理されます。これはAndroid 4.4。

ユーザーがシステム設定でアクセスを許可することに加えて、アプリがシステム設定に表示されないことを除いて、通常、Androidマニフェスト(または一部のコンポーネント)で許可が必要です。 _Android.permission.PACKAGE_USAGE_STATS_パーミッションが必要な使用状況統計。

これに関するドキュメントはあまりありませんが、いつでもAndroidソースを確認できます。ソリューションは、後でAppOpsManagerに追加された定数と、いくつかの定数(たとえば、異なるアクセス許可を確認するため)は、プライベートAPIにはまだ隠されています。

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

これにより、ユーザーがアクセス許可を付与したかどうかがわかります。 APIレベル21以降、定数_AppOpsManager.OPSTR_GET_USAGE_STATS = "Android:get_usage_stats"_があることに注意してください。

Lollipop(5.1.1を含む)でこのチェックをテストしましたが、期待どおりに機能します。ユーザーがクラッシュせずに明示的な許可を与えたかどうかを教えてくれます。 SecurityExceptionをスローする可能性のあるメソッドappOps.checkOp()もあります。

26
Tomik

checkOpNoThrowの2番目の引数は、uidではなく、pidです。

それを反映するようにコードを変更すると、他の人が抱えていた問題が修正されたようです:

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;
4
kingjason611

Chris Lianswer の追加チェックは、systemを開発しているのでなければ不要です。アプリ。

アプリが最初にインストールされ、ユーザーがアプリの使用アクセス許可に触れていない場合、 checkOpNoThrow()MODE_DEFAULT

クリスは、アプリに PACKAGE_USAGE_STATS マニフェスト許可:

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

ただし、アプリがシステムアプリでない限り、この権限をチェックすると常に PERMISSION_DENIED なぜなら これは署名の許可です 。そのため、コードを次のように簡素化できます。

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

これを次のように簡略化できます。

granted = (mode == AppOpsManager.MODE_ALLOWED);

これにより、元のシンプルなソリューションに戻ります。

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

Android 5、8.1、および9.でテストおよび検証済み.

1
Sam