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