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