• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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