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 com.android.settings.R 26 import com.android.settings.spa.development.UsageStatsListModel.SpinnerItem.Companion.toSpinnerItem 27 import com.android.settingslib.spa.widget.ui.SpinnerOption 28 import com.android.settingslib.spaprivileged.model.app.AppEntry 29 import com.android.settingslib.spaprivileged.model.app.AppListModel 30 import com.android.settingslib.spaprivileged.model.app.AppRecord 31 import java.text.DateFormat 32 import java.time.Duration 33 import java.util.concurrent.TimeUnit 34 import kotlinx.coroutines.flow.Flow 35 import kotlinx.coroutines.flow.combine 36 import kotlinx.coroutines.flow.map 37 38 data class UsageStatsAppRecord( 39 override val app: ApplicationInfo, 40 val usageStats: UsageStats?, 41 ) : AppRecord 42 43 class UsageStatsListModel(private val context: Context) : AppListModel<UsageStatsAppRecord> { 44 private val usageStatsManager = 45 context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager 46 private val now = System.currentTimeMillis() 47 48 override fun transform( 49 userIdFlow: Flow<Int>, 50 appListFlow: Flow<List<ApplicationInfo>>, 51 ) = userIdFlow.map { getUsageStats() } 52 .combine(appListFlow) { usageStatsMap, appList -> 53 appList.map { app -> UsageStatsAppRecord(app, usageStatsMap[app.packageName]) } 54 } 55 56 override fun getSpinnerOptions(recordList: List<UsageStatsAppRecord>): List<SpinnerOption> = 57 SpinnerItem.entries.map { 58 SpinnerOption( 59 id = it.ordinal, 60 text = context.getString(it.stringResId), 61 ) 62 } 63 64 override fun filter( 65 userIdFlow: Flow<Int>, 66 option: Int, 67 recordListFlow: Flow<List<UsageStatsAppRecord>>, 68 ) = recordListFlow.map { recordList -> 69 recordList.filter { it.usageStats != null } 70 } 71 72 override fun getComparator(option: Int) = when (option.toSpinnerItem()) { 73 SpinnerItem.UsageTime -> compareByDescending { it.record.usageStats?.totalTimeInForeground } 74 SpinnerItem.LastTimeUsed -> compareByDescending { it.record.usageStats?.lastTimeUsed } 75 else -> compareBy<AppEntry<UsageStatsAppRecord>> { 0 } 76 }.then(super.getComparator(option)) 77 78 @Composable 79 override fun getSummary(option: Int, record: UsageStatsAppRecord): (() -> String)? { 80 val usageStats = record.usageStats ?: return null 81 val lastTimeUsedLine = 82 "${context.getString(R.string.last_time_used_label)}: ${usageStats.getLastUsedString()}" 83 val usageTime = DateUtils.formatElapsedTime(usageStats.totalTimeInForeground / 1000) 84 val usageTimeLine = "${context.getString(R.string.usage_time_label)}: $usageTime" 85 return { "$lastTimeUsedLine\n$usageTimeLine" } 86 } 87 88 private fun UsageStats.getLastUsedString() = when { 89 lastTimeUsed < Duration.ofDays(1) 90 .toMillis() -> context.getString(R.string.last_time_used_never) 91 92 else -> DateUtils.formatSameDayTime( 93 lastTimeUsed, 94 now, 95 DateFormat.MEDIUM, 96 DateFormat.MEDIUM 97 ) 98 } 99 100 private fun getUsageStats(): Map<String, UsageStats> { 101 val startTime = now - TimeUnit.DAYS.toMillis(5) 102 103 return usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, startTime, now) 104 .groupingBy { it.packageName }.reduce { _, a, b -> a.add(b); a } 105 } 106 107 private enum class SpinnerItem(val stringResId: Int) { 108 UsageTime(R.string.usage_stats_sort_by_usage_time), 109 LastTimeUsed(R.string.usage_stats_sort_by_last_time_used), 110 AppName(R.string.usage_stats_sort_by_app_name); 111 112 companion object { 113 fun Int.toSpinnerItem(): SpinnerItem = entries[this] 114 } 115 } 116 } 117