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 android.content.Intent; 19 import android.os.UserHandle; 20 import android.util.LongSparseArray; 21 import android.util.Pair; 22 23 import com.android.launcher3.AllAppsList; 24 import com.android.launcher3.AppInfo; 25 import com.android.launcher3.FolderInfo; 26 import com.android.launcher3.InvariantDeviceProfile; 27 import com.android.launcher3.ItemInfo; 28 import com.android.launcher3.LauncherAppState; 29 import com.android.launcher3.LauncherAppWidgetInfo; 30 import com.android.launcher3.LauncherModel.CallbackTask; 31 import com.android.launcher3.LauncherModel.Callbacks; 32 import com.android.launcher3.LauncherSettings; 33 import com.android.launcher3.WorkspaceItemInfo; 34 import com.android.launcher3.Utilities; 35 import com.android.launcher3.util.GridOccupancy; 36 import com.android.launcher3.util.IntArray; 37 38 import java.util.ArrayList; 39 import java.util.List; 40 41 /** 42 * Task to add auto-created workspace items. 43 */ 44 public class AddWorkspaceItemsTask extends BaseModelUpdateTask { 45 46 private final List<Pair<ItemInfo, Object>> mItemList; 47 48 /** 49 * @param itemList items to add on the workspace 50 */ AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList)51 public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList) { 52 mItemList = itemList; 53 } 54 55 @Override execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps)56 public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { 57 if (mItemList.isEmpty()) { 58 return; 59 } 60 61 final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>(); 62 final IntArray addedWorkspaceScreensFinal = new IntArray(); 63 64 synchronized(dataModel) { 65 IntArray workspaceScreens = dataModel.collectWorkspaceScreens(); 66 67 List<ItemInfo> filteredItems = new ArrayList<>(); 68 for (Pair<ItemInfo, Object> entry : mItemList) { 69 ItemInfo item = entry.first; 70 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || 71 item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { 72 // Short-circuit this logic if the icon exists somewhere on the workspace 73 if (shortcutExists(dataModel, item.getIntent(), item.user)) { 74 continue; 75 } 76 } 77 78 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 79 if (item instanceof AppInfo) { 80 item = ((AppInfo) item).makeWorkspaceItem(); 81 } 82 } 83 if (item != null) { 84 filteredItems.add(item); 85 } 86 } 87 88 for (ItemInfo item : filteredItems) { 89 // Find appropriate space for the item. 90 int[] coords = findSpaceForItem(app, dataModel, workspaceScreens, 91 addedWorkspaceScreensFinal, item.spanX, item.spanY); 92 int screenId = coords[0]; 93 94 ItemInfo itemInfo; 95 if (item instanceof WorkspaceItemInfo || item instanceof FolderInfo || 96 item instanceof LauncherAppWidgetInfo) { 97 itemInfo = item; 98 } else if (item instanceof AppInfo) { 99 itemInfo = ((AppInfo) item).makeWorkspaceItem(); 100 } else { 101 throw new RuntimeException("Unexpected info type"); 102 } 103 104 // Add the shortcut to the db 105 getModelWriter().addItemToDatabase(itemInfo, 106 LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, 107 coords[1], coords[2]); 108 109 // Save the WorkspaceItemInfo for binding in the workspace 110 addedItemsFinal.add(itemInfo); 111 } 112 } 113 114 if (!addedItemsFinal.isEmpty()) { 115 scheduleCallbackTask(new CallbackTask() { 116 @Override 117 public void execute(Callbacks callbacks) { 118 final ArrayList<ItemInfo> addAnimated = new ArrayList<>(); 119 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<>(); 120 if (!addedItemsFinal.isEmpty()) { 121 ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1); 122 int lastScreenId = info.screenId; 123 for (ItemInfo i : addedItemsFinal) { 124 if (i.screenId == lastScreenId) { 125 addAnimated.add(i); 126 } else { 127 addNotAnimated.add(i); 128 } 129 } 130 } 131 callbacks.bindAppsAdded(addedWorkspaceScreensFinal, 132 addNotAnimated, addAnimated); 133 } 134 }); 135 } 136 } 137 138 /** 139 * Returns true if the shortcuts already exists on the workspace. This must be called after 140 * the workspace has been loaded. We identify a shortcut by its intent. 141 */ shortcutExists(BgDataModel dataModel, Intent intent, UserHandle user)142 protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandle user) { 143 final String compPkgName, intentWithPkg, intentWithoutPkg; 144 if (intent == null) { 145 // Skip items with null intents 146 return true; 147 } 148 if (intent.getComponent() != null) { 149 // If component is not null, an intent with null package will produce 150 // the same result and should also be a match. 151 compPkgName = intent.getComponent().getPackageName(); 152 if (intent.getPackage() != null) { 153 intentWithPkg = intent.toUri(0); 154 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0); 155 } else { 156 intentWithPkg = new Intent(intent).setPackage(compPkgName).toUri(0); 157 intentWithoutPkg = intent.toUri(0); 158 } 159 } else { 160 compPkgName = null; 161 intentWithPkg = intent.toUri(0); 162 intentWithoutPkg = intent.toUri(0); 163 } 164 165 boolean isLauncherAppTarget = Utilities.isLauncherAppTarget(intent); 166 synchronized (dataModel) { 167 for (ItemInfo item : dataModel.itemsIdMap) { 168 if (item instanceof WorkspaceItemInfo) { 169 WorkspaceItemInfo info = (WorkspaceItemInfo) item; 170 if (item.getIntent() != null && info.user.equals(user)) { 171 Intent copyIntent = new Intent(item.getIntent()); 172 copyIntent.setSourceBounds(intent.getSourceBounds()); 173 String s = copyIntent.toUri(0); 174 if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) { 175 return true; 176 } 177 178 // checking for existing promise icon with same package name 179 if (isLauncherAppTarget 180 && info.isPromise() 181 && info.hasStatusFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON) 182 && info.getTargetComponent() != null 183 && compPkgName != null 184 && compPkgName.equals(info.getTargetComponent().getPackageName())) { 185 return true; 186 } 187 } 188 } 189 } 190 } 191 return false; 192 } 193 194 /** 195 * Find a position on the screen for the given size or adds a new screen. 196 * @return screenId and the coordinates for the item in an int array of size 3. 197 */ findSpaceForItem( LauncherAppState app, BgDataModel dataModel, IntArray workspaceScreens, IntArray addedWorkspaceScreensFinal, int spanX, int spanY)198 protected int[] findSpaceForItem( LauncherAppState app, BgDataModel dataModel, 199 IntArray workspaceScreens, IntArray addedWorkspaceScreensFinal, int spanX, int spanY) { 200 LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>(); 201 202 // Use sBgItemsIdMap as all the items are already loaded. 203 synchronized (dataModel) { 204 for (ItemInfo info : dataModel.itemsIdMap) { 205 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 206 ArrayList<ItemInfo> items = screenItems.get(info.screenId); 207 if (items == null) { 208 items = new ArrayList<>(); 209 screenItems.put(info.screenId, items); 210 } 211 items.add(info); 212 } 213 } 214 } 215 216 // Find appropriate space for the item. 217 int screenId = 0; 218 int[] cordinates = new int[2]; 219 boolean found = false; 220 221 int screenCount = workspaceScreens.size(); 222 // First check the preferred screen. 223 int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1; 224 if (preferredScreenIndex < screenCount) { 225 screenId = workspaceScreens.get(preferredScreenIndex); 226 found = findNextAvailableIconSpaceInScreen( 227 app, screenItems.get(screenId), cordinates, spanX, spanY); 228 } 229 230 if (!found) { 231 // Search on any of the screens starting from the first screen. 232 for (int screen = 1; screen < screenCount; screen++) { 233 screenId = workspaceScreens.get(screen); 234 if (findNextAvailableIconSpaceInScreen( 235 app, screenItems.get(screenId), cordinates, spanX, spanY)) { 236 // We found a space for it 237 found = true; 238 break; 239 } 240 } 241 } 242 243 if (!found) { 244 // Still no position found. Add a new screen to the end. 245 screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(), 246 LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) 247 .getInt(LauncherSettings.Settings.EXTRA_VALUE); 248 249 // Save the screen id for binding in the workspace 250 workspaceScreens.add(screenId); 251 addedWorkspaceScreensFinal.add(screenId); 252 253 // If we still can't find an empty space, then God help us all!!! 254 if (!findNextAvailableIconSpaceInScreen( 255 app, screenItems.get(screenId), cordinates, spanX, spanY)) { 256 throw new RuntimeException("Can't find space to add the item"); 257 } 258 } 259 return new int[] {screenId, cordinates[0], cordinates[1]}; 260 } 261 findNextAvailableIconSpaceInScreen( LauncherAppState app, ArrayList<ItemInfo> occupiedPos, int[] xy, int spanX, int spanY)262 private boolean findNextAvailableIconSpaceInScreen( 263 LauncherAppState app, ArrayList<ItemInfo> occupiedPos, 264 int[] xy, int spanX, int spanY) { 265 InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); 266 267 GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows); 268 if (occupiedPos != null) { 269 for (ItemInfo r : occupiedPos) { 270 occupied.markCells(r, true); 271 } 272 } 273 return occupied.findVacantCell(xy, spanX, spanY); 274 } 275 276 } 277