• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.Utils.SETTINGS_PACKAGE_NAME;
20 
21 import android.app.Application;
22 import android.app.usage.UsageStats;
23 import android.app.usage.UsageStatsManager;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.PackageManager;
27 import android.os.PowerManager;
28 import android.os.UserHandle;
29 import android.util.ArrayMap;
30 import android.util.ArraySet;
31 import android.util.Log;
32 
33 import androidx.annotation.NonNull;
34 import androidx.annotation.VisibleForTesting;
35 
36 import com.android.settingslib.applications.AppUtils;
37 import com.android.settingslib.applications.ApplicationsState;
38 import com.android.settingslib.core.lifecycle.LifecycleObserver;
39 import com.android.settingslib.core.lifecycle.events.OnStart;
40 import com.android.settingslib.utils.ThreadUtils;
41 
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Calendar;
45 import java.util.Collections;
46 import java.util.Comparator;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 
51 
52 public class RecentAppStatsMixin implements Comparator<UsageStats>, LifecycleObserver, OnStart {
53 
54     private static final String TAG = "RecentAppStatsMixin";
55     private static final Set<String> SKIP_SYSTEM_PACKAGES = new ArraySet<>();
56 
57     @VisibleForTesting
58     final List<UsageStats> mRecentApps;
59     private final int mUserId;
60     private final int mMaximumApps;
61     private final Context mContext;
62     private final PackageManager mPm;
63     private final PowerManager mPowerManager;;
64     private final UsageStatsManager mUsageStatsManager;
65     private final ApplicationsState mApplicationsState;
66     private final List<RecentAppStatsListener> mAppStatsListeners;
67     private Calendar mCalendar;
68 
69     static {
70         SKIP_SYSTEM_PACKAGES.addAll(Arrays.asList(
71                 "android",
72                 "com.android.phone",
73                 SETTINGS_PACKAGE_NAME,
74                 "com.android.systemui",
75                 "com.android.providers.calendar",
76                 "com.android.providers.media"
77         ));
78     }
79 
RecentAppStatsMixin(Context context, int maximumApps)80     public RecentAppStatsMixin(Context context, int maximumApps) {
81         mContext = context;
82         mMaximumApps = maximumApps;
83         mUserId = UserHandle.myUserId();
84         mPm = mContext.getPackageManager();
85         mPowerManager = mContext.getSystemService(PowerManager.class);
86         mUsageStatsManager = mContext.getSystemService(UsageStatsManager.class);
87         mApplicationsState = ApplicationsState.getInstance(
88                 (Application) mContext.getApplicationContext());
89         mRecentApps = new ArrayList<>();
90         mAppStatsListeners = new ArrayList<>();
91     }
92 
93     @Override
onStart()94     public void onStart() {
95         ThreadUtils.postOnBackgroundThread(() -> {
96             loadDisplayableRecentApps(mMaximumApps);
97             for (RecentAppStatsListener listener : mAppStatsListeners) {
98                 ThreadUtils.postOnMainThread(() -> listener.onReloadDataCompleted(mRecentApps));
99             }
100         });
101     }
102 
103     @Override
compare(UsageStats a, UsageStats b)104     public final int compare(UsageStats a, UsageStats b) {
105         // return by descending order
106         return Long.compare(b.getLastTimeUsed(), a.getLastTimeUsed());
107     }
108 
addListener(@onNull RecentAppStatsListener listener)109     public void addListener(@NonNull RecentAppStatsListener listener) {
110         mAppStatsListeners.add(listener);
111     }
112 
113     @VisibleForTesting
loadDisplayableRecentApps(int number)114     void loadDisplayableRecentApps(int number) {
115         mRecentApps.clear();
116         mCalendar = Calendar.getInstance();
117         mCalendar.add(Calendar.DAY_OF_YEAR, -1);
118         final List<UsageStats> mStats = mPowerManager.isPowerSaveMode()
119                 ? new ArrayList<>()
120                 : mUsageStatsManager.queryUsageStats(
121                         UsageStatsManager.INTERVAL_BEST, mCalendar.getTimeInMillis(),
122                         System.currentTimeMillis());
123 
124         final Map<String, UsageStats> map = new ArrayMap<>();
125         final int statCount = mStats.size();
126         for (int i = 0; i < statCount; i++) {
127             final UsageStats pkgStats = mStats.get(i);
128             if (!shouldIncludePkgInRecents(pkgStats)) {
129                 continue;
130             }
131             final String pkgName = pkgStats.getPackageName();
132             final UsageStats existingStats = map.get(pkgName);
133             if (existingStats == null) {
134                 map.put(pkgName, pkgStats);
135             } else {
136                 existingStats.add(pkgStats);
137             }
138         }
139         final List<UsageStats> packageStats = new ArrayList<>();
140         packageStats.addAll(map.values());
141         Collections.sort(packageStats, this /* comparator */);
142         int count = 0;
143         for (UsageStats stat : packageStats) {
144             final ApplicationsState.AppEntry appEntry = mApplicationsState.getEntry(
145                     stat.getPackageName(), mUserId);
146             if (appEntry == null) {
147                 continue;
148             }
149             mRecentApps.add(stat);
150             count++;
151             if (count >= number) {
152                 break;
153             }
154         }
155     }
156 
157     /**
158      * Whether or not the app should be included in recent list.
159      */
shouldIncludePkgInRecents(UsageStats stat)160     private boolean shouldIncludePkgInRecents(UsageStats stat) {
161         final String pkgName = stat.getPackageName();
162         if (stat.getLastTimeUsed() < mCalendar.getTimeInMillis()) {
163             Log.d(TAG, "Invalid timestamp (usage time is more than 24 hours ago), skipping "
164                     + pkgName);
165             return false;
166         }
167 
168         if (SKIP_SYSTEM_PACKAGES.contains(pkgName)) {
169             Log.d(TAG, "System package, skipping " + pkgName);
170             return false;
171         }
172         if (AppUtils.isHiddenSystemModule(mContext, pkgName)) {
173             return false;
174         }
175         final Intent launchIntent = new Intent().addCategory(Intent.CATEGORY_LAUNCHER)
176                 .setPackage(pkgName);
177 
178         if (mPm.resolveActivity(launchIntent, 0) == null) {
179             // Not visible on launcher -> likely not a user visible app, skip if non-instant.
180             final ApplicationsState.AppEntry appEntry =
181                     mApplicationsState.getEntry(pkgName, mUserId);
182             if (appEntry == null || appEntry.info == null || !AppUtils.isInstant(appEntry.info)) {
183                 Log.d(TAG, "Not a user visible or instant app, skipping " + pkgName);
184                 return false;
185             }
186         }
187         return true;
188     }
189 
190     public interface RecentAppStatsListener {
191 
onReloadDataCompleted(List<UsageStats> recentApps)192         void onReloadDataCompleted(List<UsageStats> recentApps);
193     }
194 }
195