• 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 
24 import com.android.launcher3.AllAppsList;
25 import com.android.launcher3.AppInfo;
26 import com.android.launcher3.FolderInfo;
27 import com.android.launcher3.InvariantDeviceProfile;
28 import com.android.launcher3.ItemInfo;
29 import com.android.launcher3.LauncherAppState;
30 import com.android.launcher3.LauncherAppWidgetInfo;
31 import com.android.launcher3.LauncherModel;
32 import com.android.launcher3.LauncherModel.CallbackTask;
33 import com.android.launcher3.LauncherModel.Callbacks;
34 import com.android.launcher3.LauncherSettings;
35 import com.android.launcher3.ShortcutInfo;
36 import com.android.launcher3.util.GridOccupancy;
37 import com.android.launcher3.util.Provider;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 
42 /**
43  * Task to add auto-created workspace items.
44  */
45 public class AddWorkspaceItemsTask extends ExtendedModelTask {
46 
47     private final Provider<List<ItemInfo>> mAppsProvider;
48 
49     /**
50      * @param appsProvider items to add on the workspace
51      */
AddWorkspaceItemsTask(Provider<List<ItemInfo>> appsProvider)52     public AddWorkspaceItemsTask(Provider<List<ItemInfo>> appsProvider) {
53         mAppsProvider = appsProvider;
54     }
55 
56     @Override
execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps)57     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
58         List<ItemInfo> workspaceApps = mAppsProvider.get();
59         if (workspaceApps.isEmpty()) {
60             return;
61         }
62         Context context = app.getContext();
63 
64         final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
65         final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<>();
66 
67         // Get the list of workspace screens.  We need to append to this list and
68         // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
69         // called.
70         ArrayList<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
71         synchronized(dataModel) {
72             for (ItemInfo item : workspaceApps) {
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                 // Find appropriate space for the item.
82                 Pair<Long, int[]> coords = findSpaceForItem(app, dataModel, workspaceScreens,
83                         addedWorkspaceScreensFinal, item.spanX, item.spanY);
84                 long screenId = coords.first;
85                 int[] cordinates = coords.second;
86 
87                 ItemInfo itemInfo;
88                 if (item instanceof ShortcutInfo || item instanceof FolderInfo ||
89                         item instanceof LauncherAppWidgetInfo) {
90                     itemInfo = item;
91                 } else if (item instanceof AppInfo) {
92                     itemInfo = ((AppInfo) item).makeShortcut();
93                 } else {
94                     throw new RuntimeException("Unexpected info type");
95                 }
96 
97                 // Add the shortcut to the db
98                 getModelWriter().addItemToDatabase(itemInfo,
99                         LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId,
100                         cordinates[0], cordinates[1]);
101 
102                 // Save the ShortcutInfo for binding in the workspace
103                 addedItemsFinal.add(itemInfo);
104             }
105         }
106 
107         // Update the workspace screens
108         updateScreens(context, workspaceScreens);
109 
110         if (!addedItemsFinal.isEmpty()) {
111             scheduleCallbackTask(new CallbackTask() {
112                 @Override
113                 public void execute(Callbacks callbacks) {
114                     final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
115                     final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
116                     if (!addedItemsFinal.isEmpty()) {
117                         ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1);
118                         long lastScreenId = info.screenId;
119                         for (ItemInfo i : addedItemsFinal) {
120                             if (i.screenId == lastScreenId) {
121                                 addAnimated.add(i);
122                             } else {
123                                 addNotAnimated.add(i);
124                             }
125                         }
126                     }
127                     callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
128                             addNotAnimated, addAnimated, null);
129                 }
130             });
131         }
132     }
133 
updateScreens(Context context, ArrayList<Long> workspaceScreens)134     protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) {
135         LauncherModel.updateWorkspaceScreenOrder(context, workspaceScreens);
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 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             String packageName = 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(packageName).toUri(0);
157                 intentWithoutPkg = intent.toUri(0);
158             }
159         } else {
160             intentWithPkg = intent.toUri(0);
161             intentWithoutPkg = intent.toUri(0);
162         }
163 
164         synchronized (dataModel) {
165             for (ItemInfo item : dataModel.itemsIdMap) {
166                 if (item instanceof ShortcutInfo) {
167                     ShortcutInfo info = (ShortcutInfo) item;
168                     if (item.getIntent() != null && info.user.equals(user)) {
169                         Intent copyIntent = new Intent(item.getIntent());
170                         copyIntent.setSourceBounds(intent.getSourceBounds());
171                         String s = copyIntent.toUri(0);
172                         if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
173                             return true;
174                         }
175                     }
176                 }
177             }
178         }
179         return false;
180     }
181 
182     /**
183      * Find a position on the screen for the given size or adds a new screen.
184      * @return screenId and the coordinates for the item.
185      */
findSpaceForItem( LauncherAppState app, BgDataModel dataModel, ArrayList<Long> workspaceScreens, ArrayList<Long> addedWorkspaceScreensFinal, int spanX, int spanY)186     protected Pair<Long, int[]> findSpaceForItem(
187             LauncherAppState app, BgDataModel dataModel,
188             ArrayList<Long> workspaceScreens,
189             ArrayList<Long> addedWorkspaceScreensFinal,
190             int spanX, int spanY) {
191         LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
192 
193         // Use sBgItemsIdMap as all the items are already loaded.
194         synchronized (dataModel) {
195             for (ItemInfo info : dataModel.itemsIdMap) {
196                 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
197                     ArrayList<ItemInfo> items = screenItems.get(info.screenId);
198                     if (items == null) {
199                         items = new ArrayList<>();
200                         screenItems.put(info.screenId, items);
201                     }
202                     items.add(info);
203                 }
204             }
205         }
206 
207         // Find appropriate space for the item.
208         long screenId = 0;
209         int[] cordinates = new int[2];
210         boolean found = false;
211 
212         int screenCount = workspaceScreens.size();
213         // First check the preferred screen.
214         int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
215         if (preferredScreenIndex < screenCount) {
216             screenId = workspaceScreens.get(preferredScreenIndex);
217             found = findNextAvailableIconSpaceInScreen(
218                     app, screenItems.get(screenId), cordinates, spanX, spanY);
219         }
220 
221         if (!found) {
222             // Search on any of the screens starting from the first screen.
223             for (int screen = 1; screen < screenCount; screen++) {
224                 screenId = workspaceScreens.get(screen);
225                 if (findNextAvailableIconSpaceInScreen(
226                         app, screenItems.get(screenId), cordinates, spanX, spanY)) {
227                     // We found a space for it
228                     found = true;
229                     break;
230                 }
231             }
232         }
233 
234         if (!found) {
235             // Still no position found. Add a new screen to the end.
236             screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
237                     LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
238                     .getLong(LauncherSettings.Settings.EXTRA_VALUE);
239 
240             // Save the screen id for binding in the workspace
241             workspaceScreens.add(screenId);
242             addedWorkspaceScreensFinal.add(screenId);
243 
244             // If we still can't find an empty space, then God help us all!!!
245             if (!findNextAvailableIconSpaceInScreen(
246                     app, screenItems.get(screenId), cordinates, spanX, spanY)) {
247                 throw new RuntimeException("Can't find space to add the item");
248             }
249         }
250         return Pair.create(screenId, cordinates);
251     }
252 
findNextAvailableIconSpaceInScreen( LauncherAppState app, ArrayList<ItemInfo> occupiedPos, int[] xy, int spanX, int spanY)253     private boolean findNextAvailableIconSpaceInScreen(
254             LauncherAppState app, ArrayList<ItemInfo> occupiedPos,
255             int[] xy, int spanX, int spanY) {
256         InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
257 
258         GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
259         if (occupiedPos != null) {
260             for (ItemInfo r : occupiedPos) {
261                 occupied.markCells(r, true);
262             }
263         }
264         return occupied.findVacantCell(xy, spanX, spanY);
265     }
266 }
267