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하면 다음과 같은 화면이 뜹니다.
자신의 앱을 선택하면 다음과 같이 권한을 부여할 수 있는 화면이 보입니다. 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년간의 데이터만 쿼리하기 위해서 입니다.
- UsageStatsManager 객체를 가져옵니다.
- 인자와 함께 쿼리를 합니다. 인터벌은 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에서 확인하실 수 있습니다.
구글에서 제공하는 샘플도 참고하시면 좋습니다.
참고
Related Posts
- Android 14 - 사진/동영상 파일, 일부 접근 권한 소개
- Android - adb push, pull로 파일 복사, 다운로드
- Android 14 - 암시적 인텐트 변경사항 및 문제 해결
- Jetpack Compose - Row와 Column
- Android 13, AOSP 오픈소스 다운로드 및 빌드
- Android 13 - 세분화된 미디어 파일 권한
- Android 13에서 Notification 권한 요청, 알림 띄우기
- Android 13에서 'Access blocked: ComponentInfo' 에러 해결
- 에러 해결: android gradle plugin requires java 11 to run. you are currently using java 1.8.
- 안드로이드 - 코루틴과 Retrofit으로 비동기 통신 예제
- 안드로이드 - 코루틴으로 URL 이미지 불러오기
- Android - 진동, Vibrator, VibrationEffect 예제
- Some problems were found with the configuration of task 에러 수정
- Query method parameters should either be a type that can be converted into a database column or a List
- 우분투에서 Android 12 오픈소스 다운로드 및 빌드
- Android - ViewModel을 생성하는 방법
- Android - Transformations.map(), switchMap() 차이점
- Android - Transformations.distinctUntilChanged() 소개
- Android - TabLayout 구현 방법 (+ ViewPager2)
- Android - 휴대폰 전화번호 가져오는 방법
- Android 12 - Splash Screens 알아보기
- Android 12 - Incremental Install (Play as you Download) 소개
- Android - adb 명령어로 bugreport 로그 파일 추출
- Android - adb 명령어로 App 데이터 삭제
- Android - adb 명령어로 앱 비활성화, 활성화
- Android - adb 명령어로 특정 패키지의 PID 찾기
- Android - adb 명령어로 퍼미션 Grant 또는 Revoke
- Android - adb 명령어로 apk 설치, 삭제
- Android - adb 명령어로 특정 패키지의 프로세스 종료
- Android - adb 명령어로 screen capture 저장
- Android - adb 명령어로 System 앱 삭제, 설치
- Android - adb 명령어로 settings value 확인, 변경
- Android 12 - IntentFilter의 exported 명시적 선언
- Android - adb 명령어로 공장초기화(Factory reset)
- Android - adb logcat 명령어로 로그 출력