Files
assistant-android/app/src/main/java/com/gh/common/util/UsageStatsHelper.kt
2021-01-13 17:24:18 +08:00

270 lines
10 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@file:Suppress("DEPRECATED_IDENTITY_EQUALS")
package com.gh.common.util
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Activity
import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.OPSTR_GET_USAGE_STATS
import android.app.usage.UsageEvents
import android.app.usage.UsageStatsManager
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Process
import android.preference.PreferenceManager
import android.provider.Settings
import androidx.annotation.RequiresApi
import com.gh.common.util.UsageStatsHelper.getDataByUsageEvents
import com.gh.common.util.UsageStatsHelper.getDataByUsageStats
import com.gh.gamecenter.manager.UserManager
import com.gh.gamecenter.retrofit.BiResponse
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import okhttp3.MediaType
import okhttp3.RequestBody
import okhttp3.ResponseBody
import org.json.JSONArray
import org.json.JSONObject
import java.util.*
/**
* 参考资料: https://developer.android.com/reference/android/app/usage/UsageStatsManager
*
* queryUsageStats 无法准确获取设定时间内的使用情况,所以以下统计有两个策略(但依然无法解决queryUsageStats初始取值问题)
*
* [getDataByUsageStats]:用于获取时间跨度较大的数据(至少跨度一天)
* [getDataByUsageEvents]:用于获取时间跨度较小的数据(只用于获取当天数据)
*
* 详情可见https://stackoverflow.com/questions/36238481/android-usagestatsmanager-not-returning-correct-daily-results
*
*/
object UsageStatsHelper {
const val USAGE_STATUS_REQUEST_CODE = 233
const val USAGE_STATUS_SP_KEY = "usage_status_sp_key"
private val mApi = RetrofitManager.getInstance(HaloApp.getInstance().application).api
private val mPreference = PreferenceManager.getDefaultSharedPreferences(HaloApp.getInstance().application)
/**
* 一般用于获取时间跨度较大的数据
*
* 当天数据获取是有问题的
* 例如: 设置起始时间为上午10点当时返回的数据还是包括更前的数据例如9点
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private fun getDataByUsageStats(lastPostTime: Long): JSONArray? {
val usageStatsManager = HaloApp
.getInstance().application
.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val usageStatsMap = usageStatsManager.queryAndAggregateUsageStats(
lastPostTime,
System.currentTimeMillis()) ?: return null
val postBody = JSONArray()
for (entry in usageStatsMap) {
if (entry.value.totalTimeInForeground != 0L) {
val jsonObject = JSONObject()
jsonObject.put("package", entry.key)
jsonObject.put("duration", (entry.value.totalTimeInForeground / 1000))
postBody.put(jsonObject)
debugOnly {
Utils.log("UsageStats post log==>" + entry.key + ":" + (entry.value.totalTimeInForeground / 1000))
}
}
}
return postBody
}
/**
* 一般用于获取时间跨度较小的数据
* 在此功能只用来获取当天数据(queryEvents 获取的数据只保留几天内的数据)
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private fun getDataByUsageEvents(startTime: Long): JSONArray? {
val mUsageStatsManager = HaloApp
.getInstance()
.application
.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val usageEvents = mUsageStatsManager.queryEvents(startTime, System.currentTimeMillis())
val allEvents = ArrayList<UsageEvents.Event>()
var currentEvent: UsageEvents.Event
while (usageEvents.hasNextEvent()) {
currentEvent = UsageEvents.Event()
usageEvents.getNextEvent(currentEvent)
if (currentEvent.eventType == UsageEvents.Event.MOVE_TO_FOREGROUND
|| currentEvent.eventType == UsageEvents.Event.MOVE_TO_BACKGROUND) {
allEvents.add(currentEvent)
}
}
val pakAndTime = HashMap<String, Long>()
for (i in 0 until allEvents.size - 1) {
val curEvent = allEvents[i]
val nextEvent = allEvents[i + 1]
if (curEvent.eventType == UsageEvents.Event.MOVE_TO_FOREGROUND
&& nextEvent.eventType == UsageEvents.Event.MOVE_TO_BACKGROUND
&& curEvent.className == nextEvent.className) {
val diff = nextEvent.timeStamp - curEvent.timeStamp
if (pakAndTime[curEvent.packageName] == null) {
pakAndTime[curEvent.packageName] = diff
} else {
pakAndTime[curEvent.packageName] = pakAndTime[curEvent.packageName]!! + diff
}
}
}
val postBody = JSONArray()
for (pkg in pakAndTime.keys) {
val totalTime = pakAndTime[pkg] ?: return null
val jsonObject = JSONObject()
jsonObject.put("package", pkg)
jsonObject.put("duration", (totalTime / 1000))
postBody.put(jsonObject)
Utils.log("UsageStats getDataByUsageEvents->" + pkg + " :" + (totalTime / 1000))
}
return postBody
}
/**
* 第一次统计默认起始时间
*/
private fun getDefaultBeginTime(): Long {
val calendar = Calendar.getInstance()
calendar.add(Calendar.YEAR, -5)
return calendar.timeInMillis
}
@SuppressLint("CheckResult")
@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
private fun postUsageStats(beginTime: Long) {
debugOnly {
Utils.log("UsageStats->beginTime:$beginTime endTime:" + System.currentTimeMillis())
}
val postBody = if (isSameDay(beginTime, System.currentTimeMillis())) {
getDataByUsageEvents(beginTime)
} else {
getDataByUsageStats(beginTime)
}
if (postBody == null || postBody.length() == 0) {
debugOnly {
Utils.log("UsageStats: 没有可上传的数据")
}
return
}
val body = RequestBody.create(MediaType.parse("application/json"), postBody.toString())
mApi.postUsageStatus(body, UserManager.getInstance().userId)
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
debugOnly {
Utils.log("UsageStats: 数据上传成功")
}
}
})
}
@JvmStatic
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
fun checkForPermission(): Boolean {
val appOps = HaloApp
.getInstance().application
.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
val mode = appOps.checkOpNoThrow(
OPSTR_GET_USAGE_STATS,
Process.myUid(),
HaloApp.getInstance().application.packageName)
return mode == MODE_ALLOWED
}
@JvmStatic
@SuppressLint("CheckResult")
fun checkAndPostUsageStats() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
debugOnly {
Utils.log("UsageStats checkAndPostUsageStats Android 版本小于22")
}
return
}
if (!checkForPermission()) {
debugOnly {
Utils.log("UsageStats checkAndPostUsageStats 没有授予权限")
}
return
}
if (!mPreference.getBoolean(USAGE_STATUS_SP_KEY, true)) {
debugOnly {
Utils.log("UsageStats checkAndPostUsageStats 有权限,但用户在设置关闭统计")
}
return
}
mApi.getUsageStatusUpdateTime(HaloApp.getInstance().gid)
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
val body = JSONObject(data.string())
val lastPostTime = body.getLong("update_time") * 1000
val beginTime = if (lastPostTime == 0L) {
getDefaultBeginTime()
} else {
lastPostTime
}
postUsageStats(beginTime)
}
})
}
private fun isSameDay(timeA: Long, timeB: Long): Boolean {
if (timeA == timeB) {
return true
}
val calendarB = Calendar.getInstance()
calendarB.time = Date(timeB)
val calendarA = Calendar.getInstance()
calendarA.time = Date(timeA)
return (calendarB.get(Calendar.YEAR) === calendarA.get(Calendar.YEAR)
&& calendarB.get(Calendar.MONTH) === calendarA.get(Calendar.MONTH)
&& calendarB.get(Calendar.DATE) === calendarA.get(Calendar.DATE))
}
@JvmStatic
fun skipToUsageStats(context: Context, requestCode: Int = -1) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
if (context is Activity && requestCode != -1) {
context.startActivityForResult(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS), requestCode)
} else {
context.startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
}
} catch (e: ActivityNotFoundException) {
Utils.toast(context, "当前设备不支持直接跳转查看应用使用情况页面,请手动进入并打开")
e.printStackTrace()
}
} else {
Utils.toast(context, "当前设备不支持查看应用使用情况")
}
}
}