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