Snap for 9397200 from 67e171a872 to udc-release

Change-Id: Ic9297dddb024996c8fe857d64253980a40cab707
This commit is contained in:
Android Build Coastguard Worker
2022-12-13 04:27:41 +00:00
20 changed files with 316 additions and 236 deletions
+1 -1
View File
@@ -46,7 +46,7 @@
android:id="@+id/edu_start_button"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_marginBottom="92dp"
android:layout_marginBottom="18dp"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@id/content"
app:layout_constraintBottom_toBottomOf="parent"
@@ -878,6 +878,16 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(false);
}
/** Called when we want to hide the overlay window when user performs swipe up gesture. */
public void onSwipeToHideOverlay() {
mControllers.taskbarOverlayController.hideWindow();
}
/** Returns {@code true} if taskbar is stashed. */
public boolean isTaskbarStashed() {
return mControllers.taskbarStashController.isStashed();
}
/**
* Called to start the taskbar translation spring to its settled translation (0).
*/
@@ -15,11 +15,11 @@
*/
package com.android.launcher3.taskbar;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_APP_EDU;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
@@ -62,20 +62,21 @@ public class TaskbarEduController implements TaskbarControllers.LoggableTaskbarC
: R.layout.taskbar_edu_pages_persistent,
mPagedView,
true);
// Provide enough room for taskbar.
View startButton = mTaskbarEduView.findViewById(R.id.edu_start_button);
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) startButton.getLayoutParams();
DeviceProfile dp = overlayContext.getDeviceProfile();
layoutParams.bottomMargin += DisplayController.isTransientTaskbar(overlayContext)
? dp.taskbarSize + dp.transientTaskbarMargin
: dp.taskbarSize;
mTaskbarEduView.init(new TaskbarEduCallbacks());
mControllers.navbarButtonsViewController.setSlideInViewVisible(true);
TaskbarStashController stashController = mControllers.taskbarStashController;
stashController.updateStateForFlag(FLAG_STASHED_IN_APP_EDU, true);
stashController.applyState(overlayController.getOpenDuration());
mTaskbarEduView.setOnCloseBeginListener(() -> {
mControllers.navbarButtonsViewController.setSlideInViewVisible(false);
// Post in case view is closing due to gesture navigation. If a gesture is in progress,
// wait to unstash until after the gesture is finished.
MAIN_EXECUTOR.post(() -> stashController.resetFlagIfNoGestureInProgress(
FLAG_STASHED_IN_APP_EDU));
});
mTaskbarEduView.setOnCloseBeginListener(
() -> mControllers.navbarButtonsViewController.setSlideInViewVisible(false));
mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null);
mTaskbarEduView.show();
}
@@ -82,7 +82,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
public static final int FLAG_IN_SETUP = 1 << 8; // In the Setup Wizard
public static final int FLAG_STASHED_SMALL_SCREEN = 1 << 9; // phone screen gesture nav, stashed
public static final int FLAG_STASHED_IN_APP_AUTO = 1 << 10; // Autohide (transient taskbar).
public static final int FLAG_STASHED_IN_APP_EDU = 1 << 11; // EDU is visible.
// If any of these flags are enabled, isInApp should return true.
private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
@@ -91,7 +90,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
| FLAG_STASHED_IN_SYSUI_STATE | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
| FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_TASKBAR_ALL_APPS
| FLAG_STASHED_SMALL_SCREEN | FLAG_STASHED_IN_APP_AUTO | FLAG_STASHED_IN_APP_EDU;
| FLAG_STASHED_SMALL_SCREEN | FLAG_STASHED_IN_APP_AUTO;
private static final int FLAGS_STASHED_IN_APP_IGNORING_IME =
FLAGS_STASHED_IN_APP & ~FLAG_STASHED_IN_APP_IME;
@@ -101,8 +100,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
// Currently any flag that causes us to stash in an app is included, except for IME or All Apps
// since those cover the underlying app anyway and thus the app shouldn't change insets.
private static final int FLAGS_REPORT_STASHED_INSETS_TO_APP = FLAGS_STASHED_IN_APP
& ~FLAG_STASHED_IN_APP_IME & ~FLAG_STASHED_IN_TASKBAR_ALL_APPS
& ~FLAG_STASHED_IN_APP_EDU;
& ~FLAG_STASHED_IN_APP_IME & ~FLAG_STASHED_IN_TASKBAR_ALL_APPS;
/**
* How long to stash/unstash when manually invoked via long press.
@@ -728,7 +726,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
// Only update the following flags when system gesture is not in progress.
boolean shouldStashForIme = shouldStashForIme();
updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
updateStateForFlag(FLAG_STASHED_IN_APP_EDU, false);
if (hasAnyFlag(FLAG_STASHED_IN_APP_IME) != shouldStashForIme) {
updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme);
applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
@@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -84,7 +85,9 @@ public class TaskbarUIController {
* Manually closes the overlay window.
*/
public void hideOverlayWindow() {
mControllers.taskbarOverlayController.hideWindow();
if (!DisplayController.isTransientTaskbar(mControllers.taskbarActivityContext)) {
mControllers.taskbarOverlayController.hideWindow();
}
}
/**
@@ -19,6 +19,8 @@ import android.content.Context;
import android.util.AttributeSet;
import android.view.WindowInsets;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
@@ -44,4 +46,11 @@ public class TaskbarAllAppsContainerView extends
protected boolean isSearchSupported() {
return false;
}
@Override
protected void updateBackground(DeviceProfile deviceProfile) {
super.updateBackground(deviceProfile);
// TODO(b/240670050): Remove this and add header protection for the taskbar entrypoint.
mBottomSheetBackground.setBackgroundResource(R.drawable.bg_rounded_corner_bottom_sheet);
}
}
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar.allapps;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_APP_AUTO;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_TASKBAR_ALL_APPS;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
@@ -28,6 +29,7 @@ import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
import com.android.launcher3.util.DisplayController;
/**
* Handles the {@link TaskbarAllAppsContainerView} behavior and synchronizes its transitions with
@@ -88,18 +90,25 @@ final class TaskbarAllAppsViewController {
}
private void setUpTaskbarStashing() {
mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, true);
mTaskbarStashController.updateStateForFlag(
DisplayController.isTransientTaskbar(mContext)
? FLAG_STASHED_IN_APP_AUTO
: FLAG_STASHED_IN_TASKBAR_ALL_APPS,
true);
mTaskbarStashController.applyState(
ALL_APPS.getTransitionDuration(mContext, true /* isToState */));
mNavbarButtonsViewController.setSlideInViewVisible(true);
mSlideInView.setOnCloseBeginListener(() -> {
mNavbarButtonsViewController.setSlideInViewVisible(false);
AbstractFloatingView.closeOpenContainer(
mContext, AbstractFloatingView.TYPE_ACTION_POPUP);
// Post in case view is closing due to gesture navigation. If a gesture is in progress,
// wait to unstash until after the gesture is finished.
MAIN_EXECUTOR.post(() -> mTaskbarStashController.resetFlagIfNoGestureInProgress(
FLAG_STASHED_IN_TASKBAR_ALL_APPS));
if (!DisplayController.isTransientTaskbar(mContext)) {
// Post in case view is closing due to gesture navigation. If a gesture is in
// progress, wait to unstash until after the gesture is finished.
MAIN_EXECUTOR.post(() -> mTaskbarStashController.resetFlagIfNoGestureInProgress(
FLAG_STASHED_IN_TASKBAR_ALL_APPS));
}
});
}
@@ -34,6 +34,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.util.Log;
import android.util.SparseIntArray;
import androidx.annotation.NonNull;
@@ -54,6 +55,8 @@ import java.util.function.Consumer;
* and provide callers the relevant classes.
*/
public final class OverviewComponentObserver {
private static final String TAG = "OverviewComponentObserver";
private final BroadcastReceiver mUserPreferenceChangeReceiver =
new SimpleBroadcastReceiver(this::updateOverviewTargets);
private final BroadcastReceiver mOtherHomeAppUpdateReceiver =
@@ -147,7 +150,12 @@ public final class OverviewComponentObserver {
}
}
if (!mDeviceState.isHomeDisabled() && (defaultHome == null || mIsDefaultHome)) {
// TODO(b/258022658): Remove temporary logging.
Log.i(TAG, "updateOverviewTargets: mIsHomeDisabled=" + mIsHomeDisabled
+ ", isDefaultHomeNull=" + (defaultHome == null)
+ ", mIsDefaultHome=" + mIsDefaultHome);
if (!mIsHomeDisabled && (defaultHome == null || mIsDefaultHome)) {
// User default home is same as out home app. Use Overview integrated in Launcher.
mActivityInterface = LauncherActivityInterface.INSTANCE;
mIsHomeAndOverviewSame = true;
@@ -53,8 +53,11 @@ public class TaskbarStashInputConsumer extends DelegateInputConsumer {
private final float mUnstashArea;
private final float mScreenWidth;
private final int mTaskbarThresholdY;
private boolean mHasPassedTaskbarThreshold;
private final int mTaskbarNavThresholdY;
private final int mTaskbarAppWindowThresholdY;
private final boolean mTaskbarAlreadyOpen;
private boolean mHasPassedTaskbarNavThreshold;
private boolean mHasPassedTaskbarAppWindowThreshold;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
@@ -73,11 +76,18 @@ public class TaskbarStashInputConsumer extends DelegateInputConsumer {
Resources res = context.getResources();
mUnstashArea = res.getDimensionPixelSize(R.dimen.taskbar_unstash_input_area);
int taskbarThreshold = res.getDimensionPixelSize(ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
int taskbarNavThreshold = res.getDimensionPixelSize(ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
? R.dimen.taskbar_nav_threshold_v2
: R.dimen.taskbar_nav_threshold);
int taskbarAppWindowThreshold = res.getDimensionPixelSize(
ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
? R.dimen.taskbar_app_window_threshold_v2
: R.dimen.taskbar_app_window_threshold);
int screenHeight = taskbarActivityContext.getDeviceProfile().heightPx;
mTaskbarThresholdY = screenHeight - taskbarThreshold;
mTaskbarNavThresholdY = screenHeight - taskbarNavThreshold;
mTaskbarAppWindowThresholdY = screenHeight - taskbarAppWindowThreshold;
mTaskbarAlreadyOpen = mTaskbarActivityContext != null
&& !mTaskbarActivityContext.isTaskbarStashed();
mIsTransientTaskbar = DisplayController.isTransientTaskbar(context);
@@ -113,7 +123,8 @@ public class TaskbarStashInputConsumer extends DelegateInputConsumer {
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
mHasPassedTaskbarThreshold = false;
mHasPassedTaskbarNavThreshold = false;
mHasPassedTaskbarAppWindowThreshold = false;
mTaskbarActivityContext.setAutohideSuspendFlag(
FLAG_AUTOHIDE_SUSPEND_TOUCHING, true);
if (isInArea(x)) {
@@ -156,18 +167,23 @@ public class TaskbarStashInputConsumer extends DelegateInputConsumer {
if (mIsTransientTaskbar) {
float dY = mLastPos.y - mDownPos.y;
boolean passedTaskbarThreshold = dY < 0
&& mLastPos.y < mTaskbarThresholdY;
if (!mHasPassedTaskbarThreshold
&& passedTaskbarThreshold) {
mHasPassedTaskbarThreshold = true;
boolean passedTaskbarNavThreshold = dY < 0
&& mLastPos.y < mTaskbarNavThresholdY;
boolean passedTaskbarAppWindowThreshold = dY < 0
&& mLastPos.y < mTaskbarAppWindowThresholdY;
if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold) {
mHasPassedTaskbarNavThreshold = true;
mTaskbarActivityContext.onSwipeToUnstashTaskbar();
}
if (mTaskbarAlreadyOpen || (!mHasPassedTaskbarAppWindowThreshold
&& passedTaskbarAppWindowThreshold)) {
mHasPassedTaskbarAppWindowThreshold = true;
mTaskbarActivityContext.onSwipeToHideOverlay();
}
if (dY < 0) {
dY = -OverScroll.dampedScroll(-dY, mTaskbarThresholdY);
dY = -OverScroll.dampedScroll(-dY, mTaskbarNavThresholdY);
if (mTransitionCallback != null) {
mTransitionCallback.onActionMove(dY);
}
@@ -185,7 +201,8 @@ public class TaskbarStashInputConsumer extends DelegateInputConsumer {
if (mTransitionCallback != null) {
mTransitionCallback.onActionEnd();
}
mHasPassedTaskbarThreshold = false;
mHasPassedTaskbarNavThreshold = false;
mHasPassedTaskbarAppWindowThreshold = false;
break;
}
}
@@ -16,8 +16,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bottom_sheet_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_rounded_corner_bottom_sheet">
android:layout_height="match_parent">
<View
android:id="@+id/bottom_sheet_handle_area"
@@ -17,11 +17,12 @@
package com.android.launcher3;
import android.os.Handler;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import com.android.launcher3.util.TouchUtil;
/**
* Utility class to handle tripper long press or right click on a view with custom timeout and
* stylus event
@@ -64,7 +65,7 @@ public class CheckLongPressHelper {
cancelLongPress();
// Mouse right click should immediately trigger a long press
if (isMouseRightClickDownOrMove(ev)) {
if (TouchUtil.isMouseRightClickDownOrMove(ev)) {
mIsInMouseRightClick = true;
triggerLongPress();
final Handler handler = mView.getHandler();
@@ -176,14 +177,4 @@ public class CheckLongPressHelper {
return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
&& event.isButtonPressed(MotionEvent.BUTTON_SECONDARY);
}
/**
* Detect ACTION_DOWN or ACTION_MOVE from mouse right button. Note that we cannot detect
* ACTION_UP from mouse's right button because, in that case,
* {@link MotionEvent#getButtonState()} returns 0 for any mouse button (right, middle, right).
*/
private static boolean isMouseRightClickDownOrMove(MotionEvent event) {
return event.isFromSource(InputDevice.SOURCE_MOUSE)
&& ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0);
}
}
+1 -3
View File
@@ -43,7 +43,6 @@ import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
import static com.android.launcher3.config.FeatureFlags.SHOW_DELIGHTFUL_PAGINATION;
import static com.android.launcher3.config.FeatureFlags.SHOW_DOT_PAGINATION;
import static com.android.launcher3.logging.StatsLogManager.EventEnum;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -1280,8 +1279,7 @@ public class Launcher extends StatefulActivity<LauncherState>
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if ((SHOW_DOT_PAGINATION.get() || SHOW_DELIGHTFUL_PAGINATION.get())
&& WorkspacePageIndicator.class.getName().equals(name)) {
if ((SHOW_DOT_PAGINATION.get()) && WorkspacePageIndicator.class.getName().equals(name)) {
return LayoutInflater.from(context).inflate(R.layout.page_indicator_dots,
(ViewGroup) parent, false);
}
@@ -281,6 +281,10 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
@Override
public int getHeaderBottom() {
if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
if (mActivityContext.getDeviceProfile().isTablet) {
return super.getHeaderBottom() + mHeader.getClipTop()
+ mBottomSheetBackground.getTop();
}
return super.getHeaderBottom() + mHeader.getClipTop();
}
return super.getHeaderBottom() + mSearchContainer.getBottom();
@@ -26,8 +26,11 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.Process;
@@ -35,6 +38,7 @@ import android.os.UserManager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -121,7 +125,7 @@ public abstract class BaseAllAppsContainerView<T extends Context & ActivityConte
private SearchRecyclerView mSearchRecyclerView;
protected FloatingHeaderView mHeader;
private View mBottomSheetBackground;
protected View mBottomSheetBackground;
private View mBottomSheetHandleArea;
@Nullable private View mSearchBarProtection;
@@ -134,8 +138,12 @@ public abstract class BaseAllAppsContainerView<T extends Context & ActivityConte
private final int mScrimColor;
private final int mHeaderProtectionColor;
protected final float mHeaderThreshold;
private final Path mTmpPath = new Path();
private final RectF mTmpRectF = new RectF();
private float[] mBottomSheetCornerRadii;
private ScrimView mScrimView;
private int mHeaderColor;
private int mBottomSheetBackgroundColor;
private int mTabsProtectionAlpha;
protected BaseAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -236,6 +244,9 @@ public abstract class BaseAllAppsContainerView<T extends Context & ActivityConte
protected void updateBackground(DeviceProfile deviceProfile) {
mBottomSheetBackground.setVisibility(deviceProfile.isTablet ? View.VISIBLE : View.GONE);
// Note: For tablets, the opaque background and header protection are added in drawOnScrim.
// For the taskbar entrypoint, the scrim is drawn differently, so a static background is
// added in TaskbarAllAppsContainerView and header protection is not yet supported.
}
private void onAppsUpdated() {
@@ -428,9 +439,22 @@ public abstract class BaseAllAppsContainerView<T extends Context & ActivityConte
rebindAdapters(true /* force */);
mBottomSheetBackground = findViewById(R.id.bottom_sheet_background);
updateBackground(mActivityContext.getDeviceProfile());
mBottomSheetHandleArea = findViewById(R.id.bottom_sheet_handle_area);
float cornerRadius = Themes.getDialogCornerRadius(getContext());
mBottomSheetCornerRadii = new float[]{
cornerRadius,
cornerRadius, // Top left radius in px
cornerRadius,
cornerRadius, // Top right radius in px
0,
0, // Bottom right
0,
0 // Bottom left
};
final TypedValue value = new TypedValue();
getContext().getTheme().resolveAttribute(android.R.attr.colorBackground, value, true);
mBottomSheetBackgroundColor = value.data;
updateBackground(mActivityContext.getDeviceProfile());
}
@Override
@@ -743,6 +767,20 @@ public abstract class BaseAllAppsContainerView<T extends Context & ActivityConte
@Override
public void drawOnScrim(Canvas canvas) {
boolean isTablet = mActivityContext.getDeviceProfile().isTablet;
// Draw full background panel for tablets.
if (isTablet) {
mHeaderPaint.setColor(mBottomSheetBackgroundColor);
View panel = (View) mBottomSheetBackground;
float translationY = ((View) panel.getParent()).getTranslationY();
mTmpRectF.set(panel.getLeft(), panel.getTop() + translationY,
panel.getRight(), panel.getBottom());
mTmpPath.reset();
mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
canvas.drawPath(mTmpPath, mHeaderPaint);
}
if (!mHeader.isHeaderProtectionSupported()) {
return;
}
@@ -753,24 +791,44 @@ public abstract class BaseAllAppsContainerView<T extends Context & ActivityConte
mHeaderPaint.setColor(mHeaderColor);
mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor)));
}
if (mHeaderPaint.getColor() != mScrimColor && mHeaderPaint.getColor() != 0) {
int bottom = getHeaderBottom();
FloatingHeaderView headerView = getFloatingHeaderView();
if (!mUsingTabs && !FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
// Add protection which is otherwise added when tabs scroll up.
bottom += headerView.getTabsAdditionalPaddingTop();
if (mHeaderPaint.getColor() == mScrimColor || mHeaderPaint.getColor() == 0) {
return;
}
int bottom = getHeaderBottom();
FloatingHeaderView headerView = getFloatingHeaderView();
if (!mUsingTabs && !FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
// Add protection which is otherwise added when tabs scroll up.
bottom += headerView.getTabsAdditionalPaddingTop();
}
if (isTablet) {
// Start adding header protection if search bar or tabs will attach to the top.
if (!FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() || mUsingTabs) {
View panel = (View) mBottomSheetBackground;
float translationY = ((View) panel.getParent()).getTranslationY();
mTmpRectF.set(panel.getLeft(), panel.getTop() + translationY, panel.getRight(),
bottom);
mTmpPath.reset();
mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
canvas.drawPath(mTmpPath, mHeaderPaint);
}
} else {
canvas.drawRect(0, 0, canvas.getWidth(), bottom, mHeaderPaint);
int tabsHeight = headerView.getPeripheralProtectionHeight();
if (mTabsProtectionAlpha > 0 && tabsHeight != 0) {
if (DEBUG_HEADER_PROTECTION) {
mHeaderPaint.setColor(Color.BLUE);
mHeaderPaint.setAlpha(255);
} else {
mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha));
}
canvas.drawRect(0, bottom, canvas.getWidth(), bottom + tabsHeight, mHeaderPaint);
}
int tabsHeight = headerView.getPeripheralProtectionHeight();
if (mTabsProtectionAlpha > 0 && tabsHeight != 0) {
if (DEBUG_HEADER_PROTECTION) {
mHeaderPaint.setColor(Color.BLUE);
mHeaderPaint.setAlpha(255);
} else {
mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha));
}
int left = 0;
int right = canvas.getWidth();
if (isTablet) {
left = mBottomSheetBackground.getLeft();
right = mBottomSheetBackground.getRight();
}
canvas.drawRect(left, bottom, right, bottom + tabsHeight, mHeaderPaint);
}
}
@@ -311,10 +311,6 @@ public final class FeatureFlags {
"SCROLL_TOP_TO_RESET", false, "Bring up IME and focus on "
+ "input when scroll to top if 'Always show keyboard' is enabled or in prefix state");
public static final BooleanFlag SHOW_DELIGHTFUL_PAGINATION = getDebugFlag(
"SHOW_DELIGHTFUL_PAGINATION", false,
"Enable showing the new 'delightful pagination' which is a brand"
+ " new animation for folder pagination and workspace pagination");
public static final BooleanFlag POPUP_MATERIAL_U = new DeviceFlag(
"POPUP_MATERIAL_U", false, "Switch popup UX to use material U");
@@ -339,7 +335,7 @@ public final class FeatureFlags {
+ " nav mode and when transient taskbar is enabled.");
public static final BooleanFlag ENABLE_TRANSIENT_TASKBAR = getDebugFlag(
"ENABLE_TRANSIENT_TASKBAR", false, "Enables transient taskbar.");
"ENABLE_TRANSIENT_TASKBAR", true, "Enables transient taskbar.");
public static final BooleanFlag ENABLE_TRACKPAD_GESTURE = getDebugFlag(
"ENABLE_TRACKPAD_GESTURE", false, "Enables trackpad gesture.");
@@ -16,7 +16,6 @@
package com.android.launcher3.pageindicators;
import static com.android.launcher3.config.FeatureFlags.SHOW_DELIGHTFUL_PAGINATION;
import static com.android.launcher3.config.FeatureFlags.SHOW_DOT_PAGINATION;
import android.animation.Animator;
@@ -32,7 +31,6 @@ import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
@@ -48,7 +46,6 @@ import androidx.annotation.Nullable;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.util.Themes;
/**
@@ -70,7 +67,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator
private static final int PAGE_INDICATOR_ALPHA = 255;
private static final int DOT_ALPHA = 128;
private static final int DOT_GAP_FACTOR = 3;
private static final float DOT_GAP_FACTOR_FLOAT = 3.8f;
private static final int VISIBLE_ALPHA = 1;
private static final int INVISIBLE_ALPHA = 0;
private Paint mPaginationPaint;
@@ -78,8 +74,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator
// This value approximately overshoots to 1.5 times the original size.
private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f;
private static final float INDICATOR_ROTATION = 180f;
private static final RectF sTempRect = new RectF();
private static final FloatProperty<PageIndicatorDots> CURRENT_POSITION =
@@ -112,11 +106,8 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator
};
private final Handler mDelayedPaginationFadeHandler = new Handler(Looper.getMainLooper());
private final Drawable mPageIndicatorDrawable;
private final float mDotRadius;
private final float mCircleGap;
private final float mPageIndicatorSize;
private final float mPageIndicatorRadius;
private final boolean mIsRtl;
private int mNumPages;
@@ -159,31 +150,14 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator
mPaginationPaint.setStyle(Style.FILL);
mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
if (SHOW_DELIGHTFUL_PAGINATION.get()) {
mPageIndicatorSize = getResources().getDimension(
R.dimen.page_indicator_size);
mPageIndicatorRadius = mPageIndicatorSize / 2;
mPageIndicatorDrawable = context.getDrawable(R.drawable.page_indicator);
mPageIndicatorDrawable.setBounds(0, 0, (int) mPageIndicatorSize,
(int) mPageIndicatorSize);
mCircleGap = DOT_GAP_FACTOR_FLOAT * mDotRadius;
} else {
mPageIndicatorSize = 0;
mPageIndicatorRadius = 0;
mPageIndicatorDrawable = null;
mCircleGap = DOT_GAP_FACTOR * mDotRadius;
}
if (!SHOW_DELIGHTFUL_PAGINATION.get()) {
setOutlineProvider(new MyOutlineProver());
}
mCircleGap = DOT_GAP_FACTOR * mDotRadius;
setOutlineProvider(new MyOutlineProver());
mIsRtl = Utilities.isRtl(getResources());
}
@Override
public void setScroll(int currentScroll, int totalScroll) {
if (SHOW_DELIGHTFUL_PAGINATION.get() || SHOW_DOT_PAGINATION.get()) {
if (SHOW_DOT_PAGINATION.get()) {
animatePaginationToAlpha(VISIBLE_ALPHA);
}
@@ -197,16 +171,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator
}
mTotalScroll = totalScroll;
if (SHOW_DELIGHTFUL_PAGINATION.get()) {
mCurrentScroll = currentScroll;
invalidate();
if (mShouldAutoHide
&& (getScrollPerPage() == 0 || mCurrentScroll % getScrollPerPage() == 0)) {
hideAfterDelay();
}
return;
}
int scrollPerPage = totalScroll / (mNumPages - 1);
int pageToLeft = currentScroll / scrollPerPage;
@@ -404,122 +368,45 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator
}
for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) {
mPaginationPaint.setAlpha(i == mActivePage ? PAGE_INDICATOR_ALPHA : DOT_ALPHA);
if (SHOW_DELIGHTFUL_PAGINATION.get()) {
if (i != mActivePage) {
canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i],
mPaginationPaint);
} else {
drawPageIndicator(canvas, mEntryAnimationRadiusFactors[i]);
}
} else {
canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i],
mPaginationPaint);
}
canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i],
mPaginationPaint);
x += circleGap;
}
} else {
// Here we draw the dots
mPaginationPaint.setAlpha(DOT_ALPHA);
for (int i = 0; i < mNumPages; i++) {
if (SHOW_DELIGHTFUL_PAGINATION.get()) {
canvas.drawCircle(x, y, getRadius(x), mPaginationPaint);
} else {
canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
}
canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
x += circleGap;
}
// Here we draw the current page indicator
mPaginationPaint.setAlpha(PAGE_INDICATOR_ALPHA);
if (SHOW_DELIGHTFUL_PAGINATION.get()) {
drawPageIndicator(canvas, 1);
} else {
canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
}
canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
}
}
/**
* Draws the page indicator, denoting the currently selected page
*
* @param canvas is used to draw the page indicator and to rotate it as we scroll
* @param scale is used to set the scale of our canvas
*/
private void drawPageIndicator(Canvas canvas, float scale) {
RectF currRect = getActiveRect();
// saves the canvas so we can later restore it to its original scale
canvas.save();
// Moves the canvas to start at the top left corner of the page indicator
canvas.translate(currRect.left, currRect.top);
// Scales the canvas in place to animate the indicator on entry
canvas.scale(scale, scale, mPageIndicatorRadius, mPageIndicatorRadius);
int scrollPerPage = getScrollPerPage();
// This IF is to avoid division by 0
if (scrollPerPage != 0) {
int delta = mCurrentScroll % scrollPerPage;
canvas.rotate((INDICATOR_ROTATION * delta) / scrollPerPage,
mPageIndicatorRadius, mPageIndicatorRadius);
}
mPageIndicatorDrawable.draw(canvas);
canvas.restore();
}
/**
* Returns the radius of the circle based on how close the page indicator is to it
*
* @param dotPositionX is the position the dot is located at in the x-axis
*/
private float getRadius(float dotPositionX) {
float startXIndicator =
((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2) - getOffset();
float indicatorPosition = startXIndicator + getIndicatorScrollDistance()
+ mPageIndicatorRadius;
// If the indicator gets close enough to a dot then we change the radius
// of the dot based on how close the indicator is to it.
float dotDistance = Math.abs(indicatorPosition - dotPositionX);
if (dotDistance <= mCircleGap) {
return Utilities.mapToRange(dotDistance, 0, mCircleGap, 0f, mDotRadius,
Interpolators.LINEAR);
}
return mDotRadius;
}
private RectF getActiveRect() {
float startCircle = (int) mCurrentPosition;
float delta = mCurrentPosition - startCircle;
float diameter = 2 * mDotRadius;
float startX;
if (SHOW_DELIGHTFUL_PAGINATION.get()) {
startX = ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2) - getOffset();
sTempRect.top = (getHeight() - mPageIndicatorSize) * 0.5f;
sTempRect.bottom = (getHeight() + mPageIndicatorSize) * 0.5f;
sTempRect.left = startX + getIndicatorScrollDistance();
sTempRect.right = sTempRect.left + mPageIndicatorSize;
startX = ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2);
sTempRect.top = (getHeight() * 0.5f) - mDotRadius;
sTempRect.bottom = (getHeight() * 0.5f) + mDotRadius;
sTempRect.left = startX + (startCircle * mCircleGap);
sTempRect.right = sTempRect.left + diameter;
if (delta < SHIFT_PER_ANIMATION) {
// dot is capturing the right circle.
sTempRect.right += delta * mCircleGap * 2;
} else {
startX = ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2);
sTempRect.top = (getHeight() * 0.5f) - mDotRadius;
sTempRect.bottom = (getHeight() * 0.5f) + mDotRadius;
sTempRect.left = startX + (startCircle * mCircleGap);
sTempRect.right = sTempRect.left + diameter;
// Dot is leaving the left circle.
sTempRect.right += mCircleGap;
if (delta < SHIFT_PER_ANIMATION) {
// dot is capturing the right circle.
sTempRect.right += delta * mCircleGap * 2;
} else {
// Dot is leaving the left circle.
sTempRect.right += mCircleGap;
delta -= SHIFT_PER_ANIMATION;
sTempRect.left += delta * mCircleGap * 2;
}
delta -= SHIFT_PER_ANIMATION;
sTempRect.left += delta * mCircleGap * 2;
}
if (mIsRtl) {
@@ -531,29 +418,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator
return sTempRect;
}
/**
* The offset between the radius of the dot and the midpoint of the indicator so that
* the indicator is centered in with the indicator circles
*/
private float getOffset() {
return mPageIndicatorRadius - mDotRadius;
}
/**
* Returns an int that is the amount we need to scroll per page
*/
private int getScrollPerPage() {
return mNumPages > 1 ? mTotalScroll / (mNumPages - 1) : 0;
}
/**
* The current scroll adjusted for the distance the indicator needs to travel on the screen
*/
private float getIndicatorScrollDistance() {
int scrollPerPage = getScrollPerPage();
return scrollPerPage != 0 ? ((float) mCurrentScroll / scrollPerPage) * mCircleGap : 0;
}
private class MyOutlineProver extends ViewOutlineProvider {
@Override
@@ -44,6 +44,7 @@ import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.TouchUtil;
/**
* Helper class to handle touch on empty space in workspace and show options popup on long press
@@ -105,6 +106,11 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe
if (handleLongPress) {
mLongPressState = STATE_REQUESTED;
mTouchDownPoint.set(ev.getX(), ev.getY());
// Mouse right button's ACTION_DOWN should immediately show menu
if (TouchUtil.isMouseRightClickDownOrMove(ev)) {
maybeShowMenu();
return true;
}
}
mWorkspace.onTouchEvent(ev);
@@ -185,6 +191,10 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe
@Override
public void onLongPress(MotionEvent event) {
maybeShowMenu();
}
private void maybeShowMenu() {
if (mLongPressState == STATE_REQUESTED) {
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Workspace.longPress");
if (canHandleLongPress()) {
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 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.util;
import android.view.InputDevice;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
/** Util class for touch event. */
public final class TouchUtil {
private TouchUtil() {}
/**
* Detect ACTION_DOWN or ACTION_MOVE from mouse right button. Note that we cannot detect
* ACTION_UP from mouse's right button because, in that case,
* {@link MotionEvent#getButtonState()} returns 0 for any mouse button (right, middle, right).
*/
public static boolean isMouseRightClickDownOrMove(@NonNull MotionEvent event) {
return event.isFromSource(InputDevice.SOURCE_MOUSE)
&& ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0);
}
}
@@ -33,6 +33,7 @@ import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -85,6 +86,7 @@ public class AddWidgetTest extends AbstractLauncherUiTest {
* A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
* Custom shortcuts are replaced by deep shortcuts after api 25.
*/
@Ignore
@Test
@PortraitLandscape
public void testDragCustomShortcut() throws Throwable {
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2022 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.util
import android.view.InputDevice
import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
/** Unit tests for [TouchUtil] */
@SmallTest
@RunWith(AndroidJUnit4::class)
class TouchUtilTest {
@Test
fun isMouseRightClickDownOrMove_onMouseRightButton_returnsTrue() {
val ev = MotionEvent.obtain(200, 300, MotionEvent.ACTION_MOVE, 1.0f, 0.0f, 0)
ev.source = InputDevice.SOURCE_MOUSE
ev.buttonState = MotionEvent.BUTTON_SECONDARY
assertThat(TouchUtil.isMouseRightClickDownOrMove(ev)).isTrue()
}
@Test
fun isMouseRightClickDownOrMove_onMouseLeftButton_returnsFalse() {
val ev = MotionEvent.obtain(200, 300, MotionEvent.ACTION_MOVE, 1.0f, 0.0f, 0)
ev.source = InputDevice.SOURCE_MOUSE
ev.buttonState = MotionEvent.BUTTON_PRIMARY
assertThat(TouchUtil.isMouseRightClickDownOrMove(ev)).isFalse()
}
@Test
fun isMouseRightClickDownOrMove_onMouseTertiaryButton_returnsFalse() {
val ev = MotionEvent.obtain(200, 300, MotionEvent.ACTION_MOVE, 1.0f, 0.0f, 0)
ev.source = InputDevice.SOURCE_MOUSE
ev.buttonState = MotionEvent.BUTTON_TERTIARY
assertThat(TouchUtil.isMouseRightClickDownOrMove(ev)).isFalse()
}
@Test
fun isMouseRightClickDownOrMove_onDpadRightButton_returnsFalse() {
val ev = MotionEvent.obtain(200, 300, MotionEvent.ACTION_MOVE, 1.0f, 0.0f, 0)
ev.source = InputDevice.SOURCE_DPAD
ev.buttonState = MotionEvent.BUTTON_SECONDARY
assertThat(TouchUtil.isMouseRightClickDownOrMove(ev)).isFalse()
}
}