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