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