• 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 package com.android.car.settings.applications;
17 
18 import static android.Manifest.permission.MANAGE_OWN_CALLS;
19 
20 import android.content.pm.PackageManager;
21 import android.os.Handler;
22 import android.os.UserManager;
23 import android.os.storage.VolumeInfo;
24 
25 import androidx.lifecycle.Lifecycle;
26 
27 import com.android.car.settings.common.Logger;
28 import com.android.car.settings.common.PermissionUtil;
29 import com.android.settingslib.applications.ApplicationsState;
30 
31 import java.util.ArrayList;
32 import java.util.Comparator;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Set;
36 
37 /**
38  * Class used to load the applications installed on the system with their metadata.
39  */
40 // TODO: consolidate with AppEntryListManager.
41 public class ApplicationListItemManager implements ApplicationsState.Callbacks {
42     /**
43      * Callback that is called once the list of applications are loaded.
44      */
45     public interface AppListItemListener {
46         /**
47          * Called when the data is successfully loaded from {@link ApplicationsState.Callbacks} and
48          * icon, title and summary are set for all the applications.
49          */
onDataLoaded(ArrayList<ApplicationsState.AppEntry> apps)50         void onDataLoaded(ArrayList<ApplicationsState.AppEntry> apps);
51     }
52 
53     private static final Logger LOG = new Logger(ApplicationListItemManager.class);
54     private static final String APP_NAME_UNKNOWN = "APP NAME UNKNOWN";
55 
56     private final VolumeInfo mVolumeInfo;
57     private final Lifecycle mLifecycle;
58     private final ApplicationsState mAppState;
59     private final List<AppListItemListener> mAppListItemListeners = new ArrayList<>();
60     private final Handler mHandler;
61     private final int mMillisecondUpdateInterval;
62     // Milliseconds that warnIfNotAllLoadedInTime method waits before comparing mAppsToLoad and
63     // mLoadedApps to log any apps that failed to load.
64     private final int mMaxAppLoadWaitInterval;
65     private final boolean mIsVisibleBackgroundUser;
66     private final PackageManager mPackageManager;
67 
68     private ApplicationsState.Session mSession;
69     private ApplicationsState.AppFilter mAppFilter;
70     private Comparator<ApplicationsState.AppEntry> mAppEntryComparator;
71     // Contains all of the apps that we are expecting to load.
72     private Set<ApplicationsState.AppEntry> mAppsToLoad = new HashSet<>();
73     // Contains all apps that have been successfully loaded.
74     private ArrayList<ApplicationsState.AppEntry> mLoadedApps = new ArrayList<>();
75 
76     // Indicates whether onRebuildComplete's throttling is off and it is ready to render updates.
77     // onRebuildComplete uses throttling to prevent it from being called too often, since the
78     // animation can be choppy if the refresh rate is too high.
79     private boolean mReadyToRenderUpdates = true;
80     // Parameter we use to call onRebuildComplete method when the throttling is off and we are
81     // "ReadyToRenderUpdates" again.
82     private ArrayList<ApplicationsState.AppEntry> mDeferredAppsToUpload;
83 
ApplicationListItemManager(VolumeInfo volumeInfo, Lifecycle lifecycle, ApplicationsState appState, int millisecondUpdateInterval, int maxWaitIntervalToFinishLoading, PackageManager packageManager, UserManager userManager)84     public ApplicationListItemManager(VolumeInfo volumeInfo, Lifecycle lifecycle,
85             ApplicationsState appState, int millisecondUpdateInterval,
86             int maxWaitIntervalToFinishLoading, PackageManager packageManager,
87             UserManager userManager) {
88         mVolumeInfo = volumeInfo;
89         mLifecycle = lifecycle;
90         mAppState = appState;
91         mHandler = new Handler();
92         mMillisecondUpdateInterval = millisecondUpdateInterval;
93         mMaxAppLoadWaitInterval = maxWaitIntervalToFinishLoading;
94         mPackageManager = packageManager;
95         mIsVisibleBackgroundUser = !userManager.isUserForeground() && userManager.isUserVisible()
96                 && !userManager.isProfile();
97     }
98 
99     /**
100      * Registers a listener that will be notified once the data is loaded.
101      */
registerListener(AppListItemListener appListItemListener)102     public void registerListener(AppListItemListener appListItemListener) {
103         if (!mAppListItemListeners.contains(appListItemListener) && appListItemListener != null) {
104             mAppListItemListeners.add(appListItemListener);
105         }
106     }
107 
108     /**
109      * Unregisters the listener.
110      */
unregisterlistener(AppListItemListener appListItemListener)111     public void unregisterlistener(AppListItemListener appListItemListener) {
112         mAppListItemListeners.remove(appListItemListener);
113     }
114 
115     /**
116      * Resumes the session and starts meauring app loading time on fragment start.
117      */
onFragmentStart()118     public void onFragmentStart() {
119         mSession.onResume();
120         warnIfNotAllLoadedInTime();
121     }
122 
123     /**
124      * Pause the session on fragment stop.
125      */
onFragmentStop()126     public void onFragmentStop() {
127         mSession.onPause();
128     }
129 
130     /**
131      * Starts the new session and start loading the list of installed applications on the device.
132      * This list will be filtered out based on the {@link ApplicationsState.AppFilter} provided.
133      * Once the list is ready, {@link AppListItemListener#onDataLoaded} will be called.
134      *
135      * @param appFilter          based on which the list of applications will be filtered before
136      *                           returning.
137      * @param appEntryComparator comparator based on which the application list will be sorted.
138      */
startLoading(ApplicationsState.AppFilter appFilter, Comparator<ApplicationsState.AppEntry> appEntryComparator)139     public void startLoading(ApplicationsState.AppFilter appFilter,
140             Comparator<ApplicationsState.AppEntry> appEntryComparator) {
141         if (mSession != null) {
142             LOG.w("Loading already started but restart attempted.");
143             return; // Prevent leaking sessions.
144         }
145         mAppFilter = appFilter;
146         mAppEntryComparator = appEntryComparator;
147         mSession = mAppState.newSession(this, mLifecycle);
148     }
149 
150     /**
151      * Rebuilds the list of applications using the provided {@link ApplicationsState.AppFilter}.
152      * The filter will be used for all subsequent loading. Once the list is ready, {@link
153      * AppListItemListener#onDataLoaded} will be called.
154      */
rebuildWithFilter(ApplicationsState.AppFilter appFilter)155     public void rebuildWithFilter(ApplicationsState.AppFilter appFilter) {
156         mAppFilter = appFilter;
157         rebuild();
158     }
159 
160     @Override
onPackageIconChanged()161     public void onPackageIconChanged() {
162         rebuild();
163     }
164 
165     @Override
onPackageSizeChanged(String packageName)166     public void onPackageSizeChanged(String packageName) {
167         rebuild();
168     }
169 
170     @Override
onAllSizesComputed()171     public void onAllSizesComputed() {
172         rebuild();
173     }
174 
175     @Override
onLauncherInfoChanged()176     public void onLauncherInfoChanged() {
177         rebuild();
178     }
179 
180     @Override
onLoadEntriesCompleted()181     public void onLoadEntriesCompleted() {
182         rebuild();
183     }
184 
185     @Override
onRunningStateChanged(boolean running)186     public void onRunningStateChanged(boolean running) {
187     }
188 
189     @Override
onPackageListChanged()190     public void onPackageListChanged() {
191         rebuild();
192     }
193 
194     @Override
onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps)195     public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
196         // Checking for apps.size prevents us from unnecessarily triggering throttling and blocking
197         // subsequent updates.
198         if (apps.size() == 0) {
199             return;
200         }
201 
202         // MUMD passenger users can't use telephony applications so they don't interrupt the
203         // driver's calls
204         ArrayList<ApplicationsState.AppEntry> filteredApps = new ArrayList<>();
205         if (mIsVisibleBackgroundUser) {
206             for (ApplicationsState.AppEntry appEntry : apps) {
207                 if (!PermissionUtil.doesPackageRequestPermission(appEntry.info.packageName,
208                         mPackageManager, MANAGE_OWN_CALLS)) {
209                     filteredApps.add(appEntry);
210                 }
211             }
212         } else {
213             filteredApps.addAll(apps);
214         }
215 
216         if (mReadyToRenderUpdates) {
217             mReadyToRenderUpdates = false;
218             mLoadedApps = new ArrayList<>();
219 
220             for (ApplicationsState.AppEntry app : filteredApps) {
221                 if (isLoaded(app)) {
222                     mLoadedApps.add(app);
223                 }
224             }
225 
226             for (AppListItemListener appListItemListener : mAppListItemListeners) {
227                 appListItemListener.onDataLoaded(mLoadedApps);
228             }
229 
230             mHandler.postDelayed(() -> {
231                 mReadyToRenderUpdates = true;
232                 if (mDeferredAppsToUpload != null) {
233                     onRebuildComplete(mDeferredAppsToUpload);
234                     mDeferredAppsToUpload = null;
235                 }
236             }, mMillisecondUpdateInterval);
237         } else {
238             mDeferredAppsToUpload = filteredApps;
239         }
240 
241         // Add all apps that are not already contained in mAppsToLoad Set, since we want it to be an
242         // exhaustive Set of all apps to be loaded.
243         mAppsToLoad.addAll(filteredApps);
244     }
245 
isLoaded(ApplicationsState.AppEntry app)246     private boolean isLoaded(ApplicationsState.AppEntry app) {
247         return app.label != null && app.sizeStr != null && app.icon != null;
248     }
249 
warnIfNotAllLoadedInTime()250     private void warnIfNotAllLoadedInTime() {
251         mHandler.postDelayed(() -> {
252             if (mLoadedApps.size() < mAppsToLoad.size()) {
253                 LOG.w("Expected to load " + mAppsToLoad.size() + " apps but only loaded "
254                         + mLoadedApps.size());
255 
256                 // Creating a copy to avoid state inconsistency.
257                 Set<ApplicationsState.AppEntry> appsToLoadCopy = new HashSet(mAppsToLoad);
258                 for (ApplicationsState.AppEntry loadedApp : mLoadedApps) {
259                     appsToLoadCopy.remove(loadedApp);
260                 }
261 
262                 for (ApplicationsState.AppEntry appEntry : appsToLoadCopy) {
263                     String appName = appEntry.label == null ? APP_NAME_UNKNOWN : appEntry.label;
264                     LOG.w("App failed to load: " + appName);
265                 }
266             }
267         }, mMaxAppLoadWaitInterval);
268     }
269 
getCompositeFilter(String volumeUuid)270     ApplicationsState.AppFilter getCompositeFilter(String volumeUuid) {
271         if (mAppFilter == null) {
272             return null;
273         }
274         ApplicationsState.AppFilter filter = new ApplicationsState.VolumeFilter(volumeUuid);
275         filter = new ApplicationsState.CompoundFilter(mAppFilter, filter);
276         return filter;
277     }
278 
rebuild()279     private void rebuild() {
280         ApplicationsState.AppFilter filterObj = ApplicationsState.FILTER_EVERYTHING;
281 
282         filterObj = new ApplicationsState.CompoundFilter(filterObj,
283                 ApplicationsState.FILTER_NOT_HIDE);
284         ApplicationsState.AppFilter compositeFilter = getCompositeFilter(mVolumeInfo.getFsUuid());
285         if (compositeFilter != null) {
286             filterObj = new ApplicationsState.CompoundFilter(filterObj, compositeFilter);
287         }
288         ApplicationsState.AppFilter finalFilterObj = filterObj;
289         mSession.rebuild(finalFilterObj, mAppEntryComparator, /* foreground= */ false);
290     }
291 }
292