バックグラウンドで動作するAndroidアプリでは、現在フォアグラウンドで動作しているアプリがなんなのかを知りたくなる場合があります。他のアプリの情報が制限されている現在でも少し特殊なパーミッションが必要ですが、この情報を取得することができます。
「アプリの使用状況データ」を利用する
Androidでは「アプリの使用状況データ」が記録されており、各アプリのActivityのライフサイクルイベントがいつ発生したかを知ることができます。この情報から最後にRESUME状態となったアプリを調べれば、現在どのアプリがフォアグラウンドに表示されているかを知ることができます。
「アプリの使用状況データ」に必要なパーミッション
コンデータにアクセスするには PACKAGE_USAGE_STATS というパーミッションが必要となります。
これはランタイムパーミッションとも異なり、システム設定からユーザーに許可をしてもらう必要なパーミッションです。以下のように宣言しておきます。
<uses-permission
android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions"
/>
許諾の有無を確認する
このパーミッションは checkSelfPermission では許諾の有無を判定できません。
AppOpsManagerを使って、AppOpsとしての許諾状態を確認します。
private fun hasUsageStatsPermission(): Boolean {
val appOpsManager = getSystemService<AppOpsManager>() ?: return false
return appOpsManager.checkOpNoThrow(
AppOpsManager.OPSTR_GET_USAGE_STATS,
Process.myUid(),
packageName,
) == AppOpsManager.MODE_ALLOWED
}
AppOpsManager.OPSTR_GET_USAGE_STATS の値は "android:get_usage_stats" であり、パーミッション文字列とは別の値です。上記の様に直接指定することもできますし、パーミッション文字列から取得することもできます。
private fun hasUsageStatsPermission(): Boolean {
val appOpsManager = getSystemService<AppOpsManager>() ?: return false
val op = AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS) ?: return false
return appOpsManager.checkOpNoThrow(
op,
Process.myUid(),
packageName,
) == AppOpsManager.MODE_ALLOWED
}
許諾をリクエストする
このパーミッションはシステム設定に飛んでユーザーに許諾してもらう必要があります。
以下のように、Intentを投げることで設定画面に飛ばすことができます。
private fun requestUsageStatsPermission() {
val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS).also {
it.data = "package:${packageName}".toUri()
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(intent)
}
「アプリの使用状況データ」の取得
「アプリの使用状況データ」にアクセスするには UsageStatsManager を使用します。
queryEvents に取得したい時間の範囲を指定します。以下では5分前から現在までのイベントを取得しています。
private fun dumpEvents() {
val usageStatsManager = getSystemService<UsageStatsManager>() ?: return
val now = System.currentTimeMillis()
usageStatsManager.queryEvents(now - 5.minutes.inWholeMilliseconds, now).let {
val event = UsageEvents.Event()
while (it.getNextEvent(event)) {
if (event.eventType == UsageEvents.Event.ACTIVITY_RESUMED) {
Log.e("XXXX", "RESUMED ${event.packageName} ${event.className}")
}
}
}
}
UsageEventsのイテレーションはチョット特殊でgetNextEventがfalseを返すまで取得します。
eventTypeでそのイベントの種別が判定できますので、UsageEvents.Event.ACTIVITY_RESUMEDでフィルタリングし、最後の情報が、現在表示されているアプリの情報であると判定することができます。
コールバックを登録する手段は(@hideなメソッドは存在しますが)公開されていません。継続的に監視するにはポーリングするしかなさそうです。
以上です。