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