• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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