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