1 package com.android.launcher3.popup; 2 3 4 import android.app.ActivityOptions; 5 import android.content.Context; 6 import android.content.Intent; 7 import android.graphics.Rect; 8 import android.graphics.drawable.Icon; 9 import android.os.Handler; 10 import android.os.Looper; 11 import android.view.View; 12 import android.view.accessibility.AccessibilityNodeInfo; 13 import android.widget.ImageView; 14 import android.widget.TextView; 15 16 import com.android.launcher3.AbstractFloatingView; 17 import com.android.launcher3.BaseDraggingActivity; 18 import com.android.launcher3.ItemInfo; 19 import com.android.launcher3.Launcher; 20 import com.android.launcher3.LauncherSettings; 21 import com.android.launcher3.R; 22 import com.android.launcher3.WorkspaceItemInfo; 23 import com.android.launcher3.config.FeatureFlags; 24 import com.android.launcher3.model.AppLaunchTracker; 25 import com.android.launcher3.model.WidgetItem; 26 import com.android.launcher3.userevent.nano.LauncherLogProto.Action; 27 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 28 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; 29 import com.android.launcher3.util.InstantAppResolver; 30 import com.android.launcher3.util.PackageManagerHelper; 31 import com.android.launcher3.util.PackageUserKey; 32 import com.android.launcher3.widget.WidgetsBottomSheet; 33 34 import java.util.List; 35 /** 36 * Represents a system shortcut for a given app. The shortcut should have a label and icon, and an 37 * onClickListener that depends on the item that the shortcut services. 38 * 39 * Example system shortcuts, defined as inner classes, include Widgets and AppInfo. 40 * @param <T> 41 */ 42 public abstract class SystemShortcut<T extends BaseDraggingActivity> 43 extends ItemInfo { 44 private final int mIconResId; 45 private final int mLabelResId; 46 private final Icon mIcon; 47 private final CharSequence mLabel; 48 private final CharSequence mContentDescription; 49 private final int mAccessibilityActionId; 50 SystemShortcut(int iconResId, int labelResId)51 public SystemShortcut(int iconResId, int labelResId) { 52 mIconResId = iconResId; 53 mLabelResId = labelResId; 54 mAccessibilityActionId = labelResId; 55 mIcon = null; 56 mLabel = null; 57 mContentDescription = null; 58 } 59 SystemShortcut(Icon icon, CharSequence label, CharSequence contentDescription, int accessibilityActionId)60 public SystemShortcut(Icon icon, CharSequence label, CharSequence contentDescription, 61 int accessibilityActionId) { 62 mIcon = icon; 63 mLabel = label; 64 mContentDescription = contentDescription; 65 mAccessibilityActionId = accessibilityActionId; 66 mIconResId = 0; 67 mLabelResId = 0; 68 } 69 SystemShortcut(SystemShortcut other)70 public SystemShortcut(SystemShortcut other) { 71 mIconResId = other.mIconResId; 72 mLabelResId = other.mLabelResId; 73 mIcon = other.mIcon; 74 mLabel = other.mLabel; 75 mContentDescription = other.mContentDescription; 76 mAccessibilityActionId = other.mAccessibilityActionId; 77 } 78 79 /** 80 * Should be in the left group of icons in app's context menu header. 81 */ isLeftGroup()82 public boolean isLeftGroup() { 83 return false; 84 } 85 setIconAndLabelFor(View iconView, TextView labelView)86 public void setIconAndLabelFor(View iconView, TextView labelView) { 87 if (mIcon != null) { 88 mIcon.loadDrawableAsync(iconView.getContext(), 89 iconView::setBackground, 90 new Handler(Looper.getMainLooper())); 91 } else { 92 iconView.setBackgroundResource(mIconResId); 93 } 94 95 if (mLabel != null) { 96 labelView.setText(mLabel); 97 } else { 98 labelView.setText(mLabelResId); 99 } 100 } 101 setIconAndContentDescriptionFor(ImageView view)102 public void setIconAndContentDescriptionFor(ImageView view) { 103 if (mIcon != null) { 104 mIcon.loadDrawableAsync(view.getContext(), 105 view::setImageDrawable, 106 new Handler(Looper.getMainLooper())); 107 } else { 108 view.setImageResource(mIconResId); 109 } 110 111 view.setContentDescription(getContentDescription(view.getContext())); 112 } 113 getContentDescription(Context context)114 private CharSequence getContentDescription(Context context) { 115 return mContentDescription != null ? mContentDescription : context.getText(mLabelResId); 116 } 117 createAccessibilityAction(Context context)118 public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) { 119 return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId, 120 getContentDescription(context)); 121 } 122 hasHandlerForAction(int action)123 public boolean hasHandlerForAction(int action) { 124 return mAccessibilityActionId == action; 125 } 126 getOnClickListener(T activity, ItemInfo itemInfo)127 public abstract View.OnClickListener getOnClickListener(T activity, ItemInfo itemInfo); 128 129 public static class Widgets extends SystemShortcut<Launcher> { 130 Widgets()131 public Widgets() { 132 super(R.drawable.ic_widget, R.string.widget_button_text); 133 } 134 135 @Override getOnClickListener(final Launcher launcher, final ItemInfo itemInfo)136 public View.OnClickListener getOnClickListener(final Launcher launcher, 137 final ItemInfo itemInfo) { 138 if (itemInfo.getTargetComponent() == null) return null; 139 final List<WidgetItem> widgets = 140 launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey( 141 itemInfo.getTargetComponent().getPackageName(), itemInfo.user)); 142 if (widgets == null) { 143 return null; 144 } 145 return (view) -> { 146 AbstractFloatingView.closeAllOpenViews(launcher); 147 WidgetsBottomSheet widgetsBottomSheet = 148 (WidgetsBottomSheet) launcher.getLayoutInflater().inflate( 149 R.layout.widgets_bottom_sheet, launcher.getDragLayer(), false); 150 widgetsBottomSheet.populateAndShow(itemInfo); 151 launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, 152 ControlType.WIDGETS_BUTTON, view); 153 }; 154 } 155 } 156 157 public static class AppInfo extends SystemShortcut { AppInfo()158 public AppInfo() { 159 super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label); 160 } 161 162 @Override getOnClickListener( BaseDraggingActivity activity, ItemInfo itemInfo)163 public View.OnClickListener getOnClickListener( 164 BaseDraggingActivity activity, ItemInfo itemInfo) { 165 return (view) -> { 166 dismissTaskMenuView(activity); 167 Rect sourceBounds = activity.getViewBounds(view); 168 new PackageManagerHelper(activity).startDetailsActivityForInfo( 169 itemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle()); 170 activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, 171 ControlType.APPINFO_TARGET, view); 172 }; 173 } 174 } 175 176 public static class Install extends SystemShortcut { 177 public Install() { 178 super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label); 179 } 180 181 @Override 182 public View.OnClickListener getOnClickListener( 183 BaseDraggingActivity activity, ItemInfo itemInfo) { 184 boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo) && 185 ((WorkspaceItemInfo) itemInfo).hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI); 186 boolean isInstantApp = false; 187 if (itemInfo instanceof com.android.launcher3.AppInfo) { 188 com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo; 189 isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo); 190 } 191 boolean enabled = supportsWebUI || isInstantApp; 192 if (!enabled) { 193 return null; 194 } 195 return createOnClickListener(activity, itemInfo); 196 } 197 198 public View.OnClickListener createOnClickListener( 199 BaseDraggingActivity activity, ItemInfo itemInfo) { 200 return view -> { 201 Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent( 202 itemInfo.getTargetComponent().getPackageName()); 203 activity.startActivitySafely(view, intent, itemInfo, null); 204 AbstractFloatingView.closeAllOpenViews(activity); 205 }; 206 } 207 } 208 209 public static class DismissPrediction extends SystemShortcut<Launcher> { 210 public DismissPrediction() { 211 super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label); 212 } 213 214 @Override 215 public View.OnClickListener getOnClickListener(Launcher activity, ItemInfo itemInfo) { 216 if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null; 217 if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null; 218 return (view) -> { 219 PopupContainerWithArrow.closeAllOpenViews(activity); 220 activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, 221 ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS); 222 AppLaunchTracker.INSTANCE.get(view.getContext()) 223 .onDismissApp(itemInfo.getTargetComponent(), 224 itemInfo.user, 225 AppLaunchTracker.CONTAINER_PREDICTIONS); 226 }; 227 } 228 } 229 230 protected static void dismissTaskMenuView(BaseDraggingActivity activity) { 231 AbstractFloatingView.closeOpenViews(activity, true, 232 AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE); 233 } 234 } 235