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