HOME > android > basic

안드로이드 - UsageStatsManager로 앱 실행 기록 가져오기

JSFollow09 Aug 2019

UsageStatsManager는 앱 실행 기록을 제공하는 서비스입니다. 여기서 제공하는 API를 사용하면 최근에 실행된 앱이 무엇인지, 앱들의 사용시간은 각각 얼마인지 알 수 있습니다.

UsageStatsManager는 앱 기록을 Query할 수 있는 API를 제공하며, DAY, MONTH, YEAR의 시간 단위(Interval)로 구분된 결과를 받을 수 있습니다. 결과는 UsageStats 객체로 리턴해 줍니다. 이 객체는 앱 이름, 마지막 사용 시각, 앱이 실행된 전체 시간 등의 정보를 담고 있습니다.

UsageStatsManager는 UsageStats 외에도 UsageEvents, ConfigurationStats를 제공합니다. 리턴되는 컨텐츠가 다를 뿐 쿼리하는 방식은 비슷합니다. 여기에서는 UsageStats를 쿼리하는 방법에 초점을 맞춰 설명하겠습니다.

UsageStatsManager는 다른 앱의 정보를 얻을 때 android.permission.PACKAGE_USAGE_STATS 권한을 요구합니다. 이 권한은 시스템 앱만 얻을 수 있습니다. 일반 앱이 이 권한을 얻으려면 사용자에게 설정에 들어가서 권한을 부여해 달라고 요청해야 합니다.

권한을 요청하는 방법을 먼저 설명하고, 앱 사용 기록을 쿼리하는 방법에 대해서 소개하겠습니다.

이 글에서 사용하는 코드는 모두 코틀린으로 작성되었습니다.

권한 요청

자신의 앱의 AndroidManifest.xml에 아래와 같이 권한을 추가해야 합니다. 일반 앱은 자동으로 권한이 부여되지 않습니다.

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

그리고 앱이 실행되면 사용자가 자신의 앱에 PACKAGE_USAGE_STATS 권한을 부여해줬는지 체크해야 합니다.

아래 코드로 사용자가 권한을 부여해줬는지 체크할 수 있습니다. AppOpsManager는 권한을 못받은 퍼미션에 대해서 사용자가 강제로 권한을 부여해주는 서비스입니다. 그렇기 때문에 AppOpsManager API를 통해 권한이 강제로 부여되었는지 확인하는 것입니다.

private fun checkForPermission(): Boolean {
    val appOps = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
    val mode = appOps.checkOpNoThrow(OPSTR_GET_USAGE_STATS, Process.myUid(), packageName)
    return mode == MODE_ALLOWED
}

만약 권한이 없다면 설정할 수 있는 액티비티를 실행해 줍니다. 액션이 Settings.ACTION_USAGE_ACCESS_SETTINGS인 인텐트를 실행하면 설정 화면을 띄워줍니다.

if (!checkForPermission()) {
    Log.i(TAG, "The user may not allow the access to apps usage. ")
    Toast.makeText(
        this,
        "Failed to retrieve app usage statistics. " +
                "You may need to enable access for this app through " +
                "Settings > Security > Apps with usage access",
        Toast.LENGTH_LONG
    ).show()
    startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
} else {
    // We have the permission. Query app usage stats.
}

세팅 인텐트를 start하면 다음과 같은 화면이 뜹니다. show usage stats permission settings

자신의 앱을 선택하면 다음과 같이 권한을 부여할 수 있는 화면이 보입니다. Permit usage access를 눌러 권한을 부여합니다. permit usage access

이제 앱은 권한을 갖게 되었습니다. 권한 체크 함수를 다시 실행해보면 true를 리턴해 줍니다.

앱 사용 기록 Query하는 방법

UsageStatsManager을 통해 앱 사용기록을 가져오려면 다음 Query API를 이용해야 합니다. 예를 들어, 인자로 INTERVAL_YEARLY가 전달되면 1년 단위로 묶어서 앱의 사용기록을 정리하여 결과로 리턴해 줍니다. 그리고 쿼리하려는 데이터의 시작 시간과 끝 시간을 인자로 전달해 줍니다. 주석 처럼, 인자가 이렇게 전달되면, 앱들에 대한 사용 기록은 2013~2015 동안 1년 단위로 측정되어 결과로 리턴됩니다.

/*
* intervalType = INTERVAL_YEARLY
* beginTime = 2013
* endTime = 2015 (exclusive)
*
* Results:
* 2013 - com.example.alpha
* 2013 - com.example.beta
* 2014 - com.example.alpha
* 2014 - com.example.beta
*
* @param intervalType The time interval by which the stats are aggregated.
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
* @return A list of {@link UsageStats}
*/
public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime)

Interval type은 다음과 같은 것들이 있습니다.

Interval type
INTERVAL_BEST 주어진 기간에 가장 적합한 타입으로 쿼리
INTERVAL_DAILY 1일 시간 단위로 쿼리
INTERVAL_MONTHLY 1달 시간 단위로 쿼리
INTERVAL_WEEKLY 1주 시간 단위로 쿼리
INTERVAL_YEARLY 1년 시간 단위로 쿼리

그래서, 위의 API와 타입을 이용하여 다음처럼 구현을 할 수 있습니다.

private fun getAppUsageStats(): MutableList<UsageStats> {
    val cal = Calendar.getInstance()
    cal.add(Calendar.YEAR, -1)    // 1

    val usageStatsManager =
        getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager // 2
    val queryUsageStats = usageStatsManager.queryUsageStats(
            UsageStatsManager.INTERVAL_DAILY, cal.timeInMillis, System.currentTimeMillis() // 3
        )
    return queryUsageStats
}

'// 1'처럼 중요한 번호에 순번을 매기고 아래 설명하였습니다.

  1. 현재로부터 1년 전의 시간을 가져옵니다. 1년간의 데이터만 쿼리하기 위해서 입니다.
  2. UsageStatsManager 객체를 가져옵니다.
  3. 인자와 함께 쿼리를 합니다. 인터벌은 DAILY로 설정하였고, 시간 인자는 millis로 전달해야 합니다. 참고로, currentTimeMillis()는 Long을 리턴하며 현실 세계의 현재 시간을 의미합니다. '1970년 1월 1일'을 기준으로 Long 값을 리턴합니다.

다음은 리턴받은 List<UsageStats> 객체를 출력하는 코드입니다. 먼저 저는 가장 최근에 사용된 앱들을 먼저 보여줄 것이기 때문에, 마지막 사용시간으로 sorting을 하였습니다. 그리고 forEach로 모든 데이터를 출력했습니다. 시간은 Date객체를 이용하여 읽기 편하게 만들었습니다.

private fun showAppUsageStats(usageStats: MutableList<UsageStats>) {
    usageStats.sortWith(Comparator { right, left ->
        compareValues(left.lastTimeUsed, right.lastTimeUsed)
    })

    usageStats.forEach { it ->
        Log.d(TAG, "packageName: ${it.packageName}, lastTimeUsed: ${Date(it.lastTimeUsed)}, " +
                "totalTimeInForeground: ${it.totalTimeInForeground}")
    }
}

그럼 이런 식으로 로그가 출력이 됩니다. 앱이 가장 마지막에 사용된 시간은 언제인지, 총 실행시간은 얼마인지 출력하였습니다.

2019-08-11 14:28:49.330 com.codechacha.myapplication D/MainActivity: packageName: com.codechacha.myapplication, lastTimeUsed: Sun Aug 11 14:28:35 GMT+09:00 2019, totalTimeInForeground: 7619163
2019-08-11 14:28:49.330 com.codechacha.myapplication D/MainActivity: packageName: com.google.android.apps.nexuslauncher, lastTimeUsed: Sun Aug 11 14:28:35 GMT+09:00 2019, totalTimeInForeground: 6512061
2019-08-11 14:28:49.331 com.codechacha.myapplication D/MainActivity: packageName: com.android.settings, lastTimeUsed: Sun Aug 11 14:28:13 GMT+09:00 2019, totalTimeInForeground: 2105253
2019-08-11 14:28:49.331 com.codechacha.myapplication D/MainActivity: packageName: com.google.android.packageinstaller, lastTimeUsed: Sun Aug 11 13:58:16 GMT+09:00 2019, totalTimeInForeground: 25370240
....

UsageStats의 데이터들은 아래의 API들을 이용하여 가져올 수 있습니다.

API Returned Data
getPackageName 앱 이름
getLastTimeUsed 마지막으로 사용된 시간
getTotalTimeInForeground Foreground에서 실행된 전체 시간
getAppLaunchCount 실행된 횟수

정리

UsageStatsManager에 대해서 알아보았고, 앱 실행 기록을 가져오기 위해 쿼리하는 방법과 결과를 출력하는 방법을 알아보았습니다.

이 글에서 만든 샘플 코드는 GitHub에서 확인하실 수 있습니다.

구글에서 제공하는 샘플도 참고하시면 좋습니다.

참고