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