1 /* 2 * Copyright (C) 2016 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.launcher3.model; 17 18 import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG; 19 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.LauncherActivityInfo; 23 import android.content.pm.LauncherApps; 24 import android.content.pm.PackageInstaller.SessionInfo; 25 import android.os.UserHandle; 26 import android.util.Pair; 27 28 import androidx.annotation.NonNull; 29 import androidx.annotation.Nullable; 30 31 import com.android.launcher3.LauncherModel.CallbackTask; 32 import com.android.launcher3.LauncherModel.ModelUpdateTask; 33 import com.android.launcher3.LauncherSettings; 34 import com.android.launcher3.icons.IconCache; 35 import com.android.launcher3.logging.FileLog; 36 import com.android.launcher3.model.BgDataModel.Callbacks; 37 import com.android.launcher3.model.data.AppInfo; 38 import com.android.launcher3.model.data.CollectionInfo; 39 import com.android.launcher3.model.data.ItemInfo; 40 import com.android.launcher3.model.data.ItemInfoWithIcon; 41 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 42 import com.android.launcher3.model.data.WorkspaceItemFactory; 43 import com.android.launcher3.model.data.WorkspaceItemInfo; 44 import com.android.launcher3.pm.InstallSessionHelper; 45 import com.android.launcher3.pm.PackageInstallInfo; 46 import com.android.launcher3.util.ApplicationInfoWrapper; 47 import com.android.launcher3.util.IntArray; 48 import com.android.launcher3.util.PackageManagerHelper; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 import java.util.Objects; 53 54 /** 55 * Task to add auto-created workspace items. 56 */ 57 public class AddWorkspaceItemsTask implements ModelUpdateTask { 58 59 private static final String LOG = "AddWorkspaceItemsTask"; 60 61 @NonNull 62 private final List<Pair<ItemInfo, Object>> mItemList; 63 64 @NonNull 65 private final WorkspaceItemSpaceFinder mItemSpaceFinder; 66 67 /** 68 * @param itemList items to add on the workspace 69 * @param itemSpaceFinder inject WorkspaceItemSpaceFinder dependency for testing 70 */ AddWorkspaceItemsTask(@onNull final List<Pair<ItemInfo, Object>> itemList, @NonNull final WorkspaceItemSpaceFinder itemSpaceFinder)71 public AddWorkspaceItemsTask(@NonNull final List<Pair<ItemInfo, Object>> itemList, 72 @NonNull final WorkspaceItemSpaceFinder itemSpaceFinder) { 73 mItemList = itemList; 74 mItemSpaceFinder = itemSpaceFinder; 75 } 76 77 78 @Override execute(@onNull ModelTaskController taskController, @NonNull BgDataModel dataModel, @NonNull AllAppsList apps)79 public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel, 80 @NonNull AllAppsList apps) { 81 if (mItemList.isEmpty()) { 82 return; 83 } 84 85 final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>(); 86 final IntArray addedWorkspaceScreensFinal = new IntArray(); 87 final Context context = taskController.getContext(); 88 89 synchronized (dataModel) { 90 IntArray workspaceScreens = dataModel.collectWorkspaceScreens(); 91 92 List<ItemInfo> filteredItems = new ArrayList<>(); 93 for (Pair<ItemInfo, Object> entry : mItemList) { 94 ItemInfo item = entry.first; 95 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 96 // Short-circuit this logic if the icon exists somewhere on the workspace 97 if (shortcutExists(dataModel, item.getIntent(), item.user)) { 98 continue; 99 } 100 101 // b/139663018 Short-circuit this logic if the icon is a system app 102 if (new ApplicationInfoWrapper(context, 103 Objects.requireNonNull(item.getIntent())).isSystem()) { 104 continue; 105 } 106 107 if (item instanceof ItemInfoWithIcon 108 && ((ItemInfoWithIcon) item).isArchived()) { 109 continue; 110 } 111 } 112 113 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 114 if (item instanceof WorkspaceItemFactory) { 115 item = ((WorkspaceItemFactory) item).makeWorkspaceItem(context); 116 } 117 } 118 if (item != null) { 119 filteredItems.add(item); 120 } 121 } 122 123 InstallSessionHelper packageInstaller = 124 InstallSessionHelper.INSTANCE.get(context); 125 LauncherApps launcherApps = context.getSystemService(LauncherApps.class); 126 127 for (ItemInfo item : filteredItems) { 128 // Find appropriate space for the item. 129 int[] coords = mItemSpaceFinder.findSpaceForItem( 130 workspaceScreens, addedWorkspaceScreensFinal, item.spanX, item.spanY); 131 int screenId = coords[0]; 132 133 ItemInfo itemInfo; 134 if (item instanceof WorkspaceItemInfo || item instanceof CollectionInfo 135 || item instanceof LauncherAppWidgetInfo) { 136 itemInfo = item; 137 } else if (item instanceof WorkspaceItemFactory) { 138 itemInfo = ((WorkspaceItemFactory) item).makeWorkspaceItem(context); 139 } else { 140 throw new RuntimeException("Unexpected info type"); 141 } 142 143 if (item instanceof WorkspaceItemInfo && ((WorkspaceItemInfo) item).isPromise()) { 144 WorkspaceItemInfo workspaceInfo = (WorkspaceItemInfo) item; 145 String packageName = item.getTargetComponent() != null 146 ? item.getTargetComponent().getPackageName() : null; 147 if (packageName == null) { 148 continue; 149 } 150 SessionInfo sessionInfo = packageInstaller.getActiveSessionInfo(item.user, 151 packageName); 152 153 if (!packageInstaller.verifySessionInfo(sessionInfo)) { 154 FileLog.d(LOG, "Item info failed session info verification. " 155 + "Skipping : " + workspaceInfo); 156 continue; 157 } 158 159 List<LauncherActivityInfo> activities = Objects.requireNonNull(launcherApps) 160 .getActivityList(packageName, item.user); 161 boolean hasActivity = activities != null && !activities.isEmpty(); 162 163 if (sessionInfo == null) { 164 if (!hasActivity) { 165 // Session was cancelled, do not add. 166 continue; 167 } 168 } else { 169 workspaceInfo.setProgressLevel( 170 (int) (sessionInfo.getProgress() * 100), 171 PackageInstallInfo.STATUS_INSTALLING); 172 } 173 174 if (hasActivity) { 175 // App was installed while launcher was in the background, 176 // or app was already installed for another user. 177 itemInfo = new AppInfo(context, activities.get(0), item.user) 178 .makeWorkspaceItem(context); 179 180 if (shortcutExists(dataModel, itemInfo.getIntent(), itemInfo.user)) { 181 // We need this additional check here since we treat all auto added 182 // workspace items as promise icons. At this point we now have the 183 // correct intent to compare against existing workspace icons. 184 // Icon already exists on the workspace and should not be auto-added. 185 continue; 186 } 187 188 IconCache cache = taskController.getIconCache(); 189 WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo; 190 wii.title = ""; 191 wii.bitmap = cache.getDefaultIcon(item.user); 192 cache.getTitleAndIcon(wii, DESKTOP_ICON_FLAG); 193 } 194 } 195 196 // Add the shortcut to the db 197 taskController.getModelWriter().addItemToDatabase(itemInfo, 198 LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, 199 coords[1], coords[2]); 200 201 // Save the WorkspaceItemInfo for binding in the workspace 202 addedItemsFinal.add(itemInfo); 203 204 // log bitmap and label 205 FileLog.d(LOG, "Adding item info to workspace: " + itemInfo); 206 } 207 } 208 209 if (!addedItemsFinal.isEmpty()) { 210 taskController.scheduleCallbackTask(new CallbackTask() { 211 @Override 212 public void execute(@NonNull Callbacks callbacks) { 213 final ArrayList<ItemInfo> addAnimated = new ArrayList<>(); 214 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<>(); 215 if (!addedItemsFinal.isEmpty()) { 216 ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1); 217 int lastScreenId = info.screenId; 218 for (ItemInfo i : addedItemsFinal) { 219 if (i.screenId == lastScreenId) { 220 addAnimated.add(i); 221 } else { 222 addNotAnimated.add(i); 223 } 224 } 225 } 226 callbacks.bindAppsAdded(addedWorkspaceScreensFinal, 227 addNotAnimated, addAnimated); 228 } 229 }); 230 } 231 } 232 233 /** 234 * Returns true if the shortcuts already exists on the workspace. This must be called after 235 * the workspace has been loaded. We identify a shortcut by its intent. 236 */ shortcutExists(@onNull final BgDataModel dataModel, @Nullable final Intent intent, @NonNull final UserHandle user)237 protected boolean shortcutExists(@NonNull final BgDataModel dataModel, 238 @Nullable final Intent intent, @NonNull final UserHandle user) { 239 final String compPkgName, intentWithPkg, intentWithoutPkg; 240 if (intent == null) { 241 // Skip items with null intents 242 return true; 243 } 244 if (intent.getComponent() != null) { 245 // If component is not null, an intent with null package will produce 246 // the same result and should also be a match. 247 compPkgName = intent.getComponent().getPackageName(); 248 if (intent.getPackage() != null) { 249 intentWithPkg = intent.toUri(0); 250 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0); 251 } else { 252 intentWithPkg = new Intent(intent).setPackage(compPkgName).toUri(0); 253 intentWithoutPkg = intent.toUri(0); 254 } 255 } else { 256 compPkgName = null; 257 intentWithPkg = intent.toUri(0); 258 intentWithoutPkg = intent.toUri(0); 259 } 260 261 boolean isLauncherAppTarget = PackageManagerHelper.isLauncherAppTarget(intent); 262 synchronized (dataModel) { 263 for (ItemInfo item : dataModel.itemsIdMap) { 264 if (item instanceof WorkspaceItemInfo) { 265 WorkspaceItemInfo info = (WorkspaceItemInfo) item; 266 if (item.getIntent() != null && info.user.equals(user)) { 267 Intent copyIntent = new Intent(item.getIntent()); 268 copyIntent.setSourceBounds(intent.getSourceBounds()); 269 String s = copyIntent.toUri(0); 270 if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) { 271 return true; 272 } 273 274 // checking for existing promise icon with same package name 275 if (isLauncherAppTarget 276 && info.isPromise() 277 && info.hasStatusFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON) 278 && info.getTargetComponent() != null 279 && compPkgName != null 280 && compPkgName.equals(info.getTargetComponent().getPackageName())) { 281 return true; 282 } 283 } 284 } 285 } 286 } 287 return false; 288 } 289 } 290