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 17 package com.android.launcher3.widget; 18 19 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_BOTTOM_WIDGETS_TRAY; 20 import static com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findAllWidgetsForPackageUser; 21 22 import android.content.Context; 23 import android.graphics.Rect; 24 import android.util.AttributeSet; 25 import android.util.Pair; 26 import android.view.Gravity; 27 import android.view.LayoutInflater; 28 import android.view.MotionEvent; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.ViewParent; 32 import android.view.animation.Interpolator; 33 import android.widget.ScrollView; 34 import android.widget.TableLayout; 35 import android.widget.TextView; 36 37 import androidx.annotation.Px; 38 39 import com.android.launcher3.R; 40 import com.android.launcher3.anim.PendingAnimation; 41 import com.android.launcher3.model.WidgetItem; 42 import com.android.launcher3.model.data.ItemInfo; 43 import com.android.launcher3.util.PackageUserKey; 44 import com.android.launcher3.widget.picker.model.data.WidgetPickerData; 45 import com.android.launcher3.widget.util.WidgetsTableUtils; 46 47 import java.util.List; 48 49 /** 50 * Bottom sheet for the "Widgets" system shortcut in the long-press popup. 51 */ 52 public class WidgetsBottomSheet extends BaseWidgetSheet { 53 private static final int DEFAULT_CLOSE_DURATION = 200; 54 55 private ItemInfo mOriginalItemInfo; 56 @Px private int mMaxHorizontalSpan; 57 WidgetsBottomSheet(Context context, AttributeSet attrs)58 public WidgetsBottomSheet(Context context, AttributeSet attrs) { 59 this(context, attrs, 0); 60 } 61 WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr)62 public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) { 63 super(context, attrs, defStyleAttr); 64 setWillNotDraw(false); 65 } 66 67 @Override onFinishInflate()68 protected void onFinishInflate() { 69 super.onFinishInflate(); 70 mContent = findViewById(R.id.widgets_bottom_sheet); 71 setContentBackgroundWithParent( 72 getContext().getDrawable(R.drawable.bg_rounded_corner_bottom_sheet), mContent); 73 } 74 75 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)76 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 77 doMeasure(widthMeasureSpec, heightMeasureSpec); 78 if (updateMaxSpansPerRow()) { 79 doMeasure(widthMeasureSpec, heightMeasureSpec); 80 } 81 } 82 83 /** Returns {@code true} if the max spans have been updated. */ updateMaxSpansPerRow()84 private boolean updateMaxSpansPerRow() { 85 if (getMeasuredWidth() == 0) return false; 86 87 @Px int maxHorizontalSpan = mContent.getMeasuredWidth() - (2 * mContentHorizontalMargin); 88 if (mMaxHorizontalSpan != maxHorizontalSpan) { 89 // Ensure the table layout is showing widgets in the right column after measure. 90 mMaxHorizontalSpan = maxHorizontalSpan; 91 onWidgetsBound(); 92 return true; 93 } 94 return false; 95 } 96 97 @Override onLayout(boolean changed, int l, int t, int r, int b)98 protected void onLayout(boolean changed, int l, int t, int r, int b) { 99 int width = r - l; 100 int height = b - t; 101 102 // Content is laid out as center bottom aligned. 103 int contentWidth = mContent.getMeasuredWidth(); 104 int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left; 105 mContent.layout(contentLeft, height - mContent.getMeasuredHeight(), 106 contentLeft + contentWidth, height); 107 108 setTranslationShift(mTranslationShift); 109 110 ScrollView widgetsTableScrollView = findViewById(R.id.widgets_table_scroll_view); 111 TableLayout widgetsTable = findViewById(R.id.widgets_table); 112 if (widgetsTable.getMeasuredHeight() > widgetsTableScrollView.getMeasuredHeight()) { 113 findViewById(R.id.collapse_handle).setVisibility(VISIBLE); 114 } 115 } 116 populateAndShow(ItemInfo itemInfo)117 public void populateAndShow(ItemInfo itemInfo) { 118 mOriginalItemInfo = itemInfo; 119 ((TextView) findViewById(R.id.title)).setText(mOriginalItemInfo.title); 120 121 onWidgetsBound(); 122 attachToContainer(); 123 mIsOpen = false; 124 animateOpen(); 125 } 126 127 @Override onWidgetsBound()128 public void onWidgetsBound() { 129 final WidgetPickerData data = mActivityContext.getWidgetPickerDataProvider().get(); 130 final PackageUserKey packageUserKey = PackageUserKey.fromItemInfo(mOriginalItemInfo); 131 List<WidgetItem> widgets = packageUserKey != null ? findAllWidgetsForPackageUser(data, 132 packageUserKey) : List.of(); 133 134 TableLayout widgetsTable = findViewById(R.id.widgets_table); 135 widgetsTable.removeAllViews(); 136 137 WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgets, mActivityContext, 138 mActivityContext.getDeviceProfile(), mMaxHorizontalSpan, 139 mWidgetCellHorizontalPadding) 140 .forEach(row -> { 141 WidgetTableRow tableRow = new WidgetTableRow(getContext()); 142 tableRow.setGravity(Gravity.TOP); 143 tableRow.setupRow(row.size(), /*resizeDelayMs=*/ 0); 144 row.forEach(widgetItem -> { 145 WidgetCell widget = addItemCell(tableRow); 146 widget.applyFromCellItem(widgetItem); 147 if (widget.matchesItem(getLastSelectedWidgetItem())) { 148 widget.callOnClick(); 149 } 150 }); 151 widgetsTable.addView(tableRow); 152 }); 153 } 154 155 @Override onControllerInterceptTouchEvent(MotionEvent ev)156 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 157 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 158 mNoIntercept = false; 159 ScrollView scrollView = findViewById(R.id.widgets_table_scroll_view); 160 if (getPopupContainer().isEventOverView(scrollView, ev) 161 && scrollView.getScrollY() > 0) { 162 mNoIntercept = true; 163 } 164 } 165 return super.onControllerInterceptTouchEvent(ev); 166 } 167 addItemCell(WidgetTableRow parent)168 protected WidgetCell addItemCell(WidgetTableRow parent) { 169 WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext()) 170 .inflate(R.layout.widget_cell, parent, false); 171 widget.addPreviewReadyListener(parent); 172 widget.setOnClickListener(this); 173 174 View previewContainer = widget.findViewById(R.id.widget_preview_container); 175 previewContainer.setOnClickListener(this); 176 previewContainer.setOnLongClickListener(this); 177 widget.setAnimatePreview(false); 178 widget.setSourceContainer(CONTAINER_BOTTOM_WIDGETS_TRAY); 179 180 parent.addView(widget); 181 return widget; 182 } 183 animateOpen()184 private void animateOpen() { 185 if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) { 186 return; 187 } 188 mIsOpen = true; 189 setupNavBarColor(); 190 setUpDefaultOpenAnimation().start(); 191 } 192 193 @Override handleClose(boolean animate)194 protected void handleClose(boolean animate) { 195 handleClose(animate, DEFAULT_CLOSE_DURATION); 196 } 197 198 @Override isOfType(@loatingViewType int type)199 protected boolean isOfType(@FloatingViewType int type) { 200 return (type & TYPE_WIDGETS_BOTTOM_SHEET) != 0; 201 } 202 203 @Override setInsets(Rect insets)204 public void setInsets(Rect insets) { 205 super.setInsets(insets); 206 int bottomPadding = Math.max(insets.bottom, mNavBarScrimHeight); 207 208 View widgetsTable = findViewById(R.id.widgets_table); 209 widgetsTable.setPadding( 210 widgetsTable.getPaddingLeft(), 211 widgetsTable.getPaddingTop(), 212 widgetsTable.getPaddingRight(), 213 bottomPadding); 214 if (bottomPadding > 0) { 215 setupNavBarColor(); 216 } else { 217 clearNavBarColor(); 218 } 219 } 220 221 @Override onContentHorizontalMarginChanged(int contentHorizontalMarginInPx)222 protected void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx) { 223 ViewGroup.MarginLayoutParams layoutParams = 224 ((ViewGroup.MarginLayoutParams) findViewById(R.id.widgets_table).getLayoutParams()); 225 layoutParams.setMarginStart(contentHorizontalMarginInPx); 226 layoutParams.setMarginEnd(contentHorizontalMarginInPx); 227 } 228 229 @Override getAccessibilityTarget()230 protected Pair<View, String> getAccessibilityTarget() { 231 return Pair.create(findViewById(R.id.title), getContext().getString( 232 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed)); 233 } 234 235 @Override addHintCloseAnim( float distanceToMove, Interpolator interpolator, PendingAnimation target)236 public void addHintCloseAnim( 237 float distanceToMove, Interpolator interpolator, PendingAnimation target) { 238 target.addAnimatedFloat(mSwipeToDismissProgress, 0f, 1f, interpolator); 239 } 240 241 @Override scrollCellContainerByY(WidgetCell wc, int scrollByY)242 protected void scrollCellContainerByY(WidgetCell wc, int scrollByY) { 243 for (ViewParent parent = wc.getParent(); parent != null; parent = parent.getParent()) { 244 if (parent instanceof ScrollView scrollView) { 245 scrollView.smoothScrollBy(0, scrollByY); 246 return; 247 } else if (parent == this) { 248 return; 249 } 250 } 251 } 252 253 @Override onRecommendedWidgetsBound()254 public void onRecommendedWidgetsBound() {} // no op 255 } 256