270 lines
10 KiB
Kotlin
270 lines
10 KiB
Kotlin
@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, "当前设备不支持查看应用使用情况")
|
||
}
|
||
}
|
||
} |