1 /* 2 * Copyright (C) 2008 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.launcher3.model; 18 19 import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; 20 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR; 21 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY; 22 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.LauncherActivityInfo; 27 import android.content.pm.LauncherApps; 28 import android.os.LocaleList; 29 import android.os.UserHandle; 30 import android.util.Log; 31 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 35 import com.android.launcher3.AppFilter; 36 import com.android.launcher3.compat.AlphabeticIndexCompat; 37 import com.android.launcher3.dagger.LauncherAppSingleton; 38 import com.android.launcher3.icons.IconCache; 39 import com.android.launcher3.model.BgDataModel.Callbacks; 40 import com.android.launcher3.model.data.AppInfo; 41 import com.android.launcher3.model.data.ItemInfo; 42 import com.android.launcher3.pm.PackageInstallInfo; 43 import com.android.launcher3.pm.UserCache; 44 import com.android.launcher3.util.ApiWrapper; 45 import com.android.launcher3.util.ApplicationInfoWrapper; 46 import com.android.launcher3.util.FlagOp; 47 import com.android.launcher3.util.PackageManagerHelper; 48 import com.android.launcher3.util.SafeCloseable; 49 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.HashSet; 53 import java.util.List; 54 import java.util.function.Consumer; 55 import java.util.function.Predicate; 56 57 import javax.inject.Inject; 58 59 60 /** 61 * Stores the list of all applications for the all apps view. 62 */ 63 @LauncherAppSingleton 64 public class AllAppsList { 65 66 private static final String TAG = "AllAppsList"; 67 private static final Consumer<AppInfo> NO_OP_CONSUMER = a -> { }; 68 private static final boolean DEBUG = true; 69 70 public static final int DEFAULT_APPLICATIONS_NUMBER = 42; 71 72 /** The list off all apps. */ 73 public final ArrayList<AppInfo> data = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER); 74 75 @NonNull 76 private final IconCache mIconCache; 77 78 @NonNull 79 private final AppFilter mAppFilter; 80 81 private boolean mDataChanged = false; 82 private Consumer<AppInfo> mRemoveListener = NO_OP_CONSUMER; 83 84 private AlphabeticIndexCompat mIndex; 85 86 /** 87 * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION 88 * @see Callbacks#FLAG_QUIET_MODE_ENABLED 89 * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION 90 * @see Callbacks#FLAG_WORK_PROFILE_QUIET_MODE_ENABLED 91 * @see Callbacks#FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED 92 */ 93 private int mFlags; 94 95 /** 96 * Boring constructor. 97 */ 98 @Inject AllAppsList(IconCache iconCache, AppFilter appFilter)99 public AllAppsList(IconCache iconCache, AppFilter appFilter) { 100 mIconCache = iconCache; 101 mAppFilter = appFilter; 102 mIndex = new AlphabeticIndexCompat(LocaleList.getDefault()); 103 } 104 105 /** 106 * Returns true if there have been any changes since last call. 107 */ getAndResetChangeFlag()108 public boolean getAndResetChangeFlag() { 109 boolean result = mDataChanged; 110 mDataChanged = false; 111 return result; 112 } 113 114 /** 115 * Helper to checking {@link Callbacks#FLAG_HAS_SHORTCUT_PERMISSION} 116 */ hasShortcutHostPermission()117 public boolean hasShortcutHostPermission() { 118 return (mFlags & Callbacks.FLAG_HAS_SHORTCUT_PERMISSION) != 0; 119 } 120 121 /** 122 * Sets or clears the provided flag 123 */ setFlags(int flagMask, boolean enabled)124 public void setFlags(int flagMask, boolean enabled) { 125 if (enabled) { 126 mFlags |= flagMask; 127 } else { 128 mFlags &= ~flagMask; 129 } 130 mDataChanged = true; 131 } 132 133 /** 134 * Returns the model flags 135 */ getFlags()136 public int getFlags() { 137 return mFlags; 138 } 139 140 141 /** 142 * Add the supplied ApplicationInfo objects to the list, and enqueue it into the 143 * list to broadcast when notify() is called. 144 * 145 * If the app is already in the list, doesn't add it. 146 */ add(AppInfo info, LauncherActivityInfo activityInfo)147 public void add(AppInfo info, LauncherActivityInfo activityInfo) { 148 add(info, activityInfo, true); 149 } 150 add(AppInfo info, LauncherActivityInfo activityInfo, boolean loadIcon)151 public void add(AppInfo info, LauncherActivityInfo activityInfo, boolean loadIcon) { 152 if (!mAppFilter.shouldShowApp(info.componentName)) { 153 return; 154 } 155 if (findAppInfo(info.componentName, info.user) != null) { 156 return; 157 } 158 if (loadIcon) { 159 mIconCache.getTitleAndIcon(info, activityInfo, DEFAULT_LOOKUP_FLAG); 160 info.sectionName = mIndex.computeSectionName(info.title); 161 } else { 162 info.title = ""; 163 } 164 165 data.add(info); 166 mDataChanged = true; 167 } 168 169 @Nullable addPromiseApp(Context context, PackageInstallInfo installInfo)170 public AppInfo addPromiseApp(Context context, PackageInstallInfo installInfo) { 171 return addPromiseApp(context, installInfo, true); 172 } 173 174 @Nullable addPromiseApp( Context context, PackageInstallInfo installInfo, boolean loadIcon)175 public AppInfo addPromiseApp( 176 Context context, PackageInstallInfo installInfo, boolean loadIcon) { 177 // only if not yet installed 178 if (new ApplicationInfoWrapper(context, installInfo.packageName, installInfo.user) 179 .isInstalled()) { 180 return null; 181 } 182 AppInfo promiseAppInfo = new AppInfo(installInfo); 183 184 if (loadIcon) { 185 mIconCache.getTitleAndIcon(promiseAppInfo, promiseAppInfo.getMatchingLookupFlag()); 186 promiseAppInfo.sectionName = mIndex.computeSectionName(promiseAppInfo.title); 187 } else { 188 promiseAppInfo.title = ""; 189 } 190 191 data.add(promiseAppInfo); 192 mDataChanged = true; 193 194 return promiseAppInfo; 195 } 196 updateSectionName(AppInfo appInfo)197 public void updateSectionName(AppInfo appInfo) { 198 appInfo.sectionName = mIndex.computeSectionName(appInfo.title); 199 200 } 201 202 /** Updates the given PackageInstallInfo's associated AppInfo's installation info. */ updatePromiseInstallInfo(PackageInstallInfo installInfo)203 public List<AppInfo> updatePromiseInstallInfo(PackageInstallInfo installInfo) { 204 List<AppInfo> updatedAppInfos = new ArrayList<>(); 205 UserHandle user = installInfo.user; 206 for (int i = data.size() - 1; i >= 0; i--) { 207 final AppInfo appInfo = data.get(i); 208 final ComponentName tgtComp = appInfo.getTargetComponent(); 209 if (tgtComp != null && tgtComp.getPackageName().equals(installInfo.packageName) 210 && appInfo.user.equals(user)) { 211 if (installInfo.state == PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING 212 || installInfo.state == PackageInstallInfo.STATUS_INSTALLING 213 // In case unarchival fails, we would want to keep the icon and update 214 // back the progress to 0 for the all apps view without removing the 215 // icon, which is contrary to what happens during normal app installation 216 // flow. 217 || (installInfo.state == PackageInstallInfo.STATUS_FAILED 218 && appInfo.isArchived())) { 219 if (appInfo.isAppStartable() 220 && installInfo.state == PackageInstallInfo.STATUS_INSTALLING 221 && !appInfo.isArchived()) { 222 continue; 223 } 224 appInfo.setProgressLevel(installInfo); 225 226 updatedAppInfos.add(appInfo); 227 } else if (installInfo.state == PackageInstallInfo.STATUS_FAILED 228 && !appInfo.isAppStartable()) { 229 if (DEBUG) { 230 Log.w(TAG, "updatePromiseInstallInfo: removing app due to install" 231 + " failure and appInfo not startable." 232 + " package=" + appInfo.getTargetPackage() 233 + ", user=" + user); 234 } 235 removeApp(i); 236 } 237 } 238 } 239 return updatedAppInfos; 240 } 241 removeApp(int index)242 private void removeApp(int index) { 243 AppInfo removed = data.remove(index); 244 if (removed != null) { 245 mDataChanged = true; 246 mRemoveListener.accept(removed); 247 } 248 } 249 clear()250 public void clear() { 251 data.clear(); 252 mDataChanged = false; 253 // Reset the index as locales might have changed 254 mIndex = new AlphabeticIndexCompat(LocaleList.getDefault()); 255 } 256 257 /** 258 * Add the icons for the supplied apk called packageName. 259 */ addPackage( Context context, String packageName, UserHandle user)260 public List<LauncherActivityInfo> addPackage( 261 Context context, String packageName, UserHandle user) { 262 List<LauncherActivityInfo> activities = context.getSystemService(LauncherApps.class) 263 .getActivityList(packageName, user); 264 265 for (LauncherActivityInfo info : activities) { 266 add(new AppInfo(context, info, user), info); 267 } 268 269 return activities; 270 } 271 272 /** 273 * Remove the apps for the given apk identified by packageName. 274 */ removePackage(String packageName, UserHandle user)275 public void removePackage(String packageName, UserHandle user) { 276 final List<AppInfo> data = this.data; 277 for (int i = data.size() - 1; i >= 0; i--) { 278 AppInfo info = data.get(i); 279 if (info.user.equals(user) && packageName.equals(info.componentName.getPackageName())) { 280 removeApp(i); 281 } 282 } 283 } 284 285 /** 286 * Updates the disabled flags of apps matching {@param matcher} based on {@param op}. 287 */ updateDisabledFlags(Predicate<ItemInfo> matcher, FlagOp op)288 public void updateDisabledFlags(Predicate<ItemInfo> matcher, FlagOp op) { 289 final List<AppInfo> data = this.data; 290 for (int i = data.size() - 1; i >= 0; i--) { 291 AppInfo info = data.get(i); 292 if (matcher.test(info)) { 293 info.runtimeStatusFlags = op.apply(info.runtimeStatusFlags); 294 mDataChanged = true; 295 } 296 } 297 } 298 updateIconsAndLabels(HashSet<String> packages, UserHandle user)299 public void updateIconsAndLabels(HashSet<String> packages, UserHandle user) { 300 for (AppInfo info : data) { 301 if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) { 302 mIconCache.updateTitleAndIcon(info); 303 info.sectionName = mIndex.computeSectionName(info.title); 304 mDataChanged = true; 305 } 306 } 307 } 308 309 /** 310 * Add and remove icons for this package which has been updated. 311 */ updatePackage( Context context, String packageName, UserHandle user)312 public List<LauncherActivityInfo> updatePackage( 313 Context context, String packageName, UserHandle user) { 314 final ApiWrapper apiWrapper = ApiWrapper.INSTANCE.get(context); 315 final UserCache userCache = UserCache.getInstance(context); 316 final PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context); 317 final List<LauncherActivityInfo> matches = context.getSystemService(LauncherApps.class) 318 .getActivityList(packageName, user); 319 if (matches.size() > 0) { 320 // Find disabled/removed activities and remove them from data and add them 321 // to the removed list. 322 for (int i = data.size() - 1; i >= 0; i--) { 323 final AppInfo applicationInfo = data.get(i); 324 if (user.equals(applicationInfo.user) 325 && packageName.equals(applicationInfo.componentName.getPackageName())) { 326 if (!findActivity(matches, applicationInfo.componentName)) { 327 if (DEBUG) { 328 Log.w(TAG, "Changing shortcut target due to app component name change." 329 + " component=" + applicationInfo.componentName 330 + ", user=" + user); 331 } 332 removeApp(i); 333 } 334 } 335 } 336 337 // Find enabled activities and add them to the adapter 338 // Also updates existing activities with new labels/icons 339 for (final LauncherActivityInfo info : matches) { 340 AppInfo applicationInfo = findAppInfo(info.getComponentName(), user); 341 if (applicationInfo == null) { 342 add(new AppInfo(context, info, user), info); 343 } else { 344 Intent launchIntent = AppInfo.makeLaunchIntent(info); 345 346 mIconCache.getTitleAndIcon(applicationInfo, info, DEFAULT_LOOKUP_FLAG); 347 applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title); 348 applicationInfo.intent = launchIntent; 349 AppInfo.updateRuntimeFlagsForActivityTarget(applicationInfo, info, 350 userCache.getUserInfo(user), apiWrapper, pmHelper); 351 mDataChanged = true; 352 } 353 } 354 } else { 355 // Remove all data for this package. 356 if (DEBUG) { 357 Log.w(TAG, "updatePackage: no Activities matched updated package," 358 + " removing any AppInfo with package=" + packageName 359 + ", user=" + user); 360 } 361 for (int i = data.size() - 1; i >= 0; i--) { 362 final AppInfo applicationInfo = data.get(i); 363 if (user.equals(applicationInfo.user) 364 && packageName.equals(applicationInfo.componentName.getPackageName())) { 365 mIconCache.remove(applicationInfo.componentName, user); 366 removeApp(i); 367 } 368 } 369 } 370 371 return matches; 372 } 373 374 /** 375 * Returns whether <em>apps</em> contains <em>component</em>. 376 */ findActivity(List<LauncherActivityInfo> apps, ComponentName component)377 private static boolean findActivity(List<LauncherActivityInfo> apps, 378 ComponentName component) { 379 for (LauncherActivityInfo info : apps) { 380 if (info.getComponentName().equals(component)) { 381 return true; 382 } 383 } 384 return false; 385 } 386 387 /** 388 * Find an AppInfo object for the given componentName 389 * 390 * @return the corresponding AppInfo or null 391 */ findAppInfo(@onNull ComponentName componentName, @NonNull UserHandle user)392 public @Nullable AppInfo findAppInfo(@NonNull ComponentName componentName, 393 @NonNull UserHandle user) { 394 for (AppInfo info: data) { 395 if (componentName.equals(info.componentName) && user.equals(info.user)) { 396 return info; 397 } 398 } 399 return null; 400 } 401 copyData()402 public AppInfo[] copyData() { 403 AppInfo[] result = data.toArray(EMPTY_ARRAY); 404 Arrays.sort(result, COMPONENT_KEY_COMPARATOR); 405 return result; 406 } 407 trackRemoves(Consumer<AppInfo> removeListener)408 public SafeCloseable trackRemoves(Consumer<AppInfo> removeListener) { 409 mRemoveListener = removeListener; 410 411 return () -> mRemoveListener = NO_OP_CONSUMER; 412 } 413 } 414