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