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