Merge "Migrate UsageStats to Spa"
This commit is contained in:
@@ -23,9 +23,9 @@ import com.android.settingslib.spa.framework.BrowseActivity
|
||||
class SpaActivity : BrowseActivity(SpaEnvironment.settingsPageProviders) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun startSpaActivity(context: Context, startDestination: String) {
|
||||
fun startSpaActivity(context: Context, destination: String) {
|
||||
val intent = Intent(context, SpaActivity::class.java).apply {
|
||||
putExtra(KEY_DESTINATION, startDestination)
|
||||
putExtra(KEY_DESTINATION, destination)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
49
src/com/android/settings/spa/SpaBridgeActivity.kt
Executable file
49
src/com/android/settings/spa/SpaBridgeActivity.kt
Executable file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.spa
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.ComponentInfoFlags
|
||||
import android.os.Bundle
|
||||
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
|
||||
|
||||
/**
|
||||
* Activity used as a bridge to [SpaActivity].
|
||||
*
|
||||
* Since [SpaActivity] is not exported, [SpaActivity] could not be the target activity of
|
||||
* <activity-alias>, otherwise all its pages will be exported.
|
||||
* So need this bridge activity to sit in the middle of <activity-alias> and [SpaActivity].
|
||||
*/
|
||||
class SpaBridgeActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
getDestination()?.let { destination ->
|
||||
startSpaActivity(this, destination)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun getDestination(): String? =
|
||||
packageManager.getActivityInfo(
|
||||
componentName, ComponentInfoFlags.of(PackageManager.GET_META_DATA.toLong())
|
||||
).metaData.getString(META_DATA_KEY_DESTINATION)
|
||||
|
||||
companion object {
|
||||
private const val META_DATA_KEY_DESTINATION = "com.android.settings.spa.DESTINATION"
|
||||
}
|
||||
}
|
||||
@@ -24,11 +24,12 @@ import com.android.settings.spa.app.specialaccess.MediaManagementAppsAppListProv
|
||||
import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider
|
||||
import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
|
||||
import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider
|
||||
import com.android.settings.spa.development.UsageStatsPageProvider
|
||||
import com.android.settings.spa.home.HomePageProvider
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntryRepository
|
||||
import com.android.settingslib.spa.framework.common.SettingsPage
|
||||
import com.android.settings.spa.notification.AppListNotificationsPageProvider
|
||||
import com.android.settings.spa.notification.NotificationMainPageProvider
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntryRepository
|
||||
import com.android.settingslib.spa.framework.common.SettingsPage
|
||||
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
|
||||
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListTemplate
|
||||
|
||||
@@ -52,6 +53,7 @@ object SpaEnvironment {
|
||||
SpecialAppAccessPageProvider,
|
||||
NotificationMainPageProvider,
|
||||
AppListNotificationsPageProvider,
|
||||
UsageStatsPageProvider,
|
||||
) + togglePermissionAppListTemplate.createPageProviders(),
|
||||
rootPages = listOf(
|
||||
SettingsPage.create(HomePageProvider.name),
|
||||
|
||||
52
src/com/android/settings/spa/development/UsageStats.kt
Normal file
52
src/com/android/settings/spa/development/UsageStats.kt
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.spa.development
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
||||
import com.android.settingslib.spa.framework.compose.navigator
|
||||
import com.android.settingslib.spa.framework.compose.rememberContext
|
||||
import com.android.settingslib.spa.widget.preference.Preference
|
||||
import com.android.settingslib.spa.widget.preference.PreferenceModel
|
||||
import com.android.settingslib.spaprivileged.template.app.AppListItem
|
||||
import com.android.settingslib.spaprivileged.template.app.AppListPage
|
||||
|
||||
object UsageStatsPageProvider : SettingsPageProvider {
|
||||
override val name = "UsageStats"
|
||||
|
||||
@Composable
|
||||
override fun Page(arguments: Bundle?) {
|
||||
AppListPage(
|
||||
title = stringResource(R.string.testing_usage_stats),
|
||||
listModel = rememberContext(::UsageStatsListModel),
|
||||
primaryUserOnly = true,
|
||||
) { itemModel ->
|
||||
AppListItem(itemModel) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EntryItem() {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = stringResource(R.string.testing_usage_stats)
|
||||
override val onClick = navigator(name)
|
||||
})
|
||||
}
|
||||
}
|
||||
101
src/com/android/settings/spa/development/UsageStatsListModel.kt
Normal file
101
src/com/android/settings/spa/development/UsageStatsListModel.kt
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.spa.development
|
||||
|
||||
import android.app.usage.UsageStats
|
||||
import android.app.usage.UsageStatsManager
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import com.android.settings.R
|
||||
import com.android.settings.spa.development.UsageStatsListModel.SpinnerItem.Companion.toSpinnerItem
|
||||
import com.android.settingslib.spa.framework.compose.stateOf
|
||||
import com.android.settingslib.spaprivileged.model.app.AppEntry
|
||||
import com.android.settingslib.spaprivileged.model.app.AppListModel
|
||||
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
||||
import java.text.DateFormat
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
data class UsageStatsAppRecord(
|
||||
override val app: ApplicationInfo,
|
||||
val usageStats: UsageStats?,
|
||||
) : AppRecord
|
||||
|
||||
class UsageStatsListModel(private val context: Context) : AppListModel<UsageStatsAppRecord> {
|
||||
private val usageStatsManager =
|
||||
context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
|
||||
private val now = System.currentTimeMillis()
|
||||
|
||||
override fun transform(
|
||||
userIdFlow: Flow<Int>,
|
||||
appListFlow: Flow<List<ApplicationInfo>>,
|
||||
) = userIdFlow.map { getUsageStats() }
|
||||
.combine(appListFlow) { usageStatsMap, appList ->
|
||||
appList.map { app -> UsageStatsAppRecord(app, usageStatsMap[app.packageName]) }
|
||||
}
|
||||
|
||||
override fun getSpinnerOptions() = SpinnerItem.values().map {
|
||||
context.getString(it.stringResId)
|
||||
}
|
||||
|
||||
override fun filter(
|
||||
userIdFlow: Flow<Int>,
|
||||
option: Int,
|
||||
recordListFlow: Flow<List<UsageStatsAppRecord>>,
|
||||
) = recordListFlow.map { recordList ->
|
||||
recordList.filter { it.usageStats != null }
|
||||
}
|
||||
|
||||
override fun getComparator(option: Int) = when (option.toSpinnerItem()) {
|
||||
SpinnerItem.UsageTime -> compareByDescending { it.record.usageStats?.totalTimeInForeground }
|
||||
SpinnerItem.LastTimeUsed -> compareByDescending { it.record.usageStats?.lastTimeUsed }
|
||||
else -> compareBy<AppEntry<UsageStatsAppRecord>> { 0 }
|
||||
}.then(super.getComparator(option))
|
||||
|
||||
@Composable
|
||||
override fun getSummary(option: Int, record: UsageStatsAppRecord): State<String>? {
|
||||
val usageStats = record.usageStats ?: return null
|
||||
val lastTimeUsed = DateUtils.formatSameDayTime(
|
||||
usageStats.lastTimeUsed, now, DateFormat.MEDIUM, DateFormat.MEDIUM)
|
||||
val lastTimeUsedLine = "${context.getString(R.string.last_time_used_label)}: $lastTimeUsed"
|
||||
val usageTime = DateUtils.formatElapsedTime(usageStats.totalTimeInForeground / 1000)
|
||||
val usageTimeLine = "${context.getString(R.string.usage_time_label)}: $usageTime"
|
||||
return stateOf("$lastTimeUsedLine\n$usageTimeLine")
|
||||
}
|
||||
|
||||
private fun getUsageStats(): Map<String, UsageStats> {
|
||||
val startTime = now - TimeUnit.DAYS.toMillis(5)
|
||||
|
||||
return usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, startTime, now)
|
||||
.groupingBy { it.packageName }.reduce { _, a, b -> a.add(b); a }
|
||||
}
|
||||
|
||||
private enum class SpinnerItem(val stringResId: Int) {
|
||||
UsageTime(R.string.usage_stats_sort_by_usage_time),
|
||||
LastTimeUsed(R.string.usage_stats_sort_by_last_time_used),
|
||||
AppName(R.string.usage_stats_sort_by_app_name);
|
||||
|
||||
companion object {
|
||||
fun Int.toSpinnerItem(): SpinnerItem = values()[this]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user