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 android.app.Person;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.ShortcutInfo;
24 import android.text.TextUtils;
25 import android.util.Log;
26 
27 import androidx.annotation.NonNull;
28 
29 import com.android.launcher3.Flags;
30 import com.android.launcher3.LauncherSettings;
31 import com.android.launcher3.LauncherSettings.Favorites;
32 import com.android.launcher3.Utilities;
33 import com.android.launcher3.icons.IconCache;
34 import com.android.launcher3.pm.UserCache;
35 import com.android.launcher3.shortcuts.ShortcutKey;
36 import com.android.launcher3.util.ApiWrapper;
37 import com.android.launcher3.util.ContentWriter;
38 
39 import java.util.Arrays;
40 
41 /**
42  * Represents a launchable icon on the workspaces and in folders.
43  */
44 public class WorkspaceItemInfo extends ItemInfoWithIcon {
45 
46     public static final int DEFAULT = 0;
47 
48     /**
49      * The shortcut was restored from a backup and it not ready to be used. This is automatically
50      * set during backup/restore
51      */
52     public static final int FLAG_RESTORED_ICON = 1;
53 
54     /**
55      * The icon was added as an auto-install app, and is not ready to be used. This flag can't
56      * be present along with {@link #FLAG_RESTORED_ICON}, and is set during default layout
57      * parsing.
58      *
59      * OR this icon was added due to it being an active install session created by the user.
60      */
61     public static final int FLAG_AUTOINSTALL_ICON = 1 << 1;
62 
63     /**
64      * Indicates that the widget restore has started.
65      */
66     public static final int FLAG_RESTORE_STARTED = 1 << 2;
67 
68     /**
69      * Web UI supported.
70      */
71     public static final int FLAG_SUPPORTS_WEB_UI = 1 << 3;
72 
73     /**
74      *
75      */
76     public static final int FLAG_START_FOR_RESULT = 1 << 4;
77 
78     /**
79      * The intent used to start the application.
80      */
81     @NonNull
82     public Intent intent;
83 
84     /**
85      * A message to display when the user tries to start a disabled shortcut.
86      * This is currently only used for deep shortcuts.
87      */
88     public CharSequence disabledMessage;
89 
90     public int status;
91 
92     /**
93      * A set of person's Id associated with the WorkspaceItemInfo, this is only used if the item
94      * represents a deep shortcut.
95      */
96     @NonNull private String[] personKeys = Utilities.EMPTY_STRING_ARRAY;
97 
98     public int options;
99 
100 
WorkspaceItemInfo()101     public WorkspaceItemInfo() {
102         itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
103     }
104 
WorkspaceItemInfo(WorkspaceItemInfo info)105     public WorkspaceItemInfo(WorkspaceItemInfo info) {
106         super(info);
107         title = info.title;
108         intent = new Intent(info.intent);
109         status = info.status;
110         personKeys = info.personKeys.clone();
111     }
112 
113     /** TODO: Remove this.  It's only called by ApplicationInfo.makeWorkspaceItem. */
WorkspaceItemInfo(AppInfo info)114     public WorkspaceItemInfo(AppInfo info) {
115         super(info);
116         title = Utilities.trim(info.title);
117         intent = new Intent(info.getIntent());
118     }
119 
120     /**
121      * Creates a {@link WorkspaceItemInfo} from a {@link ShortcutInfo}.
122      */
WorkspaceItemInfo(ShortcutInfo shortcutInfo, Context context)123     public WorkspaceItemInfo(ShortcutInfo shortcutInfo, Context context) {
124         user = shortcutInfo.getUserHandle();
125         itemType = Favorites.ITEM_TYPE_DEEP_SHORTCUT;
126         if (Flags.privateSpaceRestrictAccessibilityDrag()) {
127             if (UserCache.INSTANCE.get(context).getUserInfo(user).isPrivate()) {
128                 runtimeStatusFlags |= FLAG_NOT_PINNABLE;
129             }
130         }
131         updateFromDeepShortcutInfo(shortcutInfo, context);
132     }
133 
134     @Override
onAddToDatabase(@onNull ContentWriter writer)135     public void onAddToDatabase(@NonNull ContentWriter writer) {
136         super.onAddToDatabase(writer);
137         writer.put(Favorites.TITLE, title)
138                 .put(Favorites.INTENT, getIntent())
139                 .put(Favorites.OPTIONS, options)
140                 .put(Favorites.RESTORED, status);
141 
142         if (!usingLowResIcon()) {
143             writer.putIcon(bitmap, user);
144         }
145     }
146 
147     @Override
148     @NonNull
getIntent()149     public Intent getIntent() {
150         return intent;
151     }
152 
hasStatusFlag(int flag)153     public boolean hasStatusFlag(int flag) {
154         return (status & flag) != 0;
155     }
156 
157 
isPromise()158     public final boolean isPromise() {
159         return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)
160                 // For archived apps, promise icons are always ready to be displayed.
161                 || isArchived();
162     }
163 
164     /**
165      * Returns true if the workspace item supports promise icon UI. There are a few cases where they
166      * are supported:
167      * 1. Icons to be restored via backup/restore.
168      * 2. Icons added as an auto-install app.
169      * 3. Icons added due to it being an active install session created by the user.
170      * 4. Icons for archived apps.
171      */
hasPromiseIconUi()172     public boolean hasPromiseIconUi() {
173         return isPromise() && !hasStatusFlag(FLAG_SUPPORTS_WEB_UI);
174     }
175 
updateFromDeepShortcutInfo(@onNull final ShortcutInfo shortcutInfo, @NonNull final Context context)176     public void updateFromDeepShortcutInfo(@NonNull final ShortcutInfo shortcutInfo,
177             @NonNull final Context context) {
178         // {@link ShortcutInfo#getActivity} can change during an update. Recreate the intent
179         intent = ShortcutKey.makeIntent(shortcutInfo);
180         title = shortcutInfo.getShortLabel();
181 
182         CharSequence label = shortcutInfo.getLongLabel();
183         if (TextUtils.isEmpty(label)) {
184             label = shortcutInfo.getShortLabel();
185         }
186         contentDescription = context.getPackageManager().getUserBadgedLabel(label, user);
187         if (shortcutInfo.isEnabled()) {
188             runtimeStatusFlags &= ~FLAG_DISABLED_BY_PUBLISHER;
189         } else {
190             Log.w(TAG, "updateFromDeepShortcutInfo: Updated shortcut has been disabled. "
191                     + " package=" + shortcutInfo.getPackage()
192                     + " disabledReason=" + shortcutInfo.getDisabledReason());
193             runtimeStatusFlags |= FLAG_DISABLED_BY_PUBLISHER;
194         }
195 
196         if (shortcutInfo.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
197             runtimeStatusFlags |= FLAG_DISABLED_VERSION_LOWER;
198         } else {
199             runtimeStatusFlags &= ~FLAG_DISABLED_VERSION_LOWER;
200         }
201 
202         Person[] persons = ApiWrapper.INSTANCE.get(context).getPersons(shortcutInfo);
203         personKeys = persons.length == 0 ? Utilities.EMPTY_STRING_ARRAY
204             : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);
205     }
206 
207     /**
208      * {@code true} if the shortcut is disabled due to its app being a lower version.
209      */
isDisabledVersionLower()210     public boolean isDisabledVersionLower() {
211         return (runtimeStatusFlags & FLAG_DISABLED_VERSION_LOWER) != 0;
212     }
213 
214     /** Returns the WorkspaceItemInfo id associated with the deep shortcut. */
getDeepShortcutId()215     public String getDeepShortcutId() {
216         return itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
217                 ? getIntent().getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID) : null;
218     }
219 
220     @NonNull
getPersonKeys()221     public String[] getPersonKeys() {
222         return personKeys;
223     }
224 
225     @Override
getTargetComponent()226     public ComponentName getTargetComponent() {
227         ComponentName cn = super.getTargetComponent();
228         if (cn == null && hasStatusFlag(
229                 FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON | FLAG_RESTORED_ICON)) {
230             // Legacy shortcuts and promise icons with web UI may not have a componentName but just
231             // a packageName. In that case create a empty componentName instead of adding additional
232             // check everywhere.
233             String pkg = intent.getPackage();
234             return pkg == null ? null : new ComponentName(pkg, IconCache.EMPTY_CLASS_NAME);
235         }
236         return cn;
237     }
238 
239     @Override
clone()240     public WorkspaceItemInfo clone() {
241         return new WorkspaceItemInfo(this);
242     }
243 }
244