Snap for 9397200 from 67e171a872 to udc-release
Change-Id: Ic9297dddb024996c8fe857d64253980a40cab707
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-5
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user