• 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 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