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_OFFSET;
20 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
21 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS;
22 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS;
23 
24 import android.content.Context;
25 import android.content.Intent;
26 import android.graphics.Rect;
27 import android.graphics.RectF;
28 import android.text.TextUtils;
29 import android.util.ArrayMap;
30 import android.util.AttributeSet;
31 import android.view.MotionEvent;
32 import android.view.View;
33 import android.view.View.OnClickListener;
34 import android.view.View.OnLongClickListener;
35 import android.widget.Toast;
36 
37 import androidx.annotation.Nullable;
38 import androidx.annotation.VisibleForTesting;
39 
40 import com.android.launcher3.Launcher;
41 import com.android.launcher3.LauncherSettings;
42 import com.android.launcher3.R;
43 import com.android.launcher3.Utilities;
44 import com.android.launcher3.logging.StatsLogManager.EventEnum;
45 import com.android.launcher3.model.WidgetsModel;
46 import com.android.launcher3.model.data.WorkspaceItemInfo;
47 import com.android.launcher3.popup.ArrowPopup;
48 import com.android.launcher3.shortcuts.DeepShortcutView;
49 import com.android.launcher3.testing.TestLogging;
50 import com.android.launcher3.testing.TestProtocol;
51 import com.android.launcher3.widget.WidgetsFullSheet;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 
56 /**
57  * Popup shown on long pressing an empty space in launcher
58  */
59 public class OptionsPopupView extends ArrowPopup
60         implements OnClickListener, OnLongClickListener {
61 
62     private final ArrayMap<View, OptionItem> mItemMap = new ArrayMap<>();
63     private RectF mTargetRect;
64 
OptionsPopupView(Context context, AttributeSet attrs)65     public OptionsPopupView(Context context, AttributeSet attrs) {
66         this(context, attrs, 0);
67     }
68 
OptionsPopupView(Context context, AttributeSet attrs, int defStyleAttr)69     public OptionsPopupView(Context context, AttributeSet attrs, int defStyleAttr) {
70         super(context, attrs, defStyleAttr);
71     }
72 
73     @Override
onClick(View view)74     public void onClick(View view) {
75         handleViewClick(view);
76     }
77 
78     @Override
onLongClick(View view)79     public boolean onLongClick(View view) {
80         return handleViewClick(view);
81     }
82 
handleViewClick(View view)83     private boolean handleViewClick(View view) {
84         OptionItem item = mItemMap.get(view);
85         if (item == null) {
86             return false;
87         }
88         if (item.mEventId.getId() > 0) {
89             mLauncher.getStatsLogManager().logger().log(item.mEventId);
90         }
91         if (item.mClickListener.onLongClick(view)) {
92             close(true);
93             return true;
94         }
95         return false;
96     }
97 
98     @Override
onControllerInterceptTouchEvent(MotionEvent ev)99     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
100         if (ev.getAction() != MotionEvent.ACTION_DOWN) {
101             return false;
102         }
103         if (getPopupContainer().isEventOverView(this, ev)) {
104             return false;
105         }
106         close(true);
107         return true;
108     }
109 
110     @Override
logActionCommand(int command)111     public void logActionCommand(int command) {
112         // TODO:
113     }
114 
115     @Override
isOfType(int type)116     protected boolean isOfType(int type) {
117         return (type & TYPE_OPTIONS_POPUP) != 0;
118     }
119 
120     @Override
getTargetObjectLocation(Rect outPos)121     protected void getTargetObjectLocation(Rect outPos) {
122         mTargetRect.roundOut(outPos);
123     }
124 
show(Launcher launcher, RectF targetRect, List<OptionItem> items)125     public static void show(Launcher launcher, RectF targetRect, List<OptionItem> items) {
126         OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater()
127                 .inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false);
128         popup.mTargetRect = targetRect;
129 
130         for (OptionItem item : items) {
131             DeepShortcutView view =
132                     (DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
133             view.getIconView().setBackgroundResource(item.mIconRes);
134             view.getBubbleText().setText(item.mLabelRes);
135             view.setDividerVisibility(View.INVISIBLE);
136             view.setOnClickListener(popup);
137             view.setOnLongClickListener(popup);
138             popup.mItemMap.put(view, item);
139         }
140         popup.reorderAndShow(popup.getChildCount());
141     }
142 
143     @VisibleForTesting
getOptionsPopup(Launcher launcher)144     public static ArrowPopup getOptionsPopup(Launcher launcher) {
145         return launcher.findViewById(R.id.deep_shortcuts_container);
146     }
147 
showDefaultOptions(Launcher launcher, float x, float y)148     public static void showDefaultOptions(Launcher launcher, float x, float y) {
149         float halfSize = launcher.getResources().getDimension(R.dimen.options_menu_thumb_size) / 2;
150         if (x < 0 || y < 0) {
151             x = launcher.getDragLayer().getWidth() / 2;
152             y = launcher.getDragLayer().getHeight() / 2;
153         }
154         RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
155 
156         ArrayList<OptionItem> options = new ArrayList<>();
157         int resString = Utilities.existsStyleWallpapers(launcher) ?
158                 R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
159         int resDrawable = Utilities.existsStyleWallpapers(launcher) ?
160                 R.drawable.ic_palette : R.drawable.ic_wallpaper;
161         options.add(new OptionItem(resString, resDrawable,
162                 IGNORE,
163                 OptionsPopupView::startWallpaperPicker));
164         if (!WidgetsModel.GO_DISABLE_WIDGETS) {
165             options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget,
166                     LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS,
167                     OptionsPopupView::onWidgetsClicked));
168         }
169         options.add(new OptionItem(R.string.settings_button_text, R.drawable.ic_setting,
170                 LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
171                 OptionsPopupView::startSettings));
172 
173         show(launcher, target, options);
174     }
175 
onWidgetsClicked(View view)176     public static boolean onWidgetsClicked(View view) {
177         return openWidgets(Launcher.getLauncher(view.getContext())) != null;
178     }
179 
180     /** Returns WidgetsFullSheet that was opened, or null if nothing was opened. */
181     @Nullable
openWidgets(Launcher launcher)182     public static WidgetsFullSheet openWidgets(Launcher launcher) {
183         if (launcher.getPackageManager().isSafeMode()) {
184             Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
185             return null;
186         } else {
187             return WidgetsFullSheet.show(launcher, true /* animated */);
188         }
189     }
190 
startSettings(View view)191     public static boolean startSettings(View view) {
192         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: startSettings");
193         Launcher launcher = Launcher.getLauncher(view.getContext());
194         launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
195                 .setPackage(launcher.getPackageName())
196                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
197         return true;
198     }
199 
200     /**
201      * Event handler for the wallpaper picker button that appears after a long press
202      * on the home screen.
203      */
startWallpaperPicker(View v)204     public static boolean startWallpaperPicker(View v) {
205         Launcher launcher = Launcher.getLauncher(v.getContext());
206         if (!Utilities.isWallpaperAllowed(launcher)) {
207             Toast.makeText(launcher, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show();
208             return false;
209         }
210         Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
211                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
212                 .putExtra(EXTRA_WALLPAPER_OFFSET,
213                         launcher.getWorkspace().getWallpaperOffsetForCenterPage());
214         if (!Utilities.existsStyleWallpapers(launcher)) {
215             intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "wallpaper_only");
216         } else {
217             intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "focus_wallpaper");
218         }
219         String pickerPackage = launcher.getString(R.string.wallpaper_picker_package);
220         if (!TextUtils.isEmpty(pickerPackage)) {
221             intent.setPackage(pickerPackage);
222         }
223         return launcher.startActivitySafely(v, intent, dummyInfo(intent), null);
224     }
225 
dummyInfo(Intent intent)226     static WorkspaceItemInfo dummyInfo(Intent intent) {
227         WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
228         dummyInfo.intent = intent;
229         dummyInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
230         dummyInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS;
231         return dummyInfo;
232     }
233 
234     public static class OptionItem {
235 
236         private final int mLabelRes;
237         private final int mIconRes;
238         private final EventEnum mEventId;
239         private final OnLongClickListener mClickListener;
240 
OptionItem(int labelRes, int iconRes, EventEnum eventId, OnLongClickListener clickListener)241         public OptionItem(int labelRes, int iconRes, EventEnum eventId,
242                 OnLongClickListener clickListener) {
243             mLabelRes = labelRes;
244             mIconRes = iconRes;
245             mEventId = eventId;
246             mClickListener = clickListener;
247         }
248     }
249 }
250