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.Utilities.EXTRA_WALLPAPER_FLAVOR;
19 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_LAUNCH_SOURCE;
20 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
21 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
22 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS;
23 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS;
24 
25 import android.content.Context;
26 import android.content.Intent;
27 import android.graphics.Rect;
28 import android.graphics.RectF;
29 import android.graphics.drawable.Drawable;
30 import android.text.TextUtils;
31 import android.util.ArrayMap;
32 import android.util.AttributeSet;
33 import android.view.MotionEvent;
34 import android.view.View;
35 import android.view.View.OnClickListener;
36 import android.view.View.OnLongClickListener;
37 import android.widget.Toast;
38 
39 import androidx.annotation.Nullable;
40 import androidx.core.content.ContextCompat;
41 
42 import com.android.launcher3.AbstractFloatingView;
43 import com.android.launcher3.Launcher;
44 import com.android.launcher3.LauncherSettings;
45 import com.android.launcher3.R;
46 import com.android.launcher3.Utilities;
47 import com.android.launcher3.logging.StatsLogManager.EventEnum;
48 import com.android.launcher3.model.WidgetsModel;
49 import com.android.launcher3.model.data.WorkspaceItemInfo;
50 import com.android.launcher3.popup.ArrowPopup;
51 import com.android.launcher3.shortcuts.DeepShortcutView;
52 import com.android.launcher3.testing.TestLogging;
53 import com.android.launcher3.testing.TestProtocol;
54 import com.android.launcher3.widget.picker.WidgetsFullSheet;
55 
56 import java.util.ArrayList;
57 import java.util.List;
58 
59 /**
60  * Popup shown on long pressing an empty space in launcher
61  */
62 public class OptionsPopupView extends ArrowPopup<Launcher>
63         implements OnClickListener, OnLongClickListener {
64 
65     private final ArrayMap<View, OptionItem> mItemMap = new ArrayMap<>();
66     private RectF mTargetRect;
67     private boolean mShouldAddArrow;
68 
OptionsPopupView(Context context, AttributeSet attrs)69     public OptionsPopupView(Context context, AttributeSet attrs) {
70         this(context, attrs, 0);
71     }
72 
OptionsPopupView(Context context, AttributeSet attrs, int defStyleAttr)73     public OptionsPopupView(Context context, AttributeSet attrs, int defStyleAttr) {
74         super(context, attrs, defStyleAttr);
75     }
76 
setTargetRect(RectF targetRect)77     public void setTargetRect(RectF targetRect) {
78         mTargetRect = targetRect;
79     }
80 
81     @Override
onClick(View view)82     public void onClick(View view) {
83         handleViewClick(view);
84     }
85 
86     @Override
onLongClick(View view)87     public boolean onLongClick(View view) {
88         return handleViewClick(view);
89     }
90 
handleViewClick(View view)91     private boolean handleViewClick(View view) {
92         OptionItem item = mItemMap.get(view);
93         if (item == null) {
94             return false;
95         }
96         if (item.eventId.getId() > 0) {
97             mActivityContext.getStatsLogManager().logger().log(item.eventId);
98         }
99         if (item.clickListener.onLongClick(view)) {
100             close(true);
101             return true;
102         }
103         return false;
104     }
105 
106     @Override
onControllerInterceptTouchEvent(MotionEvent ev)107     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
108         if (ev.getAction() != MotionEvent.ACTION_DOWN) {
109             return false;
110         }
111         if (getPopupContainer().isEventOverView(this, ev)) {
112             return false;
113         }
114         close(true);
115         return true;
116     }
117 
118     @Override
isOfType(int type)119     protected boolean isOfType(int type) {
120         return (type & TYPE_OPTIONS_POPUP) != 0;
121     }
122 
setShouldAddArrow(boolean shouldAddArrow)123     public void setShouldAddArrow(boolean shouldAddArrow) {
124         mShouldAddArrow = shouldAddArrow;
125     }
126 
127     @Override
shouldAddArrow()128     protected boolean shouldAddArrow() {
129         return mShouldAddArrow;
130     }
131 
132     @Override
getTargetObjectLocation(Rect outPos)133     protected void getTargetObjectLocation(Rect outPos) {
134         mTargetRect.roundOut(outPos);
135     }
136 
show( Launcher launcher, RectF targetRect, List<OptionItem> items, boolean shouldAddArrow)137     public static OptionsPopupView show(
138             Launcher launcher, RectF targetRect, List<OptionItem> items, boolean shouldAddArrow) {
139         return show(launcher, targetRect, items, shouldAddArrow, 0 /* width */);
140     }
141 
show( Launcher launcher, RectF targetRect, List<OptionItem> items, boolean shouldAddArrow, int width)142     public static OptionsPopupView show(
143             Launcher launcher, RectF targetRect, List<OptionItem> items, boolean shouldAddArrow,
144             int width) {
145         OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater()
146                 .inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false);
147         popup.mTargetRect = targetRect;
148         popup.setShouldAddArrow(shouldAddArrow);
149 
150         for (OptionItem item : items) {
151             DeepShortcutView view =
152                     (DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
153             if (width > 0) {
154                 view.getLayoutParams().width = width;
155             }
156             view.getIconView().setBackgroundDrawable(item.icon);
157             view.getBubbleText().setText(item.label);
158             view.setOnClickListener(popup);
159             view.setOnLongClickListener(popup);
160             popup.mItemMap.put(view, item);
161         }
162 
163         popup.addPreDrawForColorExtraction(launcher);
164         popup.show();
165         return popup;
166     }
167 
168     @Override
getChildrenForColorExtraction()169     protected List<View> getChildrenForColorExtraction() {
170         int childCount = getChildCount();
171         ArrayList<View> children = new ArrayList<>(childCount);
172         for (int i = 0; i < childCount; ++i) {
173             children.add(getChildAt(i));
174         }
175         return children;
176     }
177 
178     /**
179      * Returns the list of supported actions
180      */
getOptions(Launcher launcher)181     public static ArrayList<OptionItem> getOptions(Launcher launcher) {
182         ArrayList<OptionItem> options = new ArrayList<>();
183         options.add(new OptionItem(launcher,
184                 R.string.settings_button_text,
185                 R.drawable.ic_setting,
186                 LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
187                 OptionsPopupView::startSettings));
188         if (!WidgetsModel.GO_DISABLE_WIDGETS) {
189             options.add(new OptionItem(launcher,
190                     R.string.widget_button_text,
191                     R.drawable.ic_widget,
192                     LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS,
193                     OptionsPopupView::onWidgetsClicked));
194         }
195         int resString = Utilities.existsStyleWallpapers(launcher) ?
196                 R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
197         int resDrawable = Utilities.existsStyleWallpapers(launcher) ?
198                 R.drawable.ic_palette : R.drawable.ic_wallpaper;
199         options.add(new OptionItem(launcher,
200                 resString,
201                 resDrawable,
202                 IGNORE,
203                 OptionsPopupView::startWallpaperPicker));
204         return options;
205     }
206 
onWidgetsClicked(View view)207     private static boolean onWidgetsClicked(View view) {
208         return openWidgets(Launcher.getLauncher(view.getContext())) != null;
209     }
210 
211     /** Returns WidgetsFullSheet that was opened, or null if nothing was opened. */
212     @Nullable
openWidgets(Launcher launcher)213     public static WidgetsFullSheet openWidgets(Launcher launcher) {
214         if (launcher.getPackageManager().isSafeMode()) {
215             Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
216             return null;
217         } else {
218             AbstractFloatingView floatingView = AbstractFloatingView.getTopOpenViewWithType(
219                     launcher, TYPE_WIDGETS_FULL_SHEET);
220             if (floatingView != null) {
221                 return (WidgetsFullSheet) floatingView;
222             }
223             return WidgetsFullSheet.show(launcher, true /* animated */);
224         }
225     }
226 
startSettings(View view)227     private static boolean startSettings(View view) {
228         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: startSettings");
229         Launcher launcher = Launcher.getLauncher(view.getContext());
230         launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
231                 .setPackage(launcher.getPackageName())
232                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
233         return true;
234     }
235 
236     /**
237      * Event handler for the wallpaper picker button that appears after a long press
238      * on the home screen.
239      */
startWallpaperPicker(View v)240     private static boolean startWallpaperPicker(View v) {
241         Launcher launcher = Launcher.getLauncher(v.getContext());
242         if (!Utilities.isWallpaperAllowed(launcher)) {
243             Toast.makeText(launcher, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show();
244             return false;
245         }
246         Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
247                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
248                 .putExtra(EXTRA_WALLPAPER_OFFSET,
249                         launcher.getWorkspace().getWallpaperOffsetForCenterPage())
250                 .putExtra(EXTRA_WALLPAPER_LAUNCH_SOURCE, "app_launched_launcher");
251         if (!Utilities.existsStyleWallpapers(launcher)) {
252             intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "wallpaper_only");
253         } else {
254             intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "focus_wallpaper");
255         }
256         String pickerPackage = launcher.getString(R.string.wallpaper_picker_package);
257         if (!TextUtils.isEmpty(pickerPackage)) {
258             intent.setPackage(pickerPackage);
259         }
260         return launcher.startActivitySafely(v, intent, placeholderInfo(intent));
261     }
262 
placeholderInfo(Intent intent)263     static WorkspaceItemInfo placeholderInfo(Intent intent) {
264         WorkspaceItemInfo placeholderInfo = new WorkspaceItemInfo();
265         placeholderInfo.intent = intent;
266         placeholderInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
267         placeholderInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS;
268         return placeholderInfo;
269     }
270 
271     public static class OptionItem {
272 
273         // Used to create AccessibilityNodeInfo in AccessibilityActionsView.java.
274         public final int labelRes;
275 
276         public final CharSequence label;
277         public final Drawable icon;
278         public final EventEnum eventId;
279         public final OnLongClickListener clickListener;
280 
OptionItem(Context context, int labelRes, int iconRes, EventEnum eventId, OnLongClickListener clickListener)281         public OptionItem(Context context, int labelRes, int iconRes, EventEnum eventId,
282                 OnLongClickListener clickListener) {
283             this.labelRes = labelRes;
284             this.label = context.getText(labelRes);
285             this.icon = ContextCompat.getDrawable(context, iconRes);
286             this.eventId = eventId;
287             this.clickListener = clickListener;
288         }
289 
OptionItem(CharSequence label, Drawable icon, EventEnum eventId, OnLongClickListener clickListener)290         public OptionItem(CharSequence label, Drawable icon, EventEnum eventId,
291                 OnLongClickListener clickListener) {
292             this.labelRes = 0;
293             this.label = label;
294             this.icon = icon;
295             this.eventId = eventId;
296             this.clickListener = clickListener;
297         }
298     }
299 }
300