• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.views;
17 
18 import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
19 import static com.android.launcher3.LauncherState.EDIT_MODE;
20 import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
21 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
22 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_TAP_OR_LONGPRESS;
23 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS;
24 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS;
25 
26 import android.content.Context;
27 import android.content.Intent;
28 import android.graphics.Rect;
29 import android.graphics.RectF;
30 import android.graphics.drawable.Drawable;
31 import android.text.TextUtils;
32 import android.util.ArrayMap;
33 import android.util.AttributeSet;
34 import android.view.MotionEvent;
35 import android.view.View;
36 import android.view.View.OnClickListener;
37 import android.view.View.OnLongClickListener;
38 import android.view.ViewGroup;
39 import android.widget.Toast;
40 
41 import androidx.annotation.Nullable;
42 import androidx.core.content.ContextCompat;
43 
44 import com.android.launcher3.AbstractFloatingView;
45 import com.android.launcher3.Launcher;
46 import com.android.launcher3.LauncherSettings;
47 import com.android.launcher3.R;
48 import com.android.launcher3.Utilities;
49 import com.android.launcher3.logging.StatsLogManager.EventEnum;
50 import com.android.launcher3.model.data.WorkspaceItemInfo;
51 import com.android.launcher3.popup.ArrowPopup;
52 import com.android.launcher3.shortcuts.DeepShortcutView;
53 import com.android.launcher3.testing.TestLogging;
54 import com.android.launcher3.testing.shared.TestProtocol;
55 import com.android.launcher3.widget.picker.WidgetsFullSheet;
56 
57 import java.util.ArrayList;
58 import java.util.List;
59 
60 /**
61  * Popup shown on long pressing an empty space in launcher
62  *
63  * @param <T> The context showing this popup.
64  */
65 public class OptionsPopupView<T extends Context & ActivityContext> extends ArrowPopup<T>
66         implements OnClickListener, OnLongClickListener {
67 
68     // An intent extra to indicate the horizontal scroll of the wallpaper.
69     private static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
70     private static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
71     // An intent extra to indicate the launch source by launcher.
72     private static final String EXTRA_WALLPAPER_LAUNCH_SOURCE =
73             "com.android.wallpaper.LAUNCH_SOURCE";
74 
75     private final ArrayMap<View, OptionItem> mItemMap = new ArrayMap<>();
76     private RectF mTargetRect;
77     private boolean mShouldAddArrow;
78 
OptionsPopupView(Context context, AttributeSet attrs)79     public OptionsPopupView(Context context, AttributeSet attrs) {
80         this(context, attrs, 0);
81     }
82 
OptionsPopupView(Context context, AttributeSet attrs, int defStyleAttr)83     public OptionsPopupView(Context context, AttributeSet attrs, int defStyleAttr) {
84         super(context, attrs, defStyleAttr);
85     }
86 
setTargetRect(RectF targetRect)87     public void setTargetRect(RectF targetRect) {
88         mTargetRect = targetRect;
89     }
90 
91     @Override
onClick(View view)92     public void onClick(View view) {
93         handleViewClick(view);
94     }
95 
96     @Override
onLongClick(View view)97     public boolean onLongClick(View view) {
98         return handleViewClick(view);
99     }
100 
handleViewClick(View view)101     private boolean handleViewClick(View view) {
102         OptionItem item = mItemMap.get(view);
103         if (item == null) {
104             return false;
105         }
106         if (item.eventId.getId() > 0) {
107             mActivityContext.getStatsLogManager().logger().log(item.eventId);
108         }
109         if (item.clickListener.onLongClick(view)) {
110             close(true);
111             return true;
112         }
113         return false;
114     }
115 
116     @Override
onControllerInterceptTouchEvent(MotionEvent ev)117     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
118         if (ev.getAction() != MotionEvent.ACTION_DOWN) {
119             return false;
120         }
121         if (getPopupContainer().isEventOverView(this, ev)) {
122             return false;
123         }
124         close(true);
125         return true;
126     }
127 
128     @Override
isOfType(int type)129     protected boolean isOfType(int type) {
130         return (type & TYPE_OPTIONS_POPUP) != 0;
131     }
132 
setShouldAddArrow(boolean shouldAddArrow)133     public void setShouldAddArrow(boolean shouldAddArrow) {
134         mShouldAddArrow = shouldAddArrow;
135     }
136 
137     @Override
shouldAddArrow()138     protected boolean shouldAddArrow() {
139         return mShouldAddArrow;
140     }
141 
142     @Override
getTargetObjectLocation(Rect outPos)143     protected void getTargetObjectLocation(Rect outPos) {
144         mTargetRect.roundOut(outPos);
145     }
146 
147     @Override
assignMarginsAndBackgrounds(ViewGroup viewGroup)148     public void assignMarginsAndBackgrounds(ViewGroup viewGroup) {
149         assignMarginsAndBackgrounds(viewGroup, mColors[0]);
150         // last shortcut doesn't need bottom margin
151         final int count = viewGroup.getChildCount() - 1;
152         for (int i = 0; i < count; i++) {
153             // These are shortcuts and not shortcut containers, but they still need bottom margin
154             MarginLayoutParams mlp = (MarginLayoutParams) viewGroup.getChildAt(i).getLayoutParams();
155             mlp.bottomMargin = mChildContainerMargin;
156         }
157     }
158 
show( ActivityContext activityContext, RectF targetRect, List<OptionItem> items, boolean shouldAddArrow)159     public static <T extends Context & ActivityContext> OptionsPopupView<T> show(
160             ActivityContext activityContext,
161             RectF targetRect,
162             List<OptionItem> items,
163             boolean shouldAddArrow) {
164         return show(activityContext, targetRect, items, shouldAddArrow, 0 /* width */);
165     }
166 
167     @Nullable
show( @ullable ActivityContext activityContext, RectF targetRect, List<OptionItem> items, boolean shouldAddArrow, int width)168     private static <T extends Context & ActivityContext> OptionsPopupView<T> show(
169             @Nullable ActivityContext activityContext,
170             RectF targetRect,
171             List<OptionItem> items,
172             boolean shouldAddArrow,
173             int width) {
174         if (activityContext == null) {
175             return null;
176         }
177         OptionsPopupView<T> popup = (OptionsPopupView<T>) activityContext.getLayoutInflater()
178                 .inflate(R.layout.longpress_options_menu, activityContext.getDragLayer(), false);
179         popup.mTargetRect = targetRect;
180         popup.setShouldAddArrow(shouldAddArrow);
181 
182         for (OptionItem item : items) {
183             DeepShortcutView view = popup.inflateAndAdd(R.layout.system_shortcut, popup);
184             if (width > 0) {
185                 view.getLayoutParams().width = width;
186             }
187             view.getIconView().setBackgroundDrawable(item.icon);
188             view.getBubbleText().setText(item.label);
189             view.setOnClickListener(popup);
190             view.setOnLongClickListener(popup);
191             popup.mItemMap.put(view, item);
192         }
193 
194         popup.show();
195         return popup;
196     }
197 
198     /**
199      * Returns the list of supported actions
200      */
getOptions(Launcher launcher)201     public static ArrayList<OptionItem> getOptions(Launcher launcher) {
202         ArrayList<OptionItem> options = new ArrayList<>();
203         options.add(new OptionItem(launcher,
204                 R.string.styles_wallpaper_button_text,
205                 R.drawable.ic_palette,
206                 IGNORE,
207                 OptionsPopupView::startWallpaperPicker));
208         if (WIDGETS_ENABLED) {
209             options.add(new OptionItem(launcher,
210                     R.string.widget_button_text,
211                     R.drawable.ic_widget,
212                     LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS,
213                     OptionsPopupView::onWidgetsClicked));
214         }
215         if (MULTI_SELECT_EDIT_MODE.get()) {
216             options.add(new OptionItem(launcher,
217                     R.string.edit_home_screen,
218                     R.drawable.enter_home_gardening_icon,
219                     LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
220                     OptionsPopupView::enterHomeGardening));
221         }
222         options.add(new OptionItem(launcher,
223                 R.string.all_apps_button_label,
224                 R.drawable.ic_apps,
225                 LAUNCHER_ALL_APPS_TAP_OR_LONGPRESS,
226                 OptionsPopupView::enterAllApps));
227         options.add(new OptionItem(launcher,
228                 R.string.settings_button_text,
229                 R.drawable.ic_setting,
230                 LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
231                 OptionsPopupView::startSettings));
232         return options;
233     }
234 
235     /**
236      * Used by the options to open All Apps, uses an intent as to not tie the implementation of
237      * opening All Apps with OptionsPopup, instead it uses the public API to open All Apps.
238      */
enterAllApps(View view)239     public static boolean enterAllApps(View view) {
240         Launcher launcher = Launcher.getLauncher(view.getContext());
241         launcher.startActivity(
242                 new Intent(Intent.ACTION_ALL_APPS)
243                 .setComponent(launcher.getComponentName())
244                 .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
245         );
246         return true;
247     }
248 
enterHomeGardening(View view)249     private static boolean enterHomeGardening(View view) {
250         Launcher launcher = Launcher.getLauncher(view.getContext());
251         launcher.getStateManager().goToState(EDIT_MODE);
252         return true;
253     }
254 
onWidgetsClicked(View view)255     private static boolean onWidgetsClicked(View view) {
256         return openWidgets(Launcher.getLauncher(view.getContext())) != null;
257     }
258 
259     /** Returns WidgetsFullSheet that was opened, or null if nothing was opened. */
260     @Nullable
openWidgets(Launcher launcher)261     public static WidgetsFullSheet openWidgets(Launcher launcher) {
262         if (launcher.getPackageManager().isSafeMode()) {
263             Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
264             return null;
265         } else {
266             AbstractFloatingView floatingView = AbstractFloatingView.getTopOpenViewWithType(
267                     launcher, TYPE_WIDGETS_FULL_SHEET);
268             if (floatingView != null) {
269                 return (WidgetsFullSheet) floatingView;
270             }
271             return WidgetsFullSheet.show(launcher, true /* animated */);
272         }
273     }
274 
startSettings(View view)275     private static boolean startSettings(View view) {
276         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: startSettings");
277         Launcher launcher = Launcher.getLauncher(view.getContext());
278         launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
279                 .setPackage(launcher.getPackageName())
280                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
281         return true;
282     }
283 
284     /**
285      * Event handler for the wallpaper picker button that appears after a long press
286      * on the home screen.
287      */
startWallpaperPicker(View v)288     private static boolean startWallpaperPicker(View v) {
289         Launcher launcher = Launcher.getLauncher(v.getContext());
290         if (!Utilities.isWallpaperAllowed(launcher)) {
291             String message = launcher.getStringCache() != null
292                     ? launcher.getStringCache().disabledByAdminMessage
293                     : launcher.getString(R.string.msg_disabled_by_admin);
294             Toast.makeText(launcher, message, Toast.LENGTH_SHORT).show();
295             return false;
296         }
297         Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
298                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
299                 .putExtra(EXTRA_WALLPAPER_OFFSET,
300                         launcher.getWorkspace().getWallpaperOffsetForCenterPage())
301                 .putExtra(EXTRA_WALLPAPER_LAUNCH_SOURCE, "app_launched_launcher")
302                 .putExtra(EXTRA_WALLPAPER_FLAVOR, "focus_wallpaper");
303         String pickerPackage = launcher.getString(R.string.wallpaper_picker_package);
304         if (!TextUtils.isEmpty(pickerPackage)) {
305             intent.setPackage(pickerPackage);
306         }
307         return launcher.startActivitySafely(v, intent, placeholderInfo(intent)) != null;
308     }
309 
placeholderInfo(Intent intent)310     static WorkspaceItemInfo placeholderInfo(Intent intent) {
311         WorkspaceItemInfo placeholderInfo = new WorkspaceItemInfo();
312         placeholderInfo.intent = intent;
313         placeholderInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS;
314         return placeholderInfo;
315     }
316 
317     public static class OptionItem {
318 
319         // Used to create AccessibilityNodeInfo in AccessibilityActionsView.java.
320         public final int labelRes;
321 
322         public final CharSequence label;
323         public final Drawable icon;
324         public final EventEnum eventId;
325         public final OnLongClickListener clickListener;
326 
OptionItem(Context context, int labelRes, int iconRes, EventEnum eventId, OnLongClickListener clickListener)327         public OptionItem(Context context, int labelRes, int iconRes, EventEnum eventId,
328                 OnLongClickListener clickListener) {
329             this.labelRes = labelRes;
330             this.label = context.getText(labelRes);
331             this.icon = ContextCompat.getDrawable(context, iconRes);
332             this.eventId = eventId;
333             this.clickListener = clickListener;
334         }
335 
OptionItem(CharSequence label, Drawable icon, EventEnum eventId, OnLongClickListener clickListener)336         public OptionItem(CharSequence label, Drawable icon, EventEnum eventId,
337                 OnLongClickListener clickListener) {
338             this.labelRes = 0;
339             this.label = label;
340             this.icon = icon;
341             this.eventId = eventId;
342             this.clickListener = clickListener;
343         }
344     }
345 }
346