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