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