diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2fe830372e2..bbb8eea9700 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2911,15 +2911,6 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/res/layout/usage_stats_item.xml b/res/layout/usage_stats_item.xml
deleted file mode 100755
index 2879df072e5..00000000000
--- a/res/layout/usage_stats_item.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 53acc14f4af..35d246236f7 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -261,13 +261,6 @@
- Excellent
-
-
- - Usage time
- - Last time used
- - App name
-
-
- MSCHAPV2
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b8898186db6..8bdf8bd74d5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4555,15 +4555,14 @@
Always allow %1$s to create widgets and access their data
-
- Usage statistics
-
Usage statistics
-
- Sort by:
-
- App
+
+ Sort by usage time
+
+ Sort by last time used
+
+ Sort by app name
Last time used
diff --git a/src/com/android/settings/UsageStatsActivity.java b/src/com/android/settings/UsageStatsActivity.java
deleted file mode 100755
index d9803ddfb93..00000000000
--- a/src/com/android/settings/UsageStatsActivity.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/**
- * Copyright (C) 2007 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;
-
-import android.app.Activity;
-import android.app.usage.UsageStats;
-import android.app.usage.UsageStatsManager;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Bundle;
-import android.text.format.DateUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.BaseAdapter;
-import android.widget.ListView;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Activity to display package usage statistics.
- */
-public class UsageStatsActivity extends Activity implements OnItemSelectedListener {
- private static final String TAG = "UsageStatsActivity";
- private static final boolean localLOGV = false;
- private UsageStatsManager mUsageStatsManager;
- private LayoutInflater mInflater;
- private UsageStatsAdapter mAdapter;
- private PackageManager mPm;
-
- public static class AppNameComparator implements Comparator {
- private Map mAppLabelList;
-
- AppNameComparator(Map appList) {
- mAppLabelList = appList;
- }
-
- @Override
- public final int compare(UsageStats a, UsageStats b) {
- String alabel = mAppLabelList.get(a.getPackageName());
- String blabel = mAppLabelList.get(b.getPackageName());
- return alabel.compareTo(blabel);
- }
- }
-
- public static class LastTimeUsedComparator implements Comparator {
- @Override
- public final int compare(UsageStats a, UsageStats b) {
- // return by descending order
- return Long.compare(b.getLastTimeUsed(), a.getLastTimeUsed());
- }
- }
-
- public static class UsageTimeComparator implements Comparator {
- @Override
- public final int compare(UsageStats a, UsageStats b) {
- return Long.compare(b.getTotalTimeInForeground(), a.getTotalTimeInForeground());
- }
- }
-
- // View Holder used when displaying views
- static class AppViewHolder {
- TextView pkgName;
- TextView lastTimeUsed;
- TextView usageTime;
- }
-
- class UsageStatsAdapter extends BaseAdapter {
- // Constants defining order for display order
- private static final int _DISPLAY_ORDER_USAGE_TIME = 0;
- private static final int _DISPLAY_ORDER_LAST_TIME_USED = 1;
- private static final int _DISPLAY_ORDER_APP_NAME = 2;
-
- private int mDisplayOrder = _DISPLAY_ORDER_USAGE_TIME;
- private LastTimeUsedComparator mLastTimeUsedComparator = new LastTimeUsedComparator();
- private UsageTimeComparator mUsageTimeComparator = new UsageTimeComparator();
- private AppNameComparator mAppLabelComparator;
- private final ArrayMap mAppLabelMap = new ArrayMap<>();
- private final ArrayList mPackageStats = new ArrayList<>();
-
- UsageStatsAdapter() {
- Calendar cal = Calendar.getInstance();
- cal.add(Calendar.DAY_OF_YEAR, -5);
-
- final List stats =
- mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST,
- cal.getTimeInMillis(), System.currentTimeMillis());
- if (stats == null) {
- return;
- }
-
- ArrayMap map = new ArrayMap<>();
- final int statCount = stats.size();
- for (int i = 0; i < statCount; i++) {
- final android.app.usage.UsageStats pkgStats = stats.get(i);
-
- // load application labels for each application
- try {
- ApplicationInfo appInfo = mPm.getApplicationInfo(pkgStats.getPackageName(), 0);
- String label = appInfo.loadLabel(mPm).toString();
- mAppLabelMap.put(pkgStats.getPackageName(), label);
-
- UsageStats existingStats =
- map.get(pkgStats.getPackageName());
- if (existingStats == null) {
- map.put(pkgStats.getPackageName(), pkgStats);
- } else {
- existingStats.add(pkgStats);
- }
-
- } catch (NameNotFoundException e) {
- // This package may be gone.
- }
- }
- mPackageStats.addAll(map.values());
-
- // Sort list
- mAppLabelComparator = new AppNameComparator(mAppLabelMap);
- sortList();
- }
-
- @Override
- public int getCount() {
- return mPackageStats.size();
- }
-
- @Override
- public Object getItem(int position) {
- return mPackageStats.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // A ViewHolder keeps references to children views to avoid unneccessary calls
- // to findViewById() on each row.
- AppViewHolder holder;
-
- // When convertView is not null, we can reuse it directly, there is no need
- // to reinflate it. We only inflate a new View when the convertView supplied
- // by ListView is null.
- if (convertView == null) {
- convertView = mInflater.inflate(R.layout.usage_stats_item, null);
-
- // Creates a ViewHolder and store references to the two children views
- // we want to bind data to.
- holder = new AppViewHolder();
- holder.pkgName = (TextView) convertView.findViewById(R.id.package_name);
- holder.lastTimeUsed = (TextView) convertView.findViewById(R.id.last_time_used);
- holder.usageTime = (TextView) convertView.findViewById(R.id.usage_time);
- convertView.setTag(holder);
- } else {
- // Get the ViewHolder back to get fast access to the TextView
- // and the ImageView.
- holder = (AppViewHolder) convertView.getTag();
- }
-
- // Bind the data efficiently with the holder
- UsageStats pkgStats = mPackageStats.get(position);
- if (pkgStats != null) {
- String label = mAppLabelMap.get(pkgStats.getPackageName());
- holder.pkgName.setText(label);
- holder.lastTimeUsed.setText(DateUtils.formatSameDayTime(pkgStats.getLastTimeUsed(),
- System.currentTimeMillis(), DateFormat.MEDIUM, DateFormat.MEDIUM));
- holder.usageTime.setText(
- DateUtils.formatElapsedTime(pkgStats.getTotalTimeInForeground() / 1000));
- } else {
- Log.w(TAG, "No usage stats info for package:" + position);
- }
- return convertView;
- }
-
- void sortList(int sortOrder) {
- if (mDisplayOrder == sortOrder) {
- // do nothing
- return;
- }
- mDisplayOrder= sortOrder;
- sortList();
- }
- private void sortList() {
- if (mDisplayOrder == _DISPLAY_ORDER_USAGE_TIME) {
- if (localLOGV) Log.i(TAG, "Sorting by usage time");
- Collections.sort(mPackageStats, mUsageTimeComparator);
- } else if (mDisplayOrder == _DISPLAY_ORDER_LAST_TIME_USED) {
- if (localLOGV) Log.i(TAG, "Sorting by last time used");
- Collections.sort(mPackageStats, mLastTimeUsedComparator);
- } else if (mDisplayOrder == _DISPLAY_ORDER_APP_NAME) {
- if (localLOGV) Log.i(TAG, "Sorting by application name");
- Collections.sort(mPackageStats, mAppLabelComparator);
- }
- notifyDataSetChanged();
- }
- }
-
- /** Called when the activity is first created. */
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- setContentView(R.layout.usage_stats);
-
- mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
- mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mPm = getPackageManager();
-
- Spinner typeSpinner = (Spinner) findViewById(R.id.typeSpinner);
- typeSpinner.setOnItemSelectedListener(this);
-
- ListView listView = (ListView) findViewById(R.id.pkg_list);
- mAdapter = new UsageStatsAdapter();
- listView.setAdapter(mAdapter);
- }
-
- @Override
- public void onItemSelected(AdapterView> parent, View view, int position, long id) {
- mAdapter.sortList(position);
- }
-
- @Override
- public void onNothingSelected(AdapterView> parent) {
- // do nothing
- }
-}
diff --git a/src/com/android/settings/spa/SpaActivity.kt b/src/com/android/settings/spa/SpaActivity.kt
index fab0e081f65..f161c441fd6 100644
--- a/src/com/android/settings/spa/SpaActivity.kt
+++ b/src/com/android/settings/spa/SpaActivity.kt
@@ -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)
}
diff --git a/src/com/android/settings/spa/SpaBridgeActivity.kt b/src/com/android/settings/spa/SpaBridgeActivity.kt
new file mode 100755
index 00000000000..9e433e449ca
--- /dev/null
+++ b/src/com/android/settings/spa/SpaBridgeActivity.kt
@@ -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
+ * , otherwise all its pages will be exported.
+ * So need this bridge activity to sit in the middle of 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"
+ }
+}
diff --git a/src/com/android/settings/spa/SpaEnvironment.kt b/src/com/android/settings/spa/SpaEnvironment.kt
index 8c88a03cd7e..ad9999ae9c9 100644
--- a/src/com/android/settings/spa/SpaEnvironment.kt
+++ b/src/com/android/settings/spa/SpaEnvironment.kt
@@ -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),
diff --git a/src/com/android/settings/spa/development/UsageStats.kt b/src/com/android/settings/spa/development/UsageStats.kt
new file mode 100644
index 00000000000..302f2010b31
--- /dev/null
+++ b/src/com/android/settings/spa/development/UsageStats.kt
@@ -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)
+ })
+ }
+}
diff --git a/src/com/android/settings/spa/development/UsageStatsListModel.kt b/src/com/android/settings/spa/development/UsageStatsListModel.kt
new file mode 100644
index 00000000000..caa30cc2535
--- /dev/null
+++ b/src/com/android/settings/spa/development/UsageStatsListModel.kt
@@ -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 {
+ private val usageStatsManager =
+ context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
+ private val now = System.currentTimeMillis()
+
+ override fun transform(
+ userIdFlow: Flow,
+ appListFlow: Flow>,
+ ) = 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,
+ option: Int,
+ recordListFlow: Flow>,
+ ) = 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> { 0 }
+ }.then(super.getComparator(option))
+
+ @Composable
+ override fun getSummary(option: Int, record: UsageStatsAppRecord): State? {
+ 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 {
+ 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]
+ }
+ }
+}