1 package com.android.launcher3.popup; 2 3 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP; 4 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP; 5 6 import android.app.ActivityOptions; 7 import android.content.Context; 8 import android.content.Intent; 9 import android.graphics.Rect; 10 import android.view.View; 11 import android.view.accessibility.AccessibilityNodeInfo; 12 import android.widget.ImageView; 13 import android.widget.TextView; 14 15 import androidx.annotation.Nullable; 16 17 import com.android.launcher3.AbstractFloatingView; 18 import com.android.launcher3.BaseDraggingActivity; 19 import com.android.launcher3.Launcher; 20 import com.android.launcher3.R; 21 import com.android.launcher3.Utilities; 22 import com.android.launcher3.model.WidgetItem; 23 import com.android.launcher3.model.data.ItemInfo; 24 import com.android.launcher3.model.data.WorkspaceItemInfo; 25 import com.android.launcher3.util.InstantAppResolver; 26 import com.android.launcher3.util.PackageManagerHelper; 27 import com.android.launcher3.util.PackageUserKey; 28 import com.android.launcher3.views.ActivityContext; 29 import com.android.launcher3.widget.WidgetsBottomSheet; 30 31 import java.util.List; 32 33 /** 34 * Represents a system shortcut for a given app. The shortcut should have a label and icon, and an 35 * onClickListener that depends on the item that the shortcut services. 36 * 37 * Example system shortcuts, defined as inner classes, include Widgets and AppInfo. 38 * @param <T> 39 */ 40 public abstract class SystemShortcut<T extends Context & ActivityContext> extends ItemInfo 41 implements View.OnClickListener { 42 43 private final int mIconResId; 44 protected final int mLabelResId; 45 protected int mAccessibilityActionId; 46 47 protected final T mTarget; 48 protected final ItemInfo mItemInfo; 49 50 /** 51 * Indicates if it's invokable or not through some disabled UI 52 */ 53 private boolean isEnabled = true; 54 SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo)55 public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) { 56 mIconResId = iconResId; 57 mLabelResId = labelResId; 58 mAccessibilityActionId = labelResId; 59 mTarget = target; 60 mItemInfo = itemInfo; 61 } 62 SystemShortcut(SystemShortcut<T> other)63 public SystemShortcut(SystemShortcut<T> other) { 64 mIconResId = other.mIconResId; 65 mLabelResId = other.mLabelResId; 66 mAccessibilityActionId = other.mAccessibilityActionId; 67 mTarget = other.mTarget; 68 mItemInfo = other.mItemInfo; 69 } 70 71 /** 72 * Should be in the left group of icons in app's context menu header. 73 */ isLeftGroup()74 public boolean isLeftGroup() { 75 return false; 76 } 77 setIconAndLabelFor(View iconView, TextView labelView)78 public void setIconAndLabelFor(View iconView, TextView labelView) { 79 iconView.setBackgroundResource(mIconResId); 80 labelView.setText(mLabelResId); 81 } 82 setIconAndContentDescriptionFor(ImageView view)83 public void setIconAndContentDescriptionFor(ImageView view) { 84 view.setImageResource(mIconResId); 85 view.setContentDescription(view.getContext().getText(mLabelResId)); 86 } 87 createAccessibilityAction(Context context)88 public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) { 89 return new AccessibilityNodeInfo.AccessibilityAction( 90 mAccessibilityActionId, context.getText(mLabelResId)); 91 } 92 setEnabled(boolean enabled)93 public void setEnabled(boolean enabled) { 94 isEnabled = enabled; 95 } 96 isEnabled()97 public boolean isEnabled() { 98 return isEnabled; 99 } 100 hasHandlerForAction(int action)101 public boolean hasHandlerForAction(int action) { 102 return mAccessibilityActionId == action; 103 } 104 105 public interface Factory<T extends Context & ActivityContext> { 106 getShortcut(T activity, ItemInfo itemInfo)107 @Nullable SystemShortcut<T> getShortcut(T activity, ItemInfo itemInfo); 108 } 109 110 public static final Factory<Launcher> WIDGETS = (launcher, itemInfo) -> { 111 if (itemInfo.getTargetComponent() == null) return null; 112 final List<WidgetItem> widgets = 113 launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey( 114 itemInfo.getTargetComponent().getPackageName(), itemInfo.user)); 115 if (widgets.isEmpty()) { 116 return null; 117 } 118 return new Widgets(launcher, itemInfo); 119 }; 120 121 public static class Widgets extends SystemShortcut<Launcher> { Widgets(Launcher target, ItemInfo itemInfo)122 public Widgets(Launcher target, ItemInfo itemInfo) { 123 super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo); 124 } 125 126 @Override onClick(View view)127 public void onClick(View view) { 128 AbstractFloatingView.closeAllOpenViews(mTarget); 129 WidgetsBottomSheet widgetsBottomSheet = 130 (WidgetsBottomSheet) mTarget.getLayoutInflater().inflate( 131 R.layout.widgets_bottom_sheet, mTarget.getDragLayer(), false); 132 widgetsBottomSheet.populateAndShow(mItemInfo); 133 mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo) 134 .log(LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP); 135 } 136 } 137 138 public static final Factory<BaseDraggingActivity> APP_INFO = AppInfo::new; 139 140 public static class AppInfo<T extends Context & ActivityContext> extends SystemShortcut<T> { 141 142 @Nullable 143 private SplitAccessibilityInfo mSplitA11yInfo; 144 AppInfo(T target, ItemInfo itemInfo)145 public AppInfo(T target, ItemInfo itemInfo) { 146 super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target, 147 itemInfo); 148 } 149 150 /** 151 * Constructor used by overview for staged split to provide custom A11y information. 152 * 153 * Future improvements considerations: 154 * Have the logic in {@link #createAccessibilityAction(Context)} be moved to super 155 * call in {@link SystemShortcut#createAccessibilityAction(Context)} by having 156 * SystemShortcut be aware of TaskContainers and staged split. 157 * That way it could directly create the correct node info for any shortcut that supports 158 * split, but then we'll need custom resIDs for each pair of shortcuts. 159 */ AppInfo(T target, ItemInfo itemInfo, SplitAccessibilityInfo accessibilityInfo)160 public AppInfo(T target, ItemInfo itemInfo, SplitAccessibilityInfo accessibilityInfo) { 161 this(target, itemInfo); 162 mSplitA11yInfo = accessibilityInfo; 163 mAccessibilityActionId = accessibilityInfo.nodeId; 164 } 165 166 @Override createAccessibilityAction( Context context)167 public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction( 168 Context context) { 169 if (mSplitA11yInfo != null && mSplitA11yInfo.containsMultipleTasks) { 170 String accessibilityLabel = context.getString(R.string.split_app_info_accessibility, 171 mSplitA11yInfo.taskTitle); 172 return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId, 173 accessibilityLabel); 174 } else { 175 return super.createAccessibilityAction(context); 176 } 177 } 178 179 @Override onClick(View view)180 public void onClick(View view) { 181 dismissTaskMenuView(mTarget); 182 Rect sourceBounds = Utilities.getViewBounds(view); 183 new PackageManagerHelper(mTarget).startDetailsActivityForInfo( 184 mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle()); 185 mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo) 186 .log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP); 187 } 188 189 public static class SplitAccessibilityInfo { 190 public final boolean containsMultipleTasks; 191 public final CharSequence taskTitle; 192 public final int nodeId; 193 SplitAccessibilityInfo(boolean containsMultipleTasks, CharSequence taskTitle, int nodeId)194 public SplitAccessibilityInfo(boolean containsMultipleTasks, 195 CharSequence taskTitle, int nodeId) { 196 this.containsMultipleTasks = containsMultipleTasks; 197 this.taskTitle = taskTitle; 198 this.nodeId = nodeId; 199 } 200 } 201 } 202 203 public static final Factory<BaseDraggingActivity> INSTALL = (activity, itemInfo) -> { 204 boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo) 205 && ((WorkspaceItemInfo) itemInfo).hasStatusFlag( 206 WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI); 207 boolean isInstantApp = false; 208 if (itemInfo instanceof com.android.launcher3.model.data.AppInfo) { 209 com.android.launcher3.model.data.AppInfo 210 appInfo = (com.android.launcher3.model.data.AppInfo) itemInfo; 211 isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo); 212 } 213 boolean enabled = supportsWebUI || isInstantApp; 214 if (!enabled) { 215 return null; 216 } 217 return new Install(activity, itemInfo); 218 }; 219 220 public static class Install extends SystemShortcut<BaseDraggingActivity> { 221 Install(BaseDraggingActivity target, ItemInfo itemInfo)222 public Install(BaseDraggingActivity target, ItemInfo itemInfo) { 223 super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label, 224 target, itemInfo); 225 } 226 227 @Override onClick(View view)228 public void onClick(View view) { 229 Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent( 230 mItemInfo.getTargetComponent().getPackageName()); 231 mTarget.startActivitySafely(view, intent, mItemInfo); 232 AbstractFloatingView.closeAllOpenViews(mTarget); 233 } 234 } 235 dismissTaskMenuView(T activity)236 public static <T extends Context & ActivityContext> void dismissTaskMenuView(T activity) { 237 AbstractFloatingView.closeOpenViews(activity, true, 238 AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE); 239 } 240 } 241