• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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