1 /*
2  * Copyright (C) 2008 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 
17 package com.android.launcher3.model.data;
18 
19 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
20 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
21 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
22 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
23 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
24 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SEARCH_RESULTS;
25 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SETTINGS;
26 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
27 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
28 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WALLPAPERS;
29 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
30 import static com.android.launcher3.LauncherSettings.Favorites.EXTENDED_CONTAINERS;
31 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
32 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
33 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
34 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
35 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK;
36 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET;
37 import static com.android.launcher3.shortcuts.ShortcutKey.EXTRA_SHORTCUT_ID;
38 
39 import android.content.ComponentName;
40 import android.content.ContentValues;
41 import android.content.Intent;
42 import android.os.Process;
43 import android.os.UserHandle;
44 
45 import androidx.annotation.Nullable;
46 
47 import com.android.launcher3.LauncherSettings;
48 import com.android.launcher3.LauncherSettings.Favorites;
49 import com.android.launcher3.Workspace;
50 import com.android.launcher3.logger.LauncherAtom;
51 import com.android.launcher3.logger.LauncherAtom.AllAppsContainer;
52 import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
53 import com.android.launcher3.logger.LauncherAtom.PredictionContainer;
54 import com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
55 import com.android.launcher3.logger.LauncherAtom.SettingsContainer;
56 import com.android.launcher3.logger.LauncherAtom.Shortcut;
57 import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
58 import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
59 import com.android.launcher3.logger.LauncherAtom.WallpapersContainer;
60 import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
61 import com.android.launcher3.model.ModelWriter;
62 import com.android.launcher3.util.ContentWriter;
63 
64 import java.util.Optional;
65 
66 /**
67  * Represents an item in the launcher.
68  */
69 public class ItemInfo {
70 
71     public static final boolean DEBUG = false;
72     public static final int NO_ID = -1;
73     // An id that doesn't match any item, including predicted apps with have an id=NO_ID
74     public static final int NO_MATCHING_ID = Integer.MIN_VALUE;
75 
76     /**
77      * The id in the settings database for this item
78      */
79     public int id = NO_ID;
80 
81     /**
82      * One of {@link Favorites#ITEM_TYPE_APPLICATION},
83      * {@link Favorites#ITEM_TYPE_SHORTCUT},
84      * {@link Favorites#ITEM_TYPE_DEEP_SHORTCUT}
85      * {@link Favorites#ITEM_TYPE_FOLDER},
86      * {@link Favorites#ITEM_TYPE_APPWIDGET} or
87      * {@link Favorites#ITEM_TYPE_CUSTOM_APPWIDGET}.
88      */
89     public int itemType;
90 
91     /**
92      * The id of the container that holds this item. For the desktop, this will be
93      * {@link Favorites#CONTAINER_DESKTOP}. For the all applications folder it
94      * will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders
95      * it will be the id of the folder.
96      */
97     public int container = NO_ID;
98 
99     /**
100      * Indicates the screen in which the shortcut appears if the container types is
101      * {@link Favorites#CONTAINER_DESKTOP}. (i.e., ignore if the container type is
102      * {@link Favorites#CONTAINER_HOTSEAT})
103      */
104     public int screenId = -1;
105 
106     /**
107      * Indicates the X position of the associated cell.
108      */
109     public int cellX = -1;
110 
111     /**
112      * Indicates the Y position of the associated cell.
113      */
114     public int cellY = -1;
115 
116     /**
117      * Indicates the X cell span.
118      */
119     public int spanX = 1;
120 
121     /**
122      * Indicates the Y cell span.
123      */
124     public int spanY = 1;
125 
126     /**
127      * Indicates the minimum X cell span.
128      */
129     public int minSpanX = 1;
130 
131     /**
132      * Indicates the minimum Y cell span.
133      */
134     public int minSpanY = 1;
135 
136     /**
137      * Indicates the position in an ordered list.
138      */
139     public int rank = 0;
140 
141     /**
142      * Title of the item
143      */
144     public CharSequence title;
145 
146     /**
147      * Content description of the item.
148      */
149     public CharSequence contentDescription;
150 
151     /**
152      * When the instance is created using {@link #copyFrom}, this field is used to keep track of
153      * original {@link ComponentName}.
154      */
155     private ComponentName mComponentName;
156 
157     public UserHandle user;
158 
ItemInfo()159     public ItemInfo() {
160         user = Process.myUserHandle();
161     }
162 
ItemInfo(ItemInfo info)163     protected ItemInfo(ItemInfo info) {
164         copyFrom(info);
165     }
166 
copyFrom(ItemInfo info)167     public void copyFrom(ItemInfo info) {
168         id = info.id;
169         title = info.title;
170         cellX = info.cellX;
171         cellY = info.cellY;
172         spanX = info.spanX;
173         spanY = info.spanY;
174         minSpanX = info.minSpanX;
175         minSpanY = info.minSpanY;
176         rank = info.rank;
177         screenId = info.screenId;
178         itemType = info.itemType;
179         container = info.container;
180         user = info.user;
181         contentDescription = info.contentDescription;
182         mComponentName = info.getTargetComponent();
183     }
184 
getIntent()185     public Intent getIntent() {
186         return null;
187     }
188 
189     @Nullable
getTargetComponent()190     public ComponentName getTargetComponent() {
191         return Optional.ofNullable(getIntent()).map(Intent::getComponent).orElse(mComponentName);
192     }
193 
194     /**
195      * Returns this item's package name.
196      *
197      * Prioritizes the component package name, then uses the intent package name as a fallback.
198      * This ensures deep shortcuts are supported.
199      */
200     @Nullable
getTargetPackage()201     public String getTargetPackage() {
202         ComponentName component = getTargetComponent();
203         Intent intent = getIntent();
204 
205         return component != null
206                 ? component.getPackageName()
207                 : intent != null
208                         ? intent.getPackage()
209                         : null;
210     }
211 
writeToValues(ContentWriter writer)212     public void writeToValues(ContentWriter writer) {
213         writer.put(LauncherSettings.Favorites.ITEM_TYPE, itemType)
214                 .put(LauncherSettings.Favorites.CONTAINER, container)
215                 .put(LauncherSettings.Favorites.SCREEN, screenId)
216                 .put(LauncherSettings.Favorites.CELLX, cellX)
217                 .put(LauncherSettings.Favorites.CELLY, cellY)
218                 .put(LauncherSettings.Favorites.SPANX, spanX)
219                 .put(LauncherSettings.Favorites.SPANY, spanY)
220                 .put(LauncherSettings.Favorites.RANK, rank);
221     }
222 
readFromValues(ContentValues values)223     public void readFromValues(ContentValues values) {
224         itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
225         container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
226         screenId = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
227         cellX = values.getAsInteger(LauncherSettings.Favorites.CELLX);
228         cellY = values.getAsInteger(LauncherSettings.Favorites.CELLY);
229         spanX = values.getAsInteger(LauncherSettings.Favorites.SPANX);
230         spanY = values.getAsInteger(LauncherSettings.Favorites.SPANY);
231         rank = values.getAsInteger(LauncherSettings.Favorites.RANK);
232     }
233 
234     /**
235      * Write the fields of this item to the DB
236      */
onAddToDatabase(ContentWriter writer)237     public void onAddToDatabase(ContentWriter writer) {
238         if (Workspace.EXTRA_EMPTY_SCREEN_IDS.contains(screenId)) {
239             // We should never persist an item on the extra empty screen.
240             throw new RuntimeException("Screen id should not be extra empty screen: " + screenId);
241         }
242 
243         writeToValues(writer);
244         writer.put(LauncherSettings.Favorites.PROFILE_ID, user);
245     }
246 
247     @Override
toString()248     public final String toString() {
249         return getClass().getSimpleName() + "(" + dumpProperties() + ")";
250     }
251 
dumpProperties()252     protected String dumpProperties() {
253         return "id=" + id
254                 + " type=" + LauncherSettings.Favorites.itemTypeToString(itemType)
255                 + " container=" + getContainerInfo()
256                 + " targetComponent=" + getTargetComponent()
257                 + " screen=" + screenId
258                 + " cell(" + cellX + "," + cellY + ")"
259                 + " span(" + spanX + "," + spanY + ")"
260                 + " minSpan(" + minSpanX + "," + minSpanY + ")"
261                 + " rank=" + rank
262                 + " user=" + user
263                 + " title=" + title;
264     }
265 
266     /**
267      * Whether this item is disabled.
268      */
isDisabled()269     public boolean isDisabled() {
270         return false;
271     }
272 
getViewId()273     public int getViewId() {
274         // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
275         // This cast is safe as long as the id < 0x00FFFFFF
276         // Since we jail all the dynamically generated views, there should be no clashes
277         // with any other views.
278         return id;
279     }
280 
281     /**
282      * Returns if an Item is a predicted item
283      */
isPredictedItem()284     public boolean isPredictedItem() {
285         return container == CONTAINER_HOTSEAT_PREDICTION || container == CONTAINER_PREDICTION;
286     }
287 
288     /**
289      * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
290      */
buildProto()291     public LauncherAtom.ItemInfo buildProto() {
292         return buildProto(null);
293     }
294 
295     /**
296      * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
297      */
buildProto(FolderInfo fInfo)298     public LauncherAtom.ItemInfo buildProto(FolderInfo fInfo) {
299         LauncherAtom.ItemInfo.Builder itemBuilder = getDefaultItemInfoBuilder();
300         Optional<ComponentName> nullableComponent = Optional.ofNullable(getTargetComponent());
301         switch (itemType) {
302             case ITEM_TYPE_APPLICATION:
303                 itemBuilder
304                         .setApplication(nullableComponent
305                                 .map(component -> LauncherAtom.Application.newBuilder()
306                                         .setComponentName(component.flattenToShortString())
307                                         .setPackageName(component.getPackageName()))
308                                 .orElse(LauncherAtom.Application.newBuilder()));
309                 break;
310             case ITEM_TYPE_DEEP_SHORTCUT:
311                 itemBuilder
312                         .setShortcut(nullableComponent
313                                 .map(component -> {
314                                     Shortcut.Builder lsb = Shortcut.newBuilder()
315                                             .setShortcutName(component.flattenToShortString());
316                                     Optional.ofNullable(getIntent())
317                                             .map(i -> i.getStringExtra(EXTRA_SHORTCUT_ID))
318                                             .ifPresent(lsb::setShortcutId);
319                                     return lsb;
320                                 })
321                                 .orElse(LauncherAtom.Shortcut.newBuilder()));
322                 break;
323             case ITEM_TYPE_SHORTCUT:
324                 itemBuilder
325                         .setShortcut(nullableComponent
326                                 .map(component -> LauncherAtom.Shortcut.newBuilder()
327                                         .setShortcutName(component.flattenToShortString()))
328                                 .orElse(LauncherAtom.Shortcut.newBuilder()));
329                 break;
330             case ITEM_TYPE_APPWIDGET:
331                 itemBuilder
332                         .setWidget(nullableComponent
333                                 .map(component -> LauncherAtom.Widget.newBuilder()
334                                         .setComponentName(component.flattenToShortString())
335                                         .setPackageName(component.getPackageName()))
336                                 .orElse(LauncherAtom.Widget.newBuilder())
337                                 .setSpanX(spanX)
338                                 .setSpanY(spanY));
339                 break;
340             case ITEM_TYPE_TASK:
341                 itemBuilder
342                         .setTask(LauncherAtom.Task.newBuilder()
343                                 .setComponentName(getTargetComponent().flattenToShortString())
344                                 .setIndex(screenId));
345                 break;
346             default:
347                 break;
348         }
349         if (fInfo != null) {
350             LauncherAtom.FolderContainer.Builder folderBuilder =
351                     LauncherAtom.FolderContainer.newBuilder();
352             folderBuilder.setGridX(cellX).setGridY(cellY).setPageIndex(screenId);
353 
354             switch (fInfo.container) {
355                 case CONTAINER_HOTSEAT:
356                 case CONTAINER_HOTSEAT_PREDICTION:
357                     folderBuilder.setHotseat(LauncherAtom.HotseatContainer.newBuilder()
358                             .setIndex(fInfo.screenId));
359                     break;
360                 case CONTAINER_DESKTOP:
361                     folderBuilder.setWorkspace(LauncherAtom.WorkspaceContainer.newBuilder()
362                             .setPageIndex(fInfo.screenId)
363                             .setGridX(fInfo.cellX).setGridY(fInfo.cellY));
364                     break;
365             }
366             itemBuilder.setContainerInfo(ContainerInfo.newBuilder().setFolder(folderBuilder));
367         } else {
368             ContainerInfo containerInfo = getContainerInfo();
369             if (!containerInfo.getContainerCase().equals(CONTAINER_NOT_SET)) {
370                 itemBuilder.setContainerInfo(containerInfo);
371             }
372         }
373         return itemBuilder.build();
374     }
375 
getDefaultItemInfoBuilder()376     protected LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder() {
377         LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
378         itemBuilder.setIsWork(!Process.myUserHandle().equals(user));
379         itemBuilder.setRank(rank);
380         return itemBuilder;
381     }
382 
383     /**
384      * Returns {@link ContainerInfo} used when logging this item.
385      */
getContainerInfo()386     public ContainerInfo getContainerInfo() {
387         switch (container) {
388             case CONTAINER_HOTSEAT:
389                 return ContainerInfo.newBuilder()
390                         .setHotseat(LauncherAtom.HotseatContainer.newBuilder().setIndex(screenId))
391                         .build();
392             case CONTAINER_HOTSEAT_PREDICTION:
393                 return ContainerInfo.newBuilder().setPredictedHotseatContainer(
394                         LauncherAtom.PredictedHotseatContainer.newBuilder().setIndex(screenId))
395                         .build();
396             case CONTAINER_DESKTOP:
397                 return ContainerInfo.newBuilder()
398                         .setWorkspace(
399                                 LauncherAtom.WorkspaceContainer.newBuilder()
400                                         .setGridX(cellX)
401                                         .setGridY(cellY)
402                                         .setPageIndex(screenId))
403                         .build();
404             case CONTAINER_ALL_APPS:
405                 return ContainerInfo.newBuilder()
406                         .setAllAppsContainer(
407                                 AllAppsContainer.getDefaultInstance())
408                         .build();
409             case CONTAINER_WIDGETS_TRAY:
410                 return ContainerInfo.newBuilder()
411                         .setWidgetsContainer(
412                                 LauncherAtom.WidgetsContainer.getDefaultInstance())
413                         .build();
414             case CONTAINER_PREDICTION:
415                 return ContainerInfo.newBuilder()
416                         .setPredictionContainer(PredictionContainer.getDefaultInstance())
417                         .build();
418             case CONTAINER_SEARCH_RESULTS:
419                 return ContainerInfo.newBuilder()
420                         .setSearchResultContainer(SearchResultContainer.getDefaultInstance())
421                         .build();
422             case CONTAINER_SHORTCUTS:
423                 return ContainerInfo.newBuilder()
424                         .setShortcutsContainer(ShortcutsContainer.getDefaultInstance())
425                         .build();
426             case CONTAINER_SETTINGS:
427                 return ContainerInfo.newBuilder()
428                         .setSettingsContainer(SettingsContainer.getDefaultInstance())
429                         .build();
430             case CONTAINER_TASKSWITCHER:
431                 return ContainerInfo.newBuilder()
432                         .setTaskSwitcherContainer(TaskSwitcherContainer.getDefaultInstance())
433                         .build();
434             case CONTAINER_WALLPAPERS:
435                 return ContainerInfo.newBuilder()
436                         .setWallpapersContainer(WallpapersContainer.getDefaultInstance())
437                         .build();
438             case EXTENDED_CONTAINERS:
439                 return ContainerInfo.newBuilder()
440                         .setExtendedContainers(getExtendedContainer())
441                         .build();
442         }
443         return ContainerInfo.getDefaultInstance();
444     }
445 
446     /**
447      * Returns non-AOSP container wrapped by {@link ExtendedContainers} object. Should be overridden
448      * by build variants.
449      */
getExtendedContainer()450     protected ExtendedContainers getExtendedContainer() {
451         return ExtendedContainers.getDefaultInstance();
452     }
453 
454     /**
455      * Returns shallow copy of the object.
456      */
makeShallowCopy()457     public ItemInfo makeShallowCopy() {
458         ItemInfo itemInfo = new ItemInfo();
459         itemInfo.copyFrom(this);
460         return itemInfo;
461     }
462 
463     /**
464      * Sets the title of the item and writes to DB model if needed.
465      */
setTitle(CharSequence title, ModelWriter modelWriter)466     public void setTitle(CharSequence title, ModelWriter modelWriter) {
467         this.title = title;
468     }
469 }
470