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