Merge "Add WidgetsAndMore bottom sheet" into ub-launcher3-dorval

This commit is contained in:
Tony Wickham
2017-03-30 23:21:39 +00:00
committed by Android (Google) Code Review
11 changed files with 492 additions and 34 deletions

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->
<com.android.launcher3.widget.WidgetsAndMore
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:background="?android:attr/colorPrimary"
android:elevation="@dimen/deep_shortcuts_elevation"
android:layout_gravity="bottom">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_horizontal|bottom"
android:fontFamily="sans-serif"
android:textStyle="bold"
android:textColor="?android:attr/textColorPrimary"
android:textSize="20sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_horizontal|top"
android:fontFamily="sans-serif"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:text="@string/long_press_widget_to_add"/>
<TextView
android:id="@+id/widgets_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:textStyle="bold"
android:text="@string/widget_button_text"/>
<include layout="@layout/widgets_scroll_container"
android:id="@+id/widgets"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/widget_row_padding"
android:layout_marginBottom="@dimen/widget_row_padding"/>
<TextView
android:id="@+id/shortcuts_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:textStyle="bold"
android:text="@string/widgets_bottom_sheet_custom_shortcuts_section_title"/>
<include layout="@layout/widgets_scroll_container"
android:id="@+id/shortcuts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/widget_row_padding"
android:layout_marginBottom="@dimen/widget_row_padding" />
</com.android.launcher3.widget.WidgetsAndMore>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->
<HorizontalScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widgets_scroll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/colorPrimaryDark"
android:scrollbars="none">
<LinearLayout
android:id="@+id/widgets_cell_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:orientation="horizontal"
android:showDividers="none"/>
</HorizontalScrollView>

View File

@@ -203,6 +203,10 @@
<!-- Title for an app whose download has been started. -->
<string name="app_waiting_download_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> waiting to install</string>
<!-- Strings for widgets & more in the popup container/bottom sheet -->
<string name="widgets_and_more" translatable="false">Widgets &amp; more</string>
<string name="widgets_bottom_sheet_custom_shortcuts_section_title" translatable="false">Custom shortcuts</string>
<!-- Strings for accessibility actions -->
<!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] -->
<string name="action_add_to_workspace">Add to Home screen</string>

View File

@@ -16,9 +16,11 @@
package com.android.launcher3;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.IntDef;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
@@ -32,11 +34,16 @@ import java.lang.annotation.RetentionPolicy;
*/
public abstract class AbstractFloatingView extends LinearLayout {
@IntDef(flag = true, value = {TYPE_FOLDER, TYPE_POPUP_CONTAINER_WITH_ARROW})
@IntDef(flag = true, value = {
TYPE_FOLDER,
TYPE_POPUP_CONTAINER_WITH_ARROW,
TYPE_WIDGETS_AND_MORE
})
@Retention(RetentionPolicy.SOURCE)
public @interface FloatingViewType {}
public static final int TYPE_FOLDER = 1 << 0;
public static final int TYPE_POPUP_CONTAINER_WITH_ARROW = 1 << 1;
public static final int TYPE_WIDGETS_AND_MORE = 1 << 2;
protected boolean mIsOpen;
@@ -48,6 +55,15 @@ public abstract class AbstractFloatingView extends LinearLayout {
super(context, attrs, defStyleAttr);
}
/**
* We need to handle touch events to prevent them from falling through to the workspace below.
*/
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent ev) {
return true;
}
public final void close(boolean animate) {
animate &= !Utilities.isPowerSaverOn(getContext());
handleClose(animate);
@@ -119,7 +135,8 @@ public abstract class AbstractFloatingView extends LinearLayout {
}
public static AbstractFloatingView getTopOpenView(Launcher launcher) {
return getOpenView(launcher, TYPE_FOLDER | TYPE_POPUP_CONTAINER_WITH_ARROW);
return getOpenView(launcher, TYPE_FOLDER | TYPE_POPUP_CONTAINER_WITH_ARROW
| TYPE_WIDGETS_AND_MORE);
}
public abstract int getLogContainerType();

View File

@@ -88,7 +88,6 @@ import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PinItemRequestCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.dragndrop.DragController;

View File

@@ -61,6 +61,7 @@ import com.android.launcher3.logging.LoggerUtils;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.widget.WidgetsAndMore;
import java.util.ArrayList;
@@ -246,6 +247,12 @@ public class DragLayer extends InsettableFrameLayout {
return true;
}
WidgetsAndMore widgetsAndMore = WidgetsAndMore.getOpen(mLauncher);
if (widgetsAndMore != null && widgetsAndMore.onControllerInterceptTouchEvent(ev)) {
mActiveController = widgetsAndMore;
return true;
}
if (mPinchListener != null && mPinchListener.onControllerInterceptTouchEvent(ev)) {
// Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
mActiveController = mPinchListener;

View File

@@ -388,15 +388,6 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
return mFolderIcon;
}
/**
* We need to handle touch events to prevent them from falling through to the workspace below.
*/
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent ev) {
return true;
}
public void setDragController(DragController dragController) {
mDragController = dragController;
}

View File

@@ -530,15 +530,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
> ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
/**
* We need to handle touch events to prevent them from falling through to the workspace below.
*/
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent ev) {
return true;
}
/**
* Updates the notification header to reflect the badge info. Since this can be called
* for any badge info (not necessarily the one associated with this app), we first

View File

@@ -73,6 +73,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
private StylusEventHelper mStylusEventHelper;
protected CancellationSignal mActiveRequest;
private boolean mAnimatePreview = true;
protected final BaseActivity mActivity;
@@ -149,13 +150,21 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
return mWidgetImage;
}
public void setAnimatePreview(boolean shouldAnimate) {
mAnimatePreview = shouldAnimate;
}
public void applyPreview(Bitmap bitmap) {
if (bitmap != null) {
mWidgetImage.setBitmap(bitmap,
DrawableFactory.get(getContext()).getBadgeForUser(mItem.user, getContext()));
mWidgetImage.setAlpha(0f);
ViewPropertyAnimator anim = mWidgetImage.animate();
anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
if (mAnimatePreview) {
mWidgetImage.setAlpha(0f);
ViewPropertyAnimator anim = mWidgetImage.animate();
anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
} else {
mWidgetImage.setAlpha(1f);
}
}
}

View File

@@ -0,0 +1,326 @@
/*
* Copyright (C) 2017 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;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.TextView;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DropTarget;
import com.android.launcher3.Insettable;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.VerticalPullDetector;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.TouchController;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Bottom sheet for the "Widgets & more" long-press option.
*/
public class WidgetsAndMore extends AbstractFloatingView implements Insettable, TouchController,
VerticalPullDetector.Listener, View.OnClickListener, View.OnLongClickListener,
DragController.DragListener {
private int mTranslationYOpen;
private int mTranslationYClosed;
private float mTranslationYRange;
private Launcher mLauncher;
private ObjectAnimator mOpenCloseAnimator;
private Interpolator mFastOutSlowInInterpolator;
private VerticalPullDetector.ScrollInterpolator mScrollInterpolator;
private Rect mInsets;
private boolean mWasNavBarLight;
private VerticalPullDetector mVerticalPullDetector;
public WidgetsAndMore(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WidgetsAndMore(Context context, AttributeSet attrs, int defStyleAttr) {
super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme), attrs, defStyleAttr);
setWillNotDraw(false);
mLauncher = Launcher.getLauncher(context);
mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
mFastOutSlowInInterpolator = new FastOutSlowInInterpolator();
mScrollInterpolator = new VerticalPullDetector.ScrollInterpolator();
mInsets = new Rect();
mVerticalPullDetector = new VerticalPullDetector(context);
mVerticalPullDetector.setListener(this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mTranslationYOpen = 0;
mTranslationYClosed = getMeasuredHeight();
mTranslationYRange = mTranslationYClosed - mTranslationYOpen;
}
public void populateAndShow(ItemInfo itemInfo, List<WidgetItem> widgets) {
((TextView) findViewById(R.id.title)).setText(itemInfo.title);
List<WidgetItem> shortcuts = new ArrayList<>();
// Transfer configurable widgets to shortcuts
Iterator<WidgetItem> widgetsIter = widgets.iterator();
WidgetItem nextWidget;
while (widgetsIter.hasNext()) {
nextWidget = widgetsIter.next();
if (nextWidget.activityInfo != null) {
shortcuts.add(nextWidget);
widgetsIter.remove();
}
}
ViewGroup widgetRow = (ViewGroup) findViewById(R.id.widgets);
ViewGroup widgetCells = (ViewGroup) widgetRow.findViewById(R.id.widgets_cell_list);
ViewGroup shortcutRow = (ViewGroup) findViewById(R.id.shortcuts);
ViewGroup shortcutCells = (ViewGroup) shortcutRow.findViewById(R.id.widgets_cell_list);
for (int i = 0; i < widgets.size(); i++) {
addItemCell(widgetCells);
if (i < widgets.size() - 1) {
addDivider(widgetCells);
}
}
for (int i = 0; i < shortcuts.size(); i++) {
addItemCell(shortcutCells);
if (i < shortcuts.size() - 1) {
addDivider(shortcutCells);
}
}
// Bind the views in the horizontal tray regions.
if (widgetCells.getChildCount() > 0) {
for (int i = 0; i < widgets.size(); i++) {
WidgetCell widget = (WidgetCell) widgetCells.getChildAt(i*2); // skip dividers
widget.applyFromCellItem(widgets.get(i), LauncherAppState.getInstance(mLauncher)
.getWidgetCache());
widget.ensurePreview();
widget.setVisibility(View.VISIBLE);
}
} else {
removeView(findViewById(R.id.widgets_header));
}
if (shortcutCells.getChildCount() > 0) {
for (int i = 0; i < shortcuts.size(); i++) {
WidgetCell shortcut = (WidgetCell) shortcutCells.getChildAt(i*2); // skip dividers
shortcut.applyFromCellItem(shortcuts.get(i), LauncherAppState.getInstance(mLauncher)
.getWidgetCache());
shortcut.ensurePreview();
shortcut.setVisibility(View.VISIBLE);
}
} else {
removeView(findViewById(R.id.shortcuts_header));
}
mWasNavBarLight = (mLauncher.getWindow().getDecorView().getSystemUiVisibility()
& View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0;
mLauncher.getDragLayer().addView(this);
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
setTranslationY(mTranslationYClosed);
mIsOpen = false;
open(true);
}
private void addDivider(ViewGroup parent) {
LayoutInflater.from(getContext()).inflate(R.layout.widget_list_divider, parent, true);
}
private void addItemCell(ViewGroup parent) {
WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext()).inflate(
R.layout.widget_cell, parent, false);
widget.setOnClickListener(this);
widget.setOnLongClickListener(this);
widget.setAnimatePreview(false);
parent.addView(widget);
}
@Override
public void onClick(View view) {
mLauncher.getWidgetsView().handleClick();
}
@Override
public boolean onLongClick(View view) {
mLauncher.getDragController().addDragListener(this);
return mLauncher.getWidgetsView().handleLongClick(view);
}
private void open(boolean animate) {
if (mIsOpen || mOpenCloseAnimator.isRunning()) {
return;
}
mIsOpen = true;
setLightNavBar(true);
if (animate) {
mOpenCloseAnimator.setValues(new PropertyListBuilder()
.translationY(mTranslationYOpen).build());
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mVerticalPullDetector.finishedScrolling();
}
});
mOpenCloseAnimator.setInterpolator(mFastOutSlowInInterpolator);
mOpenCloseAnimator.start();
} else {
setTranslationY(mTranslationYOpen);
}
}
@Override
protected void handleClose(boolean animate) {
if (!mIsOpen || mOpenCloseAnimator.isRunning()) {
return;
}
if (animate) {
mOpenCloseAnimator.setValues(new PropertyListBuilder()
.translationY(mTranslationYClosed).build());
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mIsOpen = false;
mVerticalPullDetector.finishedScrolling();
((ViewGroup) getParent()).removeView(WidgetsAndMore.this);
setLightNavBar(mWasNavBarLight);
}
});
mOpenCloseAnimator.setInterpolator(mVerticalPullDetector.isIdleState()
? mFastOutSlowInInterpolator : mScrollInterpolator);
mOpenCloseAnimator.start();
} else {
setTranslationY(mTranslationYClosed);
setLightNavBar(mWasNavBarLight);
mIsOpen = false;
}
}
private void setLightNavBar(boolean lightNavBar) {
mLauncher.activateLightSystemBars(lightNavBar, false /* statusBar */, true /* navBar */);
}
@Override
protected boolean isOfType(@FloatingViewType int type) {
return (type & TYPE_WIDGETS_AND_MORE) != 0;
}
@Override
public int getLogContainerType() {
return LauncherLogProto.ContainerType.WIDGETS; // TODO: be more specific
}
/**
* Returns a WidgetsAndMore which is already open or null
*/
public static WidgetsAndMore getOpen(Launcher launcher) {
return getOpenView(launcher, TYPE_WIDGETS_AND_MORE);
}
@Override
public void setInsets(Rect insets) {
// Extend behind left, right, and bottom insets.
int leftInset = insets.left - mInsets.left;
int rightInset = insets.right - mInsets.right;
int bottomInset = insets.bottom - mInsets.bottom;
mInsets.set(insets);
setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
}
/* VerticalPullDetector.Listener */
@Override
public void onDragStart(boolean start) {
}
@Override
public boolean onDrag(float displacement, float velocity) {
setTranslationY(Utilities.boundToRange(displacement, mTranslationYOpen,
mTranslationYClosed));
return true;
}
@Override
public void onDragEnd(float velocity, boolean fling) {
if ((fling && velocity > 0) || getTranslationY() > (mTranslationYRange) / 2) {
mScrollInterpolator.setVelocityAtZero(velocity);
mOpenCloseAnimator.setDuration(mVerticalPullDetector.calculateDuration(velocity,
(mTranslationYClosed - getTranslationY()) / mTranslationYRange));
close(true);
} else {
mIsOpen = false;
mOpenCloseAnimator.setDuration(mVerticalPullDetector.calculateDuration(velocity,
(getTranslationY() - mTranslationYOpen) / mTranslationYRange));
open(true);
}
}
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
return mVerticalPullDetector.onTouchEvent(ev);
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
int directionsToDetectScroll = mVerticalPullDetector.isIdleState() ?
VerticalPullDetector.DIRECTION_DOWN : 0;
mVerticalPullDetector.setDetectableScrollConditions(
directionsToDetectScroll, false);
mVerticalPullDetector.onTouchEvent(ev);
return mVerticalPullDetector.isDraggingOrSettling();
}
/* DragListener */
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
// A widget or custom shortcut was dragged.
close(true);
}
@Override
public void onDragEnd() {
}
}

View File

@@ -29,13 +29,10 @@ import com.android.launcher3.BaseContainerView;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.IconCache;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.model.PackageItemInfo;
@@ -55,8 +52,6 @@ public class WidgetsContainerView extends BaseContainerView
/* Global instances that are used inside this container. */
@Thunk Launcher mLauncher;
private DragController mDragController;
private IconCache mIconCache;
/* Recycler view related member variables */
private WidgetsRecyclerView mRecyclerView;
@@ -76,9 +71,7 @@ public class WidgetsContainerView extends BaseContainerView
public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
mDragController = mLauncher.getDragController();
mAdapter = new WidgetsListAdapter(this, this, context);
mIconCache = LauncherAppState.getInstance(context).getIconCache();
if (LOGD) {
Log.d(TAG, "WidgetsContainerView constructor");
}
@@ -116,6 +109,10 @@ public class WidgetsContainerView extends BaseContainerView
|| mLauncher.getWorkspace().isSwitchingState()
|| !(v instanceof WidgetCell)) return;
handleClick();
}
public void handleClick() {
// Let the user know that they have to long press to add a widget
if (mWidgetInstructionToast != null) {
mWidgetInstructionToast.cancel();
@@ -130,14 +127,19 @@ public class WidgetsContainerView extends BaseContainerView
@Override
public boolean onLongClick(View v) {
// When we have exited the widget tray, disregard long clicks
if (!mLauncher.isWidgetsViewVisible()) return false;
return handleLongClick(v);
}
public boolean handleLongClick(View v) {
if (LOGD) {
Log.d(TAG, String.format("onLongClick [v=%s]", v));
}
// Return early if this is not initiated from a touch
if (!v.isInTouchMode()) return false;
// When we have exited all apps or are in transition, disregard long clicks
if (!mLauncher.isWidgetsViewVisible() ||
mLauncher.getWorkspace().isSwitchingState()) return false;
// When we are in transition, disregard long clicks
if (mLauncher.getWorkspace().isSwitchingState()) return false;
// Return if global dragging is not enabled
if (!mLauncher.isDraggingEnabled()) return false;