1 /* 2 * Copyright (C) 2021 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.applications; 18 19 import static com.android.settings.spa.app.appinfo.AppInfoSettingsProvider.startAppInfoSettings; 20 21 import android.app.Application; 22 import android.app.usage.UsageStats; 23 import android.content.Context; 24 import android.icu.text.RelativeDateTimeFormatter; 25 import android.text.TextUtils; 26 import android.util.ArrayMap; 27 28 import androidx.annotation.VisibleForTesting; 29 import androidx.fragment.app.Fragment; 30 import androidx.lifecycle.Lifecycle; 31 import androidx.lifecycle.LifecycleObserver; 32 import androidx.lifecycle.OnLifecycleEvent; 33 import androidx.preference.Preference; 34 import androidx.preference.PreferenceCategory; 35 import androidx.preference.PreferenceScreen; 36 37 import com.android.settings.R; 38 import com.android.settings.core.BasePreferenceController; 39 import com.android.settingslib.Utils; 40 import com.android.settingslib.applications.ApplicationsState; 41 import com.android.settingslib.utils.StringUtil; 42 import com.android.settingslib.widget.AppPreference; 43 44 import java.util.List; 45 import java.util.Map; 46 47 /** 48 * This controller displays up to four recently used apps. 49 * If there is no recently used app, we only show up an "App Info" preference. 50 */ 51 public class AppsPreferenceController extends BasePreferenceController implements 52 LifecycleObserver { 53 54 public static final int SHOW_RECENT_APP_COUNT = 4; 55 56 @VisibleForTesting 57 static final String KEY_RECENT_APPS_CATEGORY = "recent_apps_category"; 58 @VisibleForTesting 59 static final String KEY_GENERAL_CATEGORY = "general_category"; 60 @VisibleForTesting 61 static final String KEY_ALL_APP_INFO = "all_app_infos"; 62 @VisibleForTesting 63 static final String KEY_SEE_ALL = "see_all_apps"; 64 65 private final ApplicationsState mApplicationsState; 66 67 @VisibleForTesting 68 List<RecentAppStatsMixin.UsageStatsWrapper> mRecentApps; 69 @VisibleForTesting 70 PreferenceCategory mRecentAppsCategory; 71 @VisibleForTesting 72 PreferenceCategory mGeneralCategory; 73 @VisibleForTesting 74 Preference mAllAppsInfoPref; 75 @VisibleForTesting 76 Preference mSeeAllPref; 77 78 private Fragment mHost; 79 private boolean mInitialLaunch = false; 80 AppsPreferenceController(Context context)81 public AppsPreferenceController(Context context) { 82 super(context, KEY_RECENT_APPS_CATEGORY); 83 mApplicationsState = ApplicationsState.getInstance( 84 (Application) mContext.getApplicationContext()); 85 } 86 setFragment(Fragment fragment)87 public void setFragment(Fragment fragment) { 88 mHost = fragment; 89 } 90 91 @Override getAvailabilityStatus()92 public int getAvailabilityStatus() { 93 return AVAILABLE_UNSEARCHABLE; 94 } 95 96 @Override displayPreference(PreferenceScreen screen)97 public void displayPreference(PreferenceScreen screen) { 98 super.displayPreference(screen); 99 initPreferences(screen); 100 refreshUi(); 101 mInitialLaunch = true; 102 } 103 104 @Override updateState(Preference preference)105 public void updateState(Preference preference) { 106 super.updateState(preference); 107 if (!mInitialLaunch) { 108 refreshUi(); 109 } 110 } 111 112 /** 113 * Called when the apps page pauses. 114 */ 115 @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) onPause()116 public void onPause() { 117 mInitialLaunch = false; 118 } 119 120 @VisibleForTesting refreshUi()121 void refreshUi() { 122 loadAllAppsCount(); 123 mRecentApps = loadRecentApps(); 124 if (!mRecentApps.isEmpty()) { 125 displayRecentApps(); 126 mAllAppsInfoPref.setVisible(false); 127 mRecentAppsCategory.setVisible(true); 128 mGeneralCategory.setVisible(true); 129 mSeeAllPref.setVisible(true); 130 } else { 131 mAllAppsInfoPref.setVisible(true); 132 mRecentAppsCategory.setVisible(false); 133 mGeneralCategory.setVisible(false); 134 mSeeAllPref.setVisible(false); 135 } 136 } 137 138 @VisibleForTesting loadAllAppsCount()139 void loadAllAppsCount() { 140 // Show total number of installed apps as See all's summary. 141 new InstalledAppCounter(mContext, InstalledAppCounter.IGNORE_INSTALL_REASON, 142 mContext.getPackageManager()) { 143 @Override 144 protected void onCountComplete(int num) { 145 if (!mRecentApps.isEmpty()) { 146 mSeeAllPref.setTitle(StringUtil.getIcuPluralsString(mContext, num, 147 R.string.see_all_apps_title)); 148 } else { 149 mAllAppsInfoPref.setSummary(mContext.getString(R.string.apps_summary, num)); 150 } 151 } 152 }.execute(); 153 } 154 155 @VisibleForTesting loadRecentApps()156 List<RecentAppStatsMixin.UsageStatsWrapper> loadRecentApps() { 157 final RecentAppStatsMixin recentAppStatsMixin = new RecentAppStatsMixin(mContext, 158 SHOW_RECENT_APP_COUNT); 159 recentAppStatsMixin.loadDisplayableRecentApps(SHOW_RECENT_APP_COUNT); 160 return recentAppStatsMixin.mRecentApps; 161 } 162 initPreferences(PreferenceScreen screen)163 private void initPreferences(PreferenceScreen screen) { 164 mRecentAppsCategory = screen.findPreference(KEY_RECENT_APPS_CATEGORY); 165 mGeneralCategory = screen.findPreference(KEY_GENERAL_CATEGORY); 166 mAllAppsInfoPref = screen.findPreference(KEY_ALL_APP_INFO); 167 mSeeAllPref = screen.findPreference(KEY_SEE_ALL); 168 mRecentAppsCategory.setVisible(false); 169 mGeneralCategory.setVisible(false); 170 mAllAppsInfoPref.setVisible(false); 171 mSeeAllPref.setVisible(false); 172 } 173 displayRecentApps()174 private void displayRecentApps() { 175 if (mRecentAppsCategory != null) { 176 final Map<String, Preference> existedAppPreferences = new ArrayMap<>(); 177 final int prefCount = mRecentAppsCategory.getPreferenceCount(); 178 for (int i = 0; i < prefCount; i++) { 179 final Preference pref = mRecentAppsCategory.getPreference(i); 180 final String key = pref.getKey(); 181 if (!TextUtils.equals(key, KEY_SEE_ALL)) { 182 existedAppPreferences.put(key, pref); 183 } 184 } 185 186 int showAppsCount = 0; 187 for (RecentAppStatsMixin.UsageStatsWrapper statsWrapper : mRecentApps) { 188 final UsageStats stats = statsWrapper.mUsageStats; 189 final String pkgName = statsWrapper.mUsageStats.getPackageName(); 190 final String key = pkgName + statsWrapper.mUserId; 191 final ApplicationsState.AppEntry appEntry = 192 mApplicationsState.getEntry(pkgName, statsWrapper.mUserId); 193 if (appEntry == null) { 194 continue; 195 } 196 197 boolean rebindPref = true; 198 Preference pref = existedAppPreferences.remove(key); 199 if (pref == null) { 200 pref = new AppPreference(mContext); 201 rebindPref = false; 202 } 203 204 pref.setKey(key); 205 pref.setTitle(appEntry.label); 206 pref.setIcon(Utils.getBadgedIcon(mContext, appEntry.info)); 207 pref.setSummary(StringUtil.formatRelativeTime(mContext, 208 System.currentTimeMillis() - stats.getLastTimeUsed(), false, 209 RelativeDateTimeFormatter.Style.LONG)); 210 pref.setOrder(showAppsCount++); 211 pref.setOnPreferenceClickListener(preference -> { 212 startAppInfoSettings(pkgName, appEntry.info.uid, 213 mHost, 1001 /*RequestCode*/, getMetricsCategory()); 214 return true; 215 }); 216 217 if (!rebindPref) { 218 mRecentAppsCategory.addPreference(pref); 219 } 220 } 221 222 // Remove unused preferences from pref category. 223 for (Preference unusedPref : existedAppPreferences.values()) { 224 mRecentAppsCategory.removePreference(unusedPref); 225 } 226 } 227 } 228 } 229