1 /* 2 * Copyright (C) 2017 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.widget; 17 18 import android.content.Context; 19 import android.graphics.Point; 20 import android.graphics.Rect; 21 import android.util.AttributeSet; 22 import android.view.View; 23 import android.view.View.OnClickListener; 24 import android.view.View.OnLongClickListener; 25 import android.widget.Toast; 26 27 import androidx.annotation.GuardedBy; 28 import androidx.annotation.Nullable; 29 import androidx.core.view.ViewCompat; 30 31 import com.android.launcher3.DeviceProfile; 32 import com.android.launcher3.DragSource; 33 import com.android.launcher3.DropTarget.DragObject; 34 import com.android.launcher3.Insettable; 35 import com.android.launcher3.Launcher; 36 import com.android.launcher3.R; 37 import com.android.launcher3.Utilities; 38 import com.android.launcher3.dragndrop.DragOptions; 39 import com.android.launcher3.popup.PopupDataProvider; 40 import com.android.launcher3.testing.TestLogging; 41 import com.android.launcher3.testing.TestProtocol; 42 import com.android.launcher3.touch.ItemLongClickListener; 43 import com.android.launcher3.util.SystemUiController; 44 import com.android.launcher3.util.Themes; 45 import com.android.launcher3.views.AbstractSlideInView; 46 import com.android.launcher3.views.ArrowTipView; 47 48 /** 49 * Base class for various widgets popup 50 */ 51 public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher> 52 implements OnClickListener, OnLongClickListener, DragSource, 53 PopupDataProvider.PopupDataChangeListener, Insettable { 54 55 protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN = 56 "launcher.widgets_education_tip_seen"; 57 protected final Rect mInsets = new Rect(); 58 59 /* Touch handling related member variables. */ 60 private Toast mWidgetInstructionToast; 61 BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr)62 public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) { 63 super(context, attrs, defStyleAttr); 64 } 65 getScrimColor(Context context)66 protected int getScrimColor(Context context) { 67 return context.getResources().getColor(R.color.widgets_picker_scrim); 68 } 69 70 @Override onAttachedToWindow()71 protected void onAttachedToWindow() { 72 super.onAttachedToWindow(); 73 mActivityContext.getPopupDataProvider().setChangeListener(this); 74 } 75 76 @Override onDetachedFromWindow()77 protected void onDetachedFromWindow() { 78 super.onDetachedFromWindow(); 79 mActivityContext.getPopupDataProvider().setChangeListener(null); 80 } 81 82 @Override onClick(View v)83 public final void onClick(View v) { 84 Object tag = null; 85 if (v instanceof WidgetCell) { 86 tag = v.getTag(); 87 } else if (v.getParent() instanceof WidgetCell) { 88 tag = ((WidgetCell) v.getParent()).getTag(); 89 } 90 if (tag instanceof PendingAddShortcutInfo) { 91 mWidgetInstructionToast = showShortcutToast(getContext(), mWidgetInstructionToast); 92 } else { 93 mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast); 94 } 95 96 } 97 98 @Override onLongClick(View v)99 public boolean onLongClick(View v) { 100 TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick"); 101 v.cancelLongPress(); 102 if (!ItemLongClickListener.canStartDrag(mActivityContext)) return false; 103 104 if (v instanceof WidgetCell) { 105 return beginDraggingWidget((WidgetCell) v); 106 } else if (v.getParent() instanceof WidgetCell) { 107 return beginDraggingWidget((WidgetCell) v.getParent()); 108 } 109 return true; 110 } 111 112 @Override setInsets(Rect insets)113 public void setInsets(Rect insets) { 114 mInsets.set(insets); 115 } 116 117 118 /** 119 * Measures the dimension of this view and its children by taking system insets, navigation bar, 120 * status bar, into account. 121 */ 122 @GuardedBy("MainThread") doMeasure(int widthMeasureSpec, int heightMeasureSpec)123 protected void doMeasure(int widthMeasureSpec, int heightMeasureSpec) { 124 DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); 125 int widthUsed; 126 if (mInsets.bottom > 0) { 127 widthUsed = mInsets.left + mInsets.right; 128 } else { 129 Rect padding = deviceProfile.workspacePadding; 130 widthUsed = Math.max(padding.left + padding.right, 131 2 * (mInsets.left + mInsets.right)); 132 } 133 134 int heightUsed = mInsets.top + deviceProfile.edgeMarginPx; 135 measureChildWithMargins(mContent, widthMeasureSpec, 136 widthUsed, heightMeasureSpec, heightUsed); 137 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), 138 MeasureSpec.getSize(heightMeasureSpec)); 139 } 140 beginDraggingWidget(WidgetCell v)141 private boolean beginDraggingWidget(WidgetCell v) { 142 // Get the widget preview as the drag representation 143 WidgetImageView image = v.getWidgetView(); 144 145 // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and 146 // we abort the drag. 147 if (image.getDrawable() == null && v.getAppWidgetHostViewPreview() == null) { 148 return false; 149 } 150 151 PendingItemDragHelper dragHelper = new PendingItemDragHelper(v); 152 dragHelper.setRemoteViewsPreview(v.getRemoteViewsPreview()); 153 dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview()); 154 155 if (image.getDrawable() != null) { 156 int[] loc = new int[2]; 157 getPopupContainer().getLocationInDragLayer(image, loc); 158 159 dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(), 160 image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions()); 161 } else { 162 View preview = v.getAppWidgetHostViewPreview(); 163 int[] loc = new int[2]; 164 getPopupContainer().getLocationInDragLayer(preview, loc); 165 166 Rect r = new Rect(0, 0, preview.getWidth(), preview.getHeight()); 167 dragHelper.startDrag(r, preview.getMeasuredWidth(), preview.getMeasuredWidth(), 168 new Point(loc[0], loc[1]), this, new DragOptions()); 169 } 170 close(true); 171 return true; 172 } 173 174 // 175 // Drag related handling methods that implement {@link DragSource} interface. 176 // 177 178 @Override onDropCompleted(View target, DragObject d, boolean success)179 public void onDropCompleted(View target, DragObject d, boolean success) { } 180 181 onCloseComplete()182 protected void onCloseComplete() { 183 super.onCloseComplete(); 184 clearNavBarColor(); 185 } 186 clearNavBarColor()187 protected void clearNavBarColor() { 188 getSystemUiController().updateUiState( 189 SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0); 190 } 191 setupNavBarColor()192 protected void setupNavBarColor() { 193 boolean isSheetDark = Themes.getAttrBoolean(getContext(), R.attr.isMainColorDark); 194 getSystemUiController().updateUiState( 195 SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 196 isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV); 197 } 198 getSystemUiController()199 protected SystemUiController getSystemUiController() { 200 return mActivityContext.getSystemUiController(); 201 } 202 203 /** 204 * Show Widget tap toast prompting user to drag instead 205 */ showWidgetToast(Context context, Toast toast)206 public static Toast showWidgetToast(Context context, Toast toast) { 207 // Let the user know that they have to long press to add a widget 208 if (toast != null) { 209 toast.cancel(); 210 } 211 212 CharSequence msg = Utilities.wrapForTts( 213 context.getText(R.string.long_press_widget_to_add), 214 context.getString(R.string.long_accessible_way_to_add)); 215 toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT); 216 toast.show(); 217 return toast; 218 } 219 220 /** 221 * Show shortcut tap toast prompting user to drag instead. 222 */ showShortcutToast(Context context, Toast toast)223 private static Toast showShortcutToast(Context context, Toast toast) { 224 // Let the user know that they have to long press to add a widget 225 if (toast != null) { 226 toast.cancel(); 227 } 228 229 CharSequence msg = Utilities.wrapForTts( 230 context.getText(R.string.long_press_shortcut_to_add), 231 context.getString(R.string.long_accessible_way_to_add_shortcut)); 232 toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT); 233 toast.show(); 234 return toast; 235 } 236 237 /** Shows education tip on top center of {@code view} if view is laid out. */ 238 @Nullable showEducationTipOnViewIfPossible(@ullable View view)239 protected ArrowTipView showEducationTipOnViewIfPossible(@Nullable View view) { 240 if (view == null || !ViewCompat.isLaidOut(view)) { 241 return null; 242 } 243 int[] coords = new int[2]; 244 view.getLocationOnScreen(coords); 245 ArrowTipView arrowTipView = 246 new ArrowTipView(mActivityContext, /* isPointingUp= */ false).showAtLocation( 247 getContext().getString(R.string.long_press_widget_to_add), 248 /* arrowXCoord= */coords[0] + view.getWidth() / 2, 249 /* yCoord= */coords[1]); 250 if (arrowTipView != null) { 251 mActivityContext.getSharedPrefs().edit() 252 .putBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, true).apply(); 253 } 254 return arrowTipView; 255 } 256 257 /** Returns {@code true} if tip has previously been shown on any of {@link BaseWidgetSheet}. */ hasSeenEducationTip()258 protected boolean hasSeenEducationTip() { 259 return mActivityContext.getSharedPrefs().getBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, false) 260 || Utilities.IS_RUNNING_IN_TEST_HARNESS; 261 } 262 } 263