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