Snap for 7223915 from 5951856992 to sc-release
Change-Id: I42a53060b51caba8e23b76bc1ffee2b14fc0330c
This commit is contained in:
@@ -20,5 +20,5 @@
|
||||
<string name="niu_actions_package" translatable="false"/>
|
||||
|
||||
<!-- Feature Flags -->
|
||||
<bool name="enable_niu_actions">false</bool>
|
||||
<bool name="enable_niu_actions">true</bool>
|
||||
</resources>
|
||||
@@ -12,6 +12,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
// all of the 'license_kinds' from "packages_apps_Launcher3_license"
|
||||
// to get the below license kinds:
|
||||
// SPDX-license-identifier-Apache-2.0
|
||||
default_applicable_licenses: ["packages_apps_Launcher3_license"],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher3-quickstep-robolectric-src",
|
||||
path: "robolectric_tests",
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- The image of the widget. This view does not support padding. Any placement adjustment
|
||||
should be done using margins. -->
|
||||
should be done using margins.
|
||||
width & height are set at runtime after scaling the preview image. -->
|
||||
<com.android.launcher3.widget.WidgetImageView
|
||||
android:id="@+id/widget_preview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:importantForAccessibility="no"
|
||||
@@ -38,7 +39,7 @@
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="14sp" />
|
||||
android:textSize="@dimen/widget_cell_font_size" />
|
||||
|
||||
<!-- The original dimensions of the widget (can't be the same text as above due to different
|
||||
style. -->
|
||||
@@ -48,7 +49,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:textSize="14sp"
|
||||
android:textSize="@dimen/widget_cell_font_size"
|
||||
android:alpha="0.8" />
|
||||
|
||||
<TextView
|
||||
@@ -56,7 +57,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="14sp"
|
||||
android:textSize="@dimen/widget_cell_font_size"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
|
||||
@@ -18,12 +18,15 @@
|
||||
android:id="@+id/search_and_recommendations_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
<View
|
||||
android:id="@+id/collapse_handle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="2dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:elevation="2dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="?android:attr/textColorSecondary"/>
|
||||
<TextView
|
||||
@@ -36,4 +39,11 @@
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:text="@string/widget_button_text"/>
|
||||
<include layout="@layout/widgets_search_bar"/>
|
||||
|
||||
<com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
|
||||
android:id="@+id/recommended_widget_table"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/bg_widgets_searchbox">
|
||||
android:background="@drawable/bg_widgets_searchbox"
|
||||
android:elevation="2dp">
|
||||
|
||||
<com.android.launcher3.ExtendedEditText
|
||||
android:id="@+id/widgets_search_bar_edit_text"
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
<!-- Widget tray -->
|
||||
<dimen name="widget_cell_vertical_padding">8dp</dimen>
|
||||
<dimen name="widget_cell_horizontal_padding">16dp</dimen>
|
||||
<dimen name="widget_cell_font_size">14sp</dimen>
|
||||
|
||||
|
||||
<dimen name="widget_list_top_bottom_corner_radius">28dp</dimen>
|
||||
@@ -186,7 +187,6 @@
|
||||
<dimen name="deep_shortcuts_elevation">0dp</dimen>
|
||||
<dimen name="bg_popup_item_width">234dp</dimen>
|
||||
<dimen name="bg_popup_item_height">56dp</dimen>
|
||||
<dimen name="bg_popup_item_condensed_height">48dp</dimen>
|
||||
<dimen name="pre_drag_view_scale">6dp</dimen>
|
||||
<!-- an icon with shortcuts must be dragged this far before the container is removed. -->
|
||||
<dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
|
||||
|
||||
@@ -16,6 +16,15 @@
|
||||
// Launcher Robolectric test target.
|
||||
//
|
||||
// "robolectric_android-all-stub", not needed, we write our own stubs
|
||||
package {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
// all of the 'license_kinds' from "packages_apps_Launcher3_license"
|
||||
// to get the below license kinds:
|
||||
// SPDX-license-identifier-Apache-2.0
|
||||
default_applicable_licenses: ["packages_apps_Launcher3_license"],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher3-robolectric-resources",
|
||||
path: "resources",
|
||||
@@ -47,4 +56,3 @@ android_robolectric_test {
|
||||
timeout: 36000,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ public class ClippedFolderIconLayoutRule {
|
||||
|
||||
double thetaShift = 0;
|
||||
if (curNumItems == 3) {
|
||||
thetaShift = Math.PI / 6;
|
||||
thetaShift = Math.PI / 2;
|
||||
} else if (curNumItems == 4) {
|
||||
thetaShift = Math.PI / 4;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTI
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
|
||||
import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
@@ -695,6 +696,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
|
||||
|
||||
mPageIndicator.stopAllAnimations();
|
||||
startAnimation(anim);
|
||||
// Because t=0 has the folder match the folder icon, we can skip the
|
||||
// first frame and have the same movement one frame earlier.
|
||||
anim.setCurrentPlayTime(Math.min(getSingleFrameMs(getContext()), anim.getTotalDuration()));
|
||||
|
||||
// Make sure the folder picks up the last drag move even if the finger doesn't move.
|
||||
if (mDragController.isDragging()) {
|
||||
|
||||
@@ -92,6 +92,10 @@ public class NotificationItemView {
|
||||
});
|
||||
}
|
||||
|
||||
public void updateBackgroundColor(int color) {
|
||||
mMainView.updateBackgroundColor(color);
|
||||
}
|
||||
|
||||
public void addGutter() {
|
||||
if (mGutter == null) {
|
||||
mGutter = mPopupContainer.inflateAndAdd(R.layout.notification_gutter, mRootView);
|
||||
|
||||
@@ -97,15 +97,24 @@ public class NotificationMainView extends FrameLayout implements SingleAxisSwipe
|
||||
super.onFinishInflate();
|
||||
|
||||
mTextAndBackground = findViewById(R.id.text_and_background);
|
||||
ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
|
||||
mBackgroundColor = colorBackground.getColor();
|
||||
RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
|
||||
Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
|
||||
colorBackground, null);
|
||||
mTextAndBackground.setBackground(rippleBackground);
|
||||
mTitleView = mTextAndBackground.findViewById(R.id.title);
|
||||
mTextView = mTextAndBackground.findViewById(R.id.text);
|
||||
mIconView = findViewById(R.id.popup_item_icon);
|
||||
|
||||
ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
|
||||
updateBackgroundColor(colorBackground.getColor());
|
||||
}
|
||||
|
||||
public void updateBackgroundColor(int color) {
|
||||
mBackgroundColor = color;
|
||||
RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
|
||||
Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
|
||||
new ColorDrawable(color), null);
|
||||
mTextAndBackground.setBackground(rippleBackground);
|
||||
if (mNotificationInfo != null) {
|
||||
mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
|
||||
mBackgroundColor));
|
||||
}
|
||||
}
|
||||
|
||||
public void setSwipeDetector(SingleAxisSwipeDetector swipeDetector) {
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
package com.android.launcher3.popup;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
|
||||
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.animation.ValueAnimator;
|
||||
@@ -28,6 +30,8 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Pair;
|
||||
@@ -63,6 +67,9 @@ import java.util.Collections;
|
||||
*/
|
||||
public abstract class ArrowPopup<T extends BaseDraggingActivity> extends AbstractFloatingView {
|
||||
|
||||
// +1 for system shortcut view
|
||||
private static final int MAX_NUM_CHILDREN = MAX_SHORTCUTS + 1;
|
||||
|
||||
private final Rect mTempRect = new Rect();
|
||||
|
||||
protected final LayoutInflater mInflater;
|
||||
@@ -93,6 +100,9 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac
|
||||
|
||||
private Runnable mOnCloseCallback = () -> { };
|
||||
|
||||
private int mArrowColor;
|
||||
private final int[] mColors;
|
||||
|
||||
public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mInflater = LayoutInflater.from(context);
|
||||
@@ -121,15 +131,22 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac
|
||||
mArrowPointRadius = resources.getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
|
||||
|
||||
mRoundedTop = new GradientDrawable();
|
||||
mRoundedTop.setColor(Themes.getAttrColor(context, R.attr.popupColorPrimary));
|
||||
mRoundedTop.setCornerRadii(new float[] { mOutlineRadius, mOutlineRadius, mOutlineRadius,
|
||||
mOutlineRadius, 0, 0, 0, 0});
|
||||
|
||||
mRoundedBottom = new GradientDrawable();
|
||||
mRoundedBottom.setColor(Themes.getAttrColor(context, R.attr.popupColorPrimary));
|
||||
mRoundedBottom.setCornerRadii(new float[] { 0, 0, 0, 0, mOutlineRadius, mOutlineRadius,
|
||||
mOutlineRadius, mOutlineRadius});
|
||||
|
||||
int primaryColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
|
||||
int secondaryColor = Themes.getAttrColor(context, R.attr.popupColorSecondary);
|
||||
ArgbEvaluator argb = new ArgbEvaluator();
|
||||
mColors = new int[MAX_NUM_CHILDREN];
|
||||
// Interpolate between the two colors, exclusive.
|
||||
float step = 1f / (MAX_NUM_CHILDREN + 1);
|
||||
for (int i = 0; i < mColors.length; ++i) {
|
||||
mColors[i] = (int) argb.evaluate((i + 1) * step, primaryColor, secondaryColor);
|
||||
}
|
||||
}
|
||||
|
||||
public ArrowPopup(Context context, AttributeSet attrs) {
|
||||
@@ -187,6 +204,7 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac
|
||||
|
||||
int numVisibleShortcut = 0;
|
||||
View lastView = null;
|
||||
int numVisibleChild = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
View view = getChildAt(i);
|
||||
boolean isShortcut = view instanceof DeepShortcutView;
|
||||
@@ -207,15 +225,41 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac
|
||||
view.setBackground(mRoundedTop);
|
||||
} else if (numVisibleShortcut == (totalVisibleShortcuts - 1)) {
|
||||
view.setBackground(mRoundedBottom);
|
||||
} else {
|
||||
view.setBackgroundResource(R.drawable.middle_item_primary);
|
||||
}
|
||||
numVisibleShortcut++;
|
||||
}
|
||||
}
|
||||
|
||||
int color = mColors[numVisibleChild % mColors.length];
|
||||
setChildColor(view, color);
|
||||
|
||||
// Arrow color matches the first child or the last child.
|
||||
if (!mIsAboveIcon && numVisibleChild == 0) {
|
||||
mArrowColor = color;
|
||||
} else if (mIsAboveIcon) {
|
||||
mArrowColor = color;
|
||||
}
|
||||
|
||||
numVisibleChild++;
|
||||
}
|
||||
}
|
||||
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the background color of the child.
|
||||
*/
|
||||
protected void setChildColor(View view, int color) {
|
||||
Drawable bg = view.getBackground();
|
||||
if (bg instanceof GradientDrawable) {
|
||||
((GradientDrawable) bg.mutate()).setColor(color);
|
||||
} else if (bg instanceof ColorDrawable) {
|
||||
((ColorDrawable) bg.mutate()).setColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the popup at the desired location, optionally reversing the children.
|
||||
* @param viewsToFlip number of views from the top to to flip in case of reverse order
|
||||
@@ -293,7 +337,7 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac
|
||||
mOutlineRadius, getMeasuredWidth(), getMeasuredHeight(),
|
||||
mArrowOffsetHorizontal, -mArrowOffsetVertical,
|
||||
!mIsAboveIcon, mIsLeftAligned,
|
||||
Themes.getAttrColor(getContext(), R.attr.popupColorPrimary)));
|
||||
mArrowColor));
|
||||
mArrow.setElevation(getElevation());
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.notification.NotificationInfo;
|
||||
import com.android.launcher3.notification.NotificationItemView;
|
||||
import com.android.launcher3.notification.NotificationKeyData;
|
||||
import com.android.launcher3.notification.NotificationMainView;
|
||||
import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutView;
|
||||
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
|
||||
@@ -170,6 +171,14 @@ public class PopupContainerWithArrow<T extends BaseDraggingActivity> extends Arr
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setChildColor(View v, int color) {
|
||||
super.setChildColor(v, color);
|
||||
if (v.getId() == R.id.notification_container && mNotificationItemView != null) {
|
||||
mNotificationItemView.updateBackgroundColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we can show the container.
|
||||
*/
|
||||
@@ -333,19 +342,11 @@ public class PopupContainerWithArrow<T extends BaseDraggingActivity> extends Arr
|
||||
private void updateHiddenShortcuts() {
|
||||
int allowedCount = mNotificationItemView != null
|
||||
? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;
|
||||
int originalHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
|
||||
int itemHeight = mNotificationItemView != null ?
|
||||
getResources().getDimensionPixelSize(R.dimen.bg_popup_item_condensed_height)
|
||||
: originalHeight;
|
||||
float iconScale = ((float) itemHeight) / originalHeight;
|
||||
|
||||
int total = mShortcuts.size();
|
||||
for (int i = 0; i < total; i++) {
|
||||
DeepShortcutView view = mShortcuts.get(i);
|
||||
view.setVisibility(i >= allowedCount ? GONE : VISIBLE);
|
||||
view.getLayoutParams().height = itemHeight;
|
||||
view.getIconView().setScaleX(iconScale);
|
||||
view.getIconView().setScaleY(iconScale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,6 +568,7 @@ public class PopupContainerWithArrow<T extends BaseDraggingActivity> extends Arr
|
||||
// No more notifications, remove the notification views and expand all shortcuts.
|
||||
mNotificationItemView.removeAllViews();
|
||||
mNotificationItemView = null;
|
||||
mNotificationContainer.setVisibility(GONE);
|
||||
updateHiddenShortcuts();
|
||||
assignMarginsAndBackgrounds();
|
||||
} else {
|
||||
|
||||
@@ -74,6 +74,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
|
||||
protected int mPreviewHeight;
|
||||
protected int mPresetPreviewSize;
|
||||
private int mCellSize;
|
||||
private float mPreviewScale = 1f;
|
||||
|
||||
private WidgetImageView mWidgetImage;
|
||||
private TextView mWidgetName;
|
||||
@@ -254,8 +255,8 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
|
||||
}
|
||||
if (drawable != null) {
|
||||
LayoutParams layoutParams = (LayoutParams) mWidgetImage.getLayoutParams();
|
||||
layoutParams.width = drawable.getIntrinsicWidth();
|
||||
layoutParams.height = drawable.getIntrinsicHeight();
|
||||
layoutParams.width = (int) (drawable.getIntrinsicWidth() * mPreviewScale);
|
||||
layoutParams.height = (int) (drawable.getIntrinsicHeight() * mPreviewScale);
|
||||
mWidgetImage.setLayoutParams(layoutParams);
|
||||
|
||||
mWidgetImage.setDrawable(drawable, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
|
||||
@@ -305,10 +306,16 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
|
||||
|
||||
/** Sets the widget preview image size in number of cells. */
|
||||
public void setPreviewSize(int spanX, int spanY) {
|
||||
setPreviewSize(spanX, spanY, 1f);
|
||||
}
|
||||
|
||||
/** Sets the widget preview image size, in number of cells, and preview scale. */
|
||||
public void setPreviewSize(int spanX, int spanY, float previewScale) {
|
||||
int padding = 2 * getResources()
|
||||
.getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
|
||||
mPreviewWidth = mDeviceProfile.cellWidthPx * spanX + padding;
|
||||
mPreviewHeight = mDeviceProfile.cellHeightPx * spanY + padding;
|
||||
mPreviewScale = previewScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+110
-49
@@ -35,6 +35,7 @@ final class SearchAndRecommendationsScrollController implements
|
||||
private final SearchAndRecommendationViewHolder mViewHolder;
|
||||
private final WidgetsRecyclerView mPrimaryRecyclerView;
|
||||
private final WidgetsRecyclerView mSearchRecyclerView;
|
||||
private final int mTabsHeight;
|
||||
|
||||
// The following are only non null if mHasWorkProfile is true.
|
||||
@Nullable private final WidgetsRecyclerView mWorkRecyclerView;
|
||||
@@ -42,10 +43,28 @@ final class SearchAndRecommendationsScrollController implements
|
||||
@Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;
|
||||
|
||||
private WidgetsRecyclerView mCurrentRecyclerView;
|
||||
private int mMaxCollapsibleHeight = 0;
|
||||
|
||||
/**
|
||||
* The vertical distance, in pixels, until the search is pinned at the top of the screen when
|
||||
* the user scrolls down the recycler view.
|
||||
*/
|
||||
private int mCollapsibleHeightForSearch = 0;
|
||||
/**
|
||||
* The vertical distance, in pixels, until the recommendation table disappears from the top of
|
||||
* the screen when the user scrolls down the recycler view.
|
||||
*/
|
||||
private int mCollapsibleHeightForRecommendation = 0;
|
||||
/**
|
||||
* The vertical distance, in pixels, until the tabs is pinned at the top of the screen when the
|
||||
* user scrolls down the recycler view.
|
||||
*
|
||||
* <p>Always 0 if there is no work profile.
|
||||
*/
|
||||
private int mCollapsibleHeightForTabs = 0;
|
||||
|
||||
SearchAndRecommendationsScrollController(
|
||||
boolean hasWorkProfile,
|
||||
int tabsHeight,
|
||||
SearchAndRecommendationViewHolder viewHolder,
|
||||
WidgetsRecyclerView primaryRecyclerView,
|
||||
@Nullable WidgetsRecyclerView workRecyclerView,
|
||||
@@ -55,46 +74,63 @@ final class SearchAndRecommendationsScrollController implements
|
||||
mHasWorkProfile = hasWorkProfile;
|
||||
mViewHolder = viewHolder;
|
||||
mPrimaryRecyclerView = primaryRecyclerView;
|
||||
mCurrentRecyclerView = mPrimaryRecyclerView;
|
||||
mWorkRecyclerView = workRecyclerView;
|
||||
mSearchRecyclerView = searchRecyclerView;
|
||||
mPrimaryWorkTabsView = personalWorkTabsView;
|
||||
mPrimaryWorkViewPager = primaryWorkViewPager;
|
||||
mCurrentRecyclerView = mPrimaryRecyclerView;
|
||||
mTabsHeight = tabsHeight;
|
||||
}
|
||||
|
||||
/** Sets the current active {@link WidgetsRecyclerView}. */
|
||||
public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
|
||||
mCurrentRecyclerView = currentRecyclerView;
|
||||
mCurrentRecyclerView = currentRecyclerView;
|
||||
mViewHolder.mHeaderTitle.setTranslationY(0);
|
||||
mViewHolder.mRecommendedWidgetsTable.setTranslationY(0);
|
||||
mViewHolder.mSearchBar.setTranslationY(0);
|
||||
|
||||
if (mHasWorkProfile) {
|
||||
mPrimaryWorkTabsView.setTranslationY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views.
|
||||
*
|
||||
* @return {@code true} if margins or/and padding of views in the search and recommendations
|
||||
* container have been updated.
|
||||
*/
|
||||
public void updateMarginAndPadding() {
|
||||
// The maximum vertical distance, in pixels, until the last collapsible element is not
|
||||
// visible from the screen when the user scrolls down the recycler view.
|
||||
mMaxCollapsibleHeight = mViewHolder.mContainer.getPaddingTop()
|
||||
+ measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
|
||||
+ measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
|
||||
public boolean updateMarginAndPadding() {
|
||||
boolean hasMarginOrPaddingUpdated = false;
|
||||
mCollapsibleHeightForSearch = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
|
||||
mCollapsibleHeightForRecommendation =
|
||||
measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
|
||||
+ measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
|
||||
+ measureHeightWithVerticalMargins((View) mViewHolder.mSearchBar)
|
||||
+ measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
|
||||
|
||||
int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer);
|
||||
if (mHasWorkProfile) {
|
||||
mCollapsibleHeightForTabs = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
|
||||
+ measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
|
||||
// In a work profile setup, the full widget sheet contains the following views:
|
||||
// ------- -|
|
||||
// Widgets -|---> LinearLayout for search & recommendations
|
||||
// Search bar -|
|
||||
// Personal | Work
|
||||
// ------- (pinned) -|
|
||||
// Widgets (collapsible) -|---> LinearLayout for search & recommendations
|
||||
// Search bar (pinned) -|
|
||||
// Widgets recommendation (collapsible)-|
|
||||
// Personal | Work (pinned)
|
||||
// View Pager
|
||||
//
|
||||
// Views after the search & recommendations are not bound by RelativelyLayout param.
|
||||
// To position them on the expected location, padding & margin are added to these views
|
||||
|
||||
// Tabs should have a padding of the height of the search & recommendations container.
|
||||
mPrimaryWorkTabsView.setPadding(
|
||||
mPrimaryWorkTabsView.getPaddingLeft(),
|
||||
topContainerHeight,
|
||||
mPrimaryWorkTabsView.getPaddingRight(),
|
||||
mPrimaryWorkTabsView.getPaddingBottom());
|
||||
RelativeLayout.LayoutParams tabsLayoutParams =
|
||||
(RelativeLayout.LayoutParams) mPrimaryWorkTabsView.getLayoutParams();
|
||||
tabsLayoutParams.topMargin = topContainerHeight;
|
||||
mPrimaryWorkTabsView.setLayoutParams(tabsLayoutParams);
|
||||
|
||||
// Instead of setting the top offset directly, we split the top offset into two values:
|
||||
// 1. topOffsetAfterAllViewsCollapsed: this is the top offset after all collapsible
|
||||
@@ -124,39 +160,52 @@ final class SearchAndRecommendationsScrollController implements
|
||||
//
|
||||
// When the views are first inflated, the sum of topOffsetAfterAllViewsCollapsed and
|
||||
// mMaxCollapsibleDistance should equal to the top container height.
|
||||
int tabsViewActualHeight = measureHeightWithVerticalMargins(mPrimaryWorkTabsView)
|
||||
- mPrimaryWorkTabsView.getPaddingTop();
|
||||
int topOffsetAfterAllViewsCollapsed =
|
||||
topContainerHeight + tabsViewActualHeight - mMaxCollapsibleHeight;
|
||||
topContainerHeight + mTabsHeight - mCollapsibleHeightForTabs;
|
||||
|
||||
RelativeLayout.LayoutParams layoutParams =
|
||||
RelativeLayout.LayoutParams viewPagerLayoutParams =
|
||||
(RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams();
|
||||
layoutParams.setMargins(0, topOffsetAfterAllViewsCollapsed, 0, 0);
|
||||
mPrimaryWorkViewPager.setLayoutParams(layoutParams);
|
||||
mPrimaryWorkViewPager.requestLayout();
|
||||
if (viewPagerLayoutParams.topMargin != topOffsetAfterAllViewsCollapsed) {
|
||||
viewPagerLayoutParams.topMargin = topOffsetAfterAllViewsCollapsed;
|
||||
mPrimaryWorkViewPager.setLayoutParams(viewPagerLayoutParams);
|
||||
hasMarginOrPaddingUpdated = true;
|
||||
}
|
||||
|
||||
mPrimaryRecyclerView.setPadding(
|
||||
mPrimaryRecyclerView.getPaddingLeft(),
|
||||
mMaxCollapsibleHeight,
|
||||
mPrimaryRecyclerView.getPaddingRight(),
|
||||
mPrimaryRecyclerView.getPaddingBottom());
|
||||
mWorkRecyclerView.setPadding(
|
||||
mWorkRecyclerView.getPaddingLeft(),
|
||||
mMaxCollapsibleHeight,
|
||||
mWorkRecyclerView.getPaddingRight(),
|
||||
mWorkRecyclerView.getPaddingBottom());
|
||||
if (mPrimaryRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
|
||||
mPrimaryRecyclerView.setPadding(
|
||||
mPrimaryRecyclerView.getPaddingLeft(),
|
||||
mCollapsibleHeightForTabs,
|
||||
mPrimaryRecyclerView.getPaddingRight(),
|
||||
mPrimaryRecyclerView.getPaddingBottom());
|
||||
hasMarginOrPaddingUpdated = true;
|
||||
}
|
||||
if (mWorkRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
|
||||
mWorkRecyclerView.setPadding(
|
||||
mWorkRecyclerView.getPaddingLeft(),
|
||||
mCollapsibleHeightForTabs,
|
||||
mWorkRecyclerView.getPaddingRight(),
|
||||
mWorkRecyclerView.getPaddingBottom());
|
||||
hasMarginOrPaddingUpdated = true;
|
||||
}
|
||||
} else {
|
||||
mPrimaryRecyclerView.setPadding(
|
||||
mPrimaryRecyclerView.getPaddingLeft(),
|
||||
topContainerHeight,
|
||||
mPrimaryRecyclerView.getPaddingRight(),
|
||||
mPrimaryRecyclerView.getPaddingBottom());
|
||||
if (mPrimaryRecyclerView.getPaddingTop() != topContainerHeight) {
|
||||
mPrimaryRecyclerView.setPadding(
|
||||
mPrimaryRecyclerView.getPaddingLeft(),
|
||||
topContainerHeight,
|
||||
mPrimaryRecyclerView.getPaddingRight(),
|
||||
mPrimaryRecyclerView.getPaddingBottom());
|
||||
hasMarginOrPaddingUpdated = true;
|
||||
}
|
||||
}
|
||||
mSearchRecyclerView.setPadding(
|
||||
mSearchRecyclerView.getPaddingLeft(),
|
||||
topContainerHeight,
|
||||
mSearchRecyclerView.getPaddingRight(),
|
||||
mSearchRecyclerView.getPaddingBottom());
|
||||
if (mSearchRecyclerView.getPaddingTop() != topContainerHeight) {
|
||||
mSearchRecyclerView.setPadding(
|
||||
mSearchRecyclerView.getPaddingLeft(),
|
||||
topContainerHeight,
|
||||
mSearchRecyclerView.getPaddingRight(),
|
||||
mSearchRecyclerView.getPaddingBottom());
|
||||
hasMarginOrPaddingUpdated = true;
|
||||
}
|
||||
return hasMarginOrPaddingUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,13 +217,22 @@ final class SearchAndRecommendationsScrollController implements
|
||||
// Always use the recycler view offset because fast scroller offset has a different scale.
|
||||
int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
|
||||
if (recyclerViewYOffset < 0) return;
|
||||
if (mMaxCollapsibleHeight > 0) {
|
||||
int yDisplacement = Math.max(-recyclerViewYOffset, -mMaxCollapsibleHeight);
|
||||
|
||||
if (mCollapsibleHeightForRecommendation > 0) {
|
||||
int yDisplacement = Math.max(-recyclerViewYOffset,
|
||||
-mCollapsibleHeightForRecommendation);
|
||||
mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
|
||||
mViewHolder.mSearchBar.setTranslationY(yDisplacement);
|
||||
if (mHasWorkProfile) {
|
||||
mPrimaryWorkTabsView.setTranslationY(yDisplacement);
|
||||
}
|
||||
mViewHolder.mRecommendedWidgetsTable.setTranslationY(yDisplacement);
|
||||
}
|
||||
|
||||
if (mCollapsibleHeightForSearch > 0) {
|
||||
int searchYDisplacement = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForSearch);
|
||||
mViewHolder.mSearchBar.setTranslationY(searchYDisplacement);
|
||||
}
|
||||
|
||||
if (mHasWorkProfile && mCollapsibleHeightForTabs > 0) {
|
||||
int yDisplacementForTabs = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForTabs);
|
||||
mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +247,9 @@ final class SearchAndRecommendationsScrollController implements
|
||||
|
||||
/** private the height, in pixel, + the vertical margins of a given view. */
|
||||
private static int measureHeightWithVerticalMargins(View view) {
|
||||
if (view.getVisibility() != View.VISIBLE) {
|
||||
return 0;
|
||||
}
|
||||
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
|
||||
return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
|
||||
+ marginLayoutParams.topMargin;
|
||||
|
||||
@@ -33,6 +33,7 @@ import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.TextView;
|
||||
@@ -48,6 +49,7 @@ import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.views.RecyclerViewFastScroller;
|
||||
import com.android.launcher3.views.TopRoundedCornerView;
|
||||
import com.android.launcher3.widget.BaseWidgetSheet;
|
||||
@@ -55,6 +57,7 @@ import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListene
|
||||
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
|
||||
import com.android.launcher3.widget.picker.search.SearchModeListener;
|
||||
import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
|
||||
import com.android.launcher3.widget.util.WidgetsTableUtils;
|
||||
import com.android.launcher3.workprofile.PersonalWorkPagedView;
|
||||
import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
|
||||
|
||||
@@ -68,10 +71,15 @@ import java.util.function.Predicate;
|
||||
public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
|
||||
WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
|
||||
private static final String TAG = WidgetsFullSheet.class.getSimpleName();
|
||||
|
||||
private static final long DEFAULT_OPEN_DURATION = 267;
|
||||
private static final long FADE_IN_DURATION = 150;
|
||||
private static final float VERTICAL_START_POSITION = 0.3f;
|
||||
// The widget recommendation table can easily take over the entire screen on devices with small
|
||||
// resolution or landscape on phone. This ratio defines the max percentage of content area that
|
||||
// the table can display.
|
||||
private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
|
||||
|
||||
private final Rect mInsets = new Rect();
|
||||
private final boolean mHasWorkProfile;
|
||||
@@ -81,10 +89,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mCurrentUser.equals(entry.mPkgItem.user);
|
||||
private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
|
||||
mPrimaryWidgetsFilter.negate();
|
||||
private final int mTabsHeight;
|
||||
private final int mWidgetCellHorizontalPadding;
|
||||
|
||||
@Nullable private PersonalWorkPagedView mViewPager;
|
||||
private int mInitialTabsHeight = 0;
|
||||
private boolean mIsInSearchMode;
|
||||
private int mMaxSpansPerRow = 4;
|
||||
private View mTabsView;
|
||||
private TextView mNoWidgetsView;
|
||||
private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
|
||||
@@ -96,6 +106,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
|
||||
mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
|
||||
mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
|
||||
mTabsHeight = mHasWorkProfile
|
||||
? getContext().getResources()
|
||||
.getDimensionPixelSize(R.dimen.all_apps_header_tab_height)
|
||||
: 0;
|
||||
mWidgetCellHorizontalPadding = 2 * getResources().getDimensionPixelOffset(
|
||||
R.dimen.widget_cell_horizontal_padding);
|
||||
}
|
||||
|
||||
public WidgetsFullSheet(Context context, AttributeSet attrs) {
|
||||
@@ -140,6 +156,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
findViewById(R.id.search_and_recommendations_container));
|
||||
mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
|
||||
mHasWorkProfile,
|
||||
mTabsHeight,
|
||||
mSearchAndRecommendationViewHolder,
|
||||
findViewById(R.id.primary_widgets_list_view),
|
||||
mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
|
||||
@@ -150,6 +167,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
mNoWidgetsView = findViewById(R.id.no_widgets_text);
|
||||
|
||||
onRecommendedWidgetsBound();
|
||||
onWidgetsBound();
|
||||
|
||||
mSearchAndRecommendationViewHolder.mSearchBar.initialize(
|
||||
@@ -257,6 +275,22 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
doMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
|
||||
doMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
if (updateMaxSpansPerRow()) {
|
||||
doMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
|
||||
doMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
|
||||
int widthUsed;
|
||||
if (mInsets.bottom > 0) {
|
||||
@@ -272,24 +306,29 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
widthUsed, heightMeasureSpec, heightUsed);
|
||||
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
|
||||
MeasureSpec.getSize(heightMeasureSpec));
|
||||
}
|
||||
|
||||
int paddingPx = 2 * getResources().getDimensionPixelOffset(
|
||||
R.dimen.widget_cell_horizontal_padding);
|
||||
int maxSpansPerRow = getMeasuredWidth() / (deviceProfile.cellWidthPx + paddingPx);
|
||||
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
|
||||
maxSpansPerRow);
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
|
||||
maxSpansPerRow);
|
||||
if (mHasWorkProfile) {
|
||||
mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
|
||||
maxSpansPerRow);
|
||||
/** Returns {@code true} if the max spans have been updated. */
|
||||
private boolean updateMaxSpansPerRow() {
|
||||
if (getMeasuredWidth() == 0) return false;
|
||||
|
||||
int previousMaxSpansPerRow = mMaxSpansPerRow;
|
||||
mMaxSpansPerRow = getMeasuredWidth()
|
||||
/ (mLauncher.getDeviceProfile().cellWidthPx + mWidgetCellHorizontalPadding);
|
||||
|
||||
if (previousMaxSpansPerRow != mMaxSpansPerRow) {
|
||||
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
|
||||
mMaxSpansPerRow);
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
|
||||
mMaxSpansPerRow);
|
||||
if (mHasWorkProfile) {
|
||||
mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
|
||||
mMaxSpansPerRow);
|
||||
}
|
||||
onRecommendedWidgetsBound();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mInitialTabsHeight == 0 && mTabsView != null) {
|
||||
mInitialTabsHeight = measureHeightWithVerticalMargins(mTabsView);
|
||||
}
|
||||
|
||||
mSearchAndRecommendationsScrollController.updateMarginAndPadding();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -346,6 +385,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mViewPager.snapToPage(AdapterHolder.PRIMARY);
|
||||
}
|
||||
attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
|
||||
|
||||
mSearchAndRecommendationsScrollController.updateMarginAndPadding();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -357,6 +398,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
|
||||
mIsInSearchMode = isInSearchMode;
|
||||
mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
|
||||
.setVisibility(isInSearchMode ? GONE : VISIBLE);
|
||||
if (mHasWorkProfile) {
|
||||
mViewPager.setVisibility(isInSearchMode ? GONE : VISIBLE);
|
||||
mTabsView.setVisibility(isInSearchMode ? GONE : VISIBLE);
|
||||
@@ -374,6 +417,25 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecommendedWidgetsBound() {
|
||||
List<WidgetItem> recommendedWidgets =
|
||||
mLauncher.getPopupDataProvider().getRecommendedWidgets();
|
||||
WidgetsRecommendationTableLayout table =
|
||||
mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
|
||||
if (recommendedWidgets.size() > 0) {
|
||||
float maxTableHeight =
|
||||
(mLauncher.getDeviceProfile().heightPx - mTabsHeight - getHeaderViewHeight())
|
||||
* RECOMMENDATION_TABLE_HEIGHT_RATIO;
|
||||
List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
|
||||
WidgetsTableUtils.groupWidgetItemsIntoTable(recommendedWidgets,
|
||||
mMaxSpansPerRow);
|
||||
table.setRecommendedWidgets(recommendedWidgetsInTable, maxTableHeight);
|
||||
} else {
|
||||
table.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void open(boolean animate) {
|
||||
if (animate) {
|
||||
if (getPopupContainer().getInsets().bottom > 0) {
|
||||
@@ -534,20 +596,29 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mWidgetsRecyclerView.setEdgeEffectFactory(
|
||||
((TopRoundedCornerView) mContent).createEdgeEffectFactory());
|
||||
mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
|
||||
mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
|
||||
}
|
||||
}
|
||||
|
||||
final class SearchAndRecommendationViewHolder {
|
||||
final View mContainer;
|
||||
final ViewGroup mContainer;
|
||||
final View mCollapseHandle;
|
||||
final WidgetsSearchBar mSearchBar;
|
||||
final TextView mHeaderTitle;
|
||||
final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
|
||||
|
||||
SearchAndRecommendationViewHolder(View searchAndRecommendationContainer) {
|
||||
SearchAndRecommendationViewHolder(ViewGroup searchAndRecommendationContainer) {
|
||||
mContainer = searchAndRecommendationContainer;
|
||||
mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
|
||||
mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
|
||||
mHeaderTitle = mContainer.findViewById(R.id.title);
|
||||
mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
|
||||
mRecommendedWidgetsTable.setWidgetCellOnTouchListener((view, event) -> {
|
||||
getRecyclerView().onTouchEvent(event);
|
||||
return false;
|
||||
});
|
||||
mRecommendedWidgetsTable.setWidgetCellLongClickListener(WidgetsFullSheet.this);
|
||||
mRecommendedWidgetsTable.setWidgetCellOnClickListener(WidgetsFullSheet.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.widget.picker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.widget.WidgetCell;
|
||||
import com.android.launcher3.widget.WidgetImageView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** A {@link TableLayout} for showing recommended widgets. */
|
||||
public final class WidgetsRecommendationTableLayout extends TableLayout {
|
||||
private static final float SCALE_DOWN_RATIO = 0.9f;
|
||||
private final DeviceProfile mDeviceProfile;
|
||||
private final float mWidgetCellTextViewsHeight;
|
||||
|
||||
private float mRecommendationTableMaxHeight = Float.MAX_VALUE;
|
||||
@Nullable private OnLongClickListener mWidgetCellOnLongClickListener;
|
||||
@Nullable private OnClickListener mWidgetCellOnClickListener;
|
||||
@Nullable private OnTouchListener mWidgetCellOnTouchListener;
|
||||
|
||||
public WidgetsRecommendationTableLayout(Context context) {
|
||||
this(context, /* attrs= */ null);
|
||||
}
|
||||
|
||||
public WidgetsRecommendationTableLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
|
||||
// There are 1 row for title, 1 row for dimension and 2 rows for description.
|
||||
mWidgetCellTextViewsHeight = 4 * getResources().getDimension(R.dimen.widget_cell_font_size);
|
||||
}
|
||||
|
||||
/** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
|
||||
public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) {
|
||||
mWidgetCellOnLongClickListener = onLongClickListener;
|
||||
}
|
||||
|
||||
/** Sets a {@link android.view.View.OnClickListener} for all widget cells in this table. */
|
||||
public void setWidgetCellOnClickListener(OnClickListener widgetCellOnClickListener) {
|
||||
mWidgetCellOnClickListener = widgetCellOnClickListener;
|
||||
}
|
||||
|
||||
/** Sets a {@link android.view.View.OnTouchListener} for all widget cells in this table. */
|
||||
public void setWidgetCellOnTouchListener(OnTouchListener widgetCellOnTouchListener) {
|
||||
mWidgetCellOnTouchListener = widgetCellOnTouchListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a list of recommended widgets that would like to be displayed in this table within the
|
||||
* desired {@code recommendationTableMaxHeight}.
|
||||
*
|
||||
* <p>If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a
|
||||
* last row from the {@code recommendedWidgets} until it fits or only one row left. If the only
|
||||
* row still doesn't fit, we scale down the preview image.
|
||||
*/
|
||||
public void setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
|
||||
float recommendationTableMaxHeight) {
|
||||
mRecommendationTableMaxHeight = recommendationTableMaxHeight;
|
||||
RecommendationTableData data = fitRecommendedWidgetsToTableSpace(/* previewScale= */ 1f,
|
||||
recommendedWidgets);
|
||||
bindData(data);
|
||||
}
|
||||
|
||||
private void bindData(RecommendationTableData data) {
|
||||
if (data.mRecommendationTable.size() == 0) {
|
||||
setVisibility(GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
removeAllViews();
|
||||
|
||||
for (int i = 0; i < data.mRecommendationTable.size(); i++) {
|
||||
List<WidgetItem> widgetItems = data.mRecommendationTable.get(i);
|
||||
TableRow tableRow = new TableRow(getContext());
|
||||
tableRow.setGravity(Gravity.TOP);
|
||||
|
||||
for (WidgetItem widgetItem : widgetItems) {
|
||||
WidgetCell widgetCell = addItemCell(tableRow);
|
||||
widgetCell.setPreviewSize(widgetItem.spanX, widgetItem.spanY, data.mPreviewScale);
|
||||
widgetCell.applyFromCellItem(widgetItem,
|
||||
LauncherAppState.getInstance(getContext()).getWidgetCache());
|
||||
widgetCell.ensurePreview();
|
||||
}
|
||||
addView(tableRow);
|
||||
}
|
||||
setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private WidgetCell addItemCell(ViewGroup parent) {
|
||||
WidgetCell widget = (WidgetCell) LayoutInflater.from(
|
||||
getContext()).inflate(R.layout.widget_cell, parent, false);
|
||||
|
||||
widget.setOnTouchListener(mWidgetCellOnTouchListener);
|
||||
WidgetImageView preview = widget.findViewById(R.id.widget_preview);
|
||||
preview.setOnClickListener(mWidgetCellOnClickListener);
|
||||
preview.setOnLongClickListener(mWidgetCellOnLongClickListener);
|
||||
widget.setAnimatePreview(false);
|
||||
|
||||
parent.addView(widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
private RecommendationTableData fitRecommendedWidgetsToTableSpace(
|
||||
float previewScale,
|
||||
List<ArrayList<WidgetItem>> recommendedWidgetsInTable) {
|
||||
// A naive estimation of the widgets recommendation table height without inflation.
|
||||
float totalHeight = 0;
|
||||
for (int i = 0; i < recommendedWidgetsInTable.size(); i++) {
|
||||
List<WidgetItem> widgetItems = recommendedWidgetsInTable.get(i);
|
||||
float rowHeight = 0;
|
||||
for (int j = 0; j < widgetItems.size(); j++) {
|
||||
float previewHeight = widgetItems.get(j).spanY * mDeviceProfile.allAppsCellHeightPx
|
||||
* previewScale;
|
||||
rowHeight = Math.max(rowHeight, previewHeight + mWidgetCellTextViewsHeight);
|
||||
}
|
||||
totalHeight += rowHeight;
|
||||
}
|
||||
|
||||
if (totalHeight < mRecommendationTableMaxHeight) {
|
||||
return new RecommendationTableData(recommendedWidgetsInTable, previewScale);
|
||||
}
|
||||
|
||||
if (recommendedWidgetsInTable.size() > 1) {
|
||||
// We don't want to scale down widgets preview unless we really need to. Reduce the
|
||||
// num of row by 1 to see if it fits.
|
||||
return fitRecommendedWidgetsToTableSpace(
|
||||
previewScale,
|
||||
recommendedWidgetsInTable.subList(/* fromIndex= */0,
|
||||
/* toIndex= */recommendedWidgetsInTable.size() - 1));
|
||||
}
|
||||
|
||||
float nextPreviewScale = previewScale * SCALE_DOWN_RATIO;
|
||||
return fitRecommendedWidgetsToTableSpace(nextPreviewScale, recommendedWidgetsInTable);
|
||||
}
|
||||
|
||||
/** Data class for the widgets recommendation table and widgets preview scaling. */
|
||||
private class RecommendationTableData {
|
||||
private final List<ArrayList<WidgetItem>> mRecommendationTable;
|
||||
private final float mPreviewScale;
|
||||
|
||||
RecommendationTableData(List<ArrayList<WidgetItem>> recommendationTable,
|
||||
float previewScale) {
|
||||
mRecommendationTable = recommendationTable;
|
||||
mPreviewScale = previewScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,15 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
// all of the 'license_kinds' from "packages_apps_Launcher3_license"
|
||||
// to get the below license kinds:
|
||||
// SPDX-license-identifier-Apache-2.0
|
||||
default_applicable_licenses: ["packages_apps_Launcher3_license"],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher3-test-src-common",
|
||||
srcs: ["src_common/**/*.java"],
|
||||
|
||||
@@ -1214,7 +1214,7 @@ public final class LauncherInstrumentation {
|
||||
|
||||
final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
|
||||
assertTrue("injectInputEvent failed",
|
||||
mInstrumentation.getUiAutomation().injectInputEvent(event, true));
|
||||
mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
|
||||
event.recycle();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ import androidx.test.uiautomator.Until;
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* All widgets container.
|
||||
@@ -106,7 +105,8 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
|
||||
fullWidgetsPicker.wait(Until.scrollable(true), WAIT_TIME_MS));
|
||||
final Point displaySize = mLauncher.getRealDisplaySize();
|
||||
|
||||
final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer();
|
||||
Rect headerRect = new Rect();
|
||||
final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer(headerRect);
|
||||
mLauncher.assertTrue("Can't locate widgets list for the test app: "
|
||||
+ mLauncher.getLauncherPackageName(),
|
||||
widgetsContainer != null);
|
||||
@@ -132,7 +132,12 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
|
||||
|
||||
mLauncher.assertTrue("Too many attempts", ++i <= 40);
|
||||
final int scroll = getWidgetsScroll();
|
||||
mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, tableRows, 0);
|
||||
mLauncher.scroll(
|
||||
fullWidgetsPicker,
|
||||
Direction.DOWN,
|
||||
headerRect,
|
||||
10,
|
||||
true);
|
||||
final int newScroll = getWidgetsScroll();
|
||||
mLauncher.assertTrue(
|
||||
"Scrolled in a wrong direction in Widgets: from " + scroll + " to "
|
||||
@@ -143,7 +148,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
|
||||
}
|
||||
|
||||
/** Finds the widgets list of this test app from the collapsed full widgets picker. */
|
||||
private UiObject2 findTestAppWidgetsTableContainer() {
|
||||
private UiObject2 findTestAppWidgetsTableContainer(Rect outHeaderRect) {
|
||||
final BySelector headerSelector = By.res(mLauncher.getLauncherPackageName(),
|
||||
"widgets_list_header");
|
||||
final BySelector targetAppSelector = By.clazz("android.widget.TextView").text(
|
||||
@@ -156,6 +161,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
|
||||
UiObject2 fullWidgetsPicker = verifyActiveContainer();
|
||||
|
||||
UiObject2 header = fullWidgetsPicker.findObject(headerSelector);
|
||||
outHeaderRect.set(0, 0, 0, header.getVisibleBounds().height());
|
||||
mLauncher.assertTrue("Can't find a widget header", header != null);
|
||||
|
||||
// Look for a header that has the test app name.
|
||||
@@ -179,11 +185,14 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
|
||||
if (widgetsContainer != null) {
|
||||
return widgetsContainer;
|
||||
}
|
||||
mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, List.of(headerTitle), 0);
|
||||
} else {
|
||||
mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, fullWidgetsPicker.getChildren(),
|
||||
0);
|
||||
|
||||
}
|
||||
mLauncher.scroll(
|
||||
fullWidgetsPicker,
|
||||
Direction.DOWN,
|
||||
outHeaderRect,
|
||||
/* steps= */ 10,
|
||||
/* slowDown= */ true);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user