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