1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.spa.development 18 19 import android.app.usage.UsageStats 20 import android.app.usage.UsageStatsManager 21 import android.content.Context 22 import android.content.pm.ApplicationInfo 23 import android.text.format.DateUtils 24 import androidx.compose.runtime.Composable 25 import androidx.compose.runtime.State 26 import com.android.settings.R 27 import com.android.settings.spa.development.UsageStatsListModel.SpinnerItem.Companion.toSpinnerItem 28 import com.android.settingslib.spa.framework.compose.stateOf 29 import com.android.settingslib.spa.widget.ui.SpinnerOption 30 import com.android.settingslib.spaprivileged.model.app.AppEntry 31 import com.android.settingslib.spaprivileged.model.app.AppListModel 32 import com.android.settingslib.spaprivileged.model.app.AppRecord 33 import java.text.DateFormat 34 import java.util.concurrent.TimeUnit 35 import kotlinx.coroutines.flow.Flow 36 import kotlinx.coroutines.flow.combine 37 import kotlinx.coroutines.flow.map 38 39 data class UsageStatsAppRecord( 40 override val app: ApplicationInfo, 41 val usageStats: UsageStats?, 42 ) : AppRecord 43 44 class UsageStatsListModel(private val context: Context) : AppListModel<UsageStatsAppRecord> { 45 private val usageStatsManager = 46 context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager 47 private val now = System.currentTimeMillis() 48 49 override fun transform( 50 userIdFlow: Flow<Int>, 51 appListFlow: Flow<List<ApplicationInfo>>, 52 ) = userIdFlow.map { getUsageStats() } 53 .combine(appListFlow) { usageStatsMap, appList -> 54 appList.map { app -> UsageStatsAppRecord(app, usageStatsMap[app.packageName]) } 55 } 56 57 override fun getSpinnerOptions(recordList: List<UsageStatsAppRecord>): List<SpinnerOption> = 58 SpinnerItem.values().map { 59 SpinnerOption( 60 id = it.ordinal, 61 text = context.getString(it.stringResId), 62 ) 63 } 64 65 override fun filter( 66 userIdFlow: Flow<Int>, 67 option: Int, 68 recordListFlow: Flow<List<UsageStatsAppRecord>>, 69 ) = recordListFlow.map { recordList -> 70 recordList.filter { it.usageStats != null } 71 } 72 73 override fun getComparator(option: Int) = when (option.toSpinnerItem()) { 74 SpinnerItem.UsageTime -> compareByDescending { it.record.usageStats?.totalTimeInForeground } 75 SpinnerItem.LastTimeUsed -> compareByDescending { it.record.usageStats?.lastTimeUsed } 76 else -> compareBy<AppEntry<UsageStatsAppRecord>> { 0 } 77 }.then(super.getComparator(option)) 78 79 @Composable 80 override fun getSummary(option: Int, record: UsageStatsAppRecord): State<String>? { 81 val usageStats = record.usageStats ?: return null 82 val lastTimeUsed = DateUtils.formatSameDayTime( 83 usageStats.lastTimeUsed, now, DateFormat.MEDIUM, DateFormat.MEDIUM 84 ) 85 val lastTimeUsedLine = "${context.getString(R.string.last_time_used_label)}: $lastTimeUsed" 86 val usageTime = DateUtils.formatElapsedTime(usageStats.totalTimeInForeground / 1000) 87 val usageTimeLine = "${context.getString(R.string.usage_time_label)}: $usageTime" 88 return stateOf("$lastTimeUsedLine\n$usageTimeLine") 89 } 90 91 private fun getUsageStats(): Map<String, UsageStats> { 92 val startTime = now - TimeUnit.DAYS.toMillis(5) 93 94 return usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, startTime, now) 95 .groupingBy { it.packageName }.reduce { _, a, b -> a.add(b); a } 96 } 97 98 private enum class SpinnerItem(val stringResId: Int) { 99 UsageTime(R.string.usage_stats_sort_by_usage_time), 100 LastTimeUsed(R.string.usage_stats_sort_by_last_time_used), 101 AppName(R.string.usage_stats_sort_by_app_name); 102 103 companion object { 104 fun Int.toSpinnerItem(): SpinnerItem = values()[this] 105 } 106 } 107 } 108