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