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