Merge "Don't draw bottom scrim in fully gestural mode" into ub-launcher3-master
diff --git a/quickstep/res/layout/back_gesture_tutorial_fragment.xml b/quickstep/res/layout/back_gesture_tutorial_fragment.xml
index 294e46e..d8c25bd 100644
--- a/quickstep/res/layout/back_gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/back_gesture_tutorial_fragment.xml
@@ -16,9 +16,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layerType="software"
android:background="@color/back_gesture_tutorial_background_color">
- <!--The layout is rendered on the software layer to avoid b/136158117-->
<ImageView
android:id="@+id/back_gesture_tutorial_fragment_hand_coaching"
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
new file mode 100644
index 0000000..3583676
--- /dev/null
+++ b/quickstep/res/values/colors.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<resources>
+ <color name="back_arrow_color_dark">#99000000</color>
+</resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 3fe91a3..5c2e992 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -25,6 +25,7 @@
import com.android.launcher3.R;
import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import java.util.Optional;
@@ -79,21 +80,33 @@
mHandCoachingAnimation.stop();
}
- void onGestureDetected() {
- hideHandCoachingAnimation();
-
- if (mTutorialStep == TutorialStep.CONFIRM) {
+ void onGestureAttempted(BackGestureResult result) {
+ if (mTutorialStep == TutorialStep.CONFIRM
+ && (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
+ || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT)) {
mFragment.closeTutorial();
return;
}
- if (mTutorialTypeInfo.get().getTutorialType() == TutorialType.RIGHT_EDGE_BACK_NAVIGATION) {
- mFragment.changeController(TutorialStep.ENGAGED,
- TutorialType.LEFT_EDGE_BACK_NAVIGATION);
+ if (!mTutorialTypeInfo.isPresent()) {
return;
}
- mFragment.changeController(TutorialStep.CONFIRM);
+ switch (mTutorialTypeInfo.get().getTutorialType()) {
+ case RIGHT_EDGE_BACK_NAVIGATION:
+ if (result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
+ hideHandCoachingAnimation();
+ mFragment.changeController(
+ TutorialStep.ENGAGED, TutorialType.LEFT_EDGE_BACK_NAVIGATION);
+ }
+ break;
+ case LEFT_EDGE_BACK_NAVIGATION:
+ if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT) {
+ hideHandCoachingAnimation();
+ mFragment.changeController(TutorialStep.CONFIRM);
+ }
+ break;
+ }
}
abstract Optional<Integer> getTitleStringId();
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
index 54408ce..593b695 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -17,21 +17,26 @@
import android.content.ActivityNotFoundException;
import android.content.Intent;
+import android.graphics.Insets;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
+import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.android.launcher3.R;
+import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
+import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import java.net.URISyntaxException;
import java.util.Optional;
/** Shows the Back gesture interactive tutorial. */
-public class BackGestureTutorialFragment extends Fragment {
+public class BackGestureTutorialFragment extends Fragment implements BackGestureAttemptCallback {
private static final String LOG_TAG = "TutorialFragment";
private static final String KEY_TUTORIAL_STEP = "tutorialStep";
@@ -47,6 +52,7 @@
private Optional<BackGestureTutorialController> mTutorialController = Optional.empty();
private View mRootView;
private BackGestureTutorialHandAnimation mHandCoachingAnimation;
+ private EdgeBackGestureHandler mEdgeBackGestureHandler;
public static BackGestureTutorialFragment newInstance(
TutorialStep tutorialStep, TutorialType tutorialType) {
@@ -64,17 +70,25 @@
Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
mTutorialStep = (TutorialStep) args.getSerializable(KEY_TUTORIAL_STEP);
mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE);
+ mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext());
+ mEdgeBackGestureHandler.registerBackGestureAttemptCallback(this);
}
@Override
public View onCreateView(
- LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mRootView = inflater.inflate(R.layout.back_gesture_tutorial_fragment,
container, /* attachToRoot= */ false);
mRootView.findViewById(R.id.back_gesture_tutorial_fragment_close_button)
.setOnClickListener(this::onCloseButtonClicked);
+ mRootView.setOnApplyWindowInsetsListener((view, insets) -> {
+ Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars());
+ mEdgeBackGestureHandler.setInsets(systemInsets.left, systemInsets.right);
+ return insets;
+ });
+ mRootView.setOnTouchListener(mEdgeBackGestureHandler);
mHandCoachingAnimation = new BackGestureTutorialHandAnimation(getContext(), mRootView);
return mRootView;
@@ -92,6 +106,14 @@
mHandCoachingAnimation.stop();
}
+ void onAttachedToWindow() {
+ mEdgeBackGestureHandler.setIsEnabled(true);
+ }
+
+ void onDetachedFromWindow() {
+ mEdgeBackGestureHandler.setIsEnabled(false);
+ }
+
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putSerializable(KEY_TUTORIAL_STEP, mTutorialStep);
@@ -125,10 +147,9 @@
this.mTutorialType = tutorialType;
}
- void onBackPressed() {
- if (mTutorialController.isPresent()) {
- mTutorialController.get().onGestureDetected();
- }
+ @Override
+ public void onBackGestureAttempted(BackGestureResult result) {
+ mTutorialController.ifPresent(controller -> controller.onGestureAttempted(result));
}
void closeTutorial() {
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
new file mode 100644
index 0000000..04cd2f4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2020 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.quickstep.interaction;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+import com.android.launcher3.ResourceUtils;
+
+/**
+ * Utility class to handle edge swipes for back gestures.
+ *
+ * Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java.
+ */
+public class EdgeBackGestureHandler implements DisplayListener, OnTouchListener {
+
+ private static final String TAG = "EdgeBackGestureHandler";
+ private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
+ "gestures.back_timeout", 250);
+
+ private final Context mContext;
+
+ private final Point mDisplaySize = new Point();
+ private final int mDisplayId;
+
+ // The edge width where touch down is allowed
+ private int mEdgeWidth;
+ // The bottom gesture area height
+ private int mBottomGestureHeight;
+ // The slop to distinguish between horizontal and vertical motion
+ private final float mTouchSlop;
+ // Duration after which we consider the event as longpress.
+ private final int mLongPressTimeout;
+
+ private final PointF mDownPoint = new PointF();
+ private boolean mThresholdCrossed = false;
+ private boolean mAllowGesture = false;
+ private boolean mIsEnabled;
+ private int mLeftInset;
+ private int mRightInset;
+
+ private EdgeBackGesturePanel mEdgeBackPanel;
+ private BackGestureAttemptCallback mGestureCallback;
+
+ private final EdgeBackGesturePanel.BackCallback mBackCallback =
+ new EdgeBackGesturePanel.BackCallback() {
+ @Override
+ public void triggerBack() {
+ if (mGestureCallback != null) {
+ mGestureCallback.onBackGestureAttempted(mEdgeBackPanel.getIsLeftPanel()
+ ? BackGestureResult.BACK_COMPLETED_FROM_LEFT
+ : BackGestureResult.BACK_COMPLETED_FROM_RIGHT);
+ }
+ }
+
+ @Override
+ public void cancelBack() {
+ if (mGestureCallback != null) {
+ mGestureCallback.onBackGestureAttempted(mEdgeBackPanel.getIsLeftPanel()
+ ? BackGestureResult.BACK_CANCELLED_FROM_LEFT
+ : BackGestureResult.BACK_CANCELLED_FROM_RIGHT);
+ }
+ }
+ };
+
+ EdgeBackGestureHandler(Context context) {
+ final Resources res = context.getResources();
+ mContext = context;
+ mDisplayId = context.getDisplay() == null
+ ? Display.DEFAULT_DISPLAY : context.getDisplay().getDisplayId();
+
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
+ ViewConfiguration.getLongPressTimeout());
+
+ mBottomGestureHeight =
+ ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, res);
+ mEdgeWidth = ResourceUtils.getNavbarSize("config_backGestureInset", res);
+ }
+
+ void setIsEnabled(boolean isEnabled) {
+ if (isEnabled == mIsEnabled) {
+ return;
+ }
+ mIsEnabled = isEnabled;
+
+ if (mEdgeBackPanel != null) {
+ mEdgeBackPanel.onDestroy();
+ mEdgeBackPanel = null;
+ }
+
+ if (!mIsEnabled) {
+ mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+ } else {
+ updateDisplaySize();
+ mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
+ new Handler(Looper.getMainLooper()));
+
+ // Add a nav bar panel window.
+ mEdgeBackPanel = new EdgeBackGesturePanel(mContext);
+ mEdgeBackPanel.setBackCallback(mBackCallback);
+ mEdgeBackPanel.setLayoutParams(createLayoutParams());
+ updateDisplaySize();
+ }
+ }
+
+ void registerBackGestureAttemptCallback(BackGestureAttemptCallback callback) {
+ mGestureCallback = callback;
+ }
+
+ private WindowManager.LayoutParams createLayoutParams() {
+ Resources resources = mContext.getResources();
+ WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
+ ResourceUtils.getNavbarSize("navigation_edge_panel_width", resources),
+ ResourceUtils.getNavbarSize("navigation_edge_panel_height", resources),
+ LayoutParams.TYPE_APPLICATION_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+ PixelFormat.TRANSLUCENT);
+ layoutParams.setTitle(TAG + mDisplayId);
+ layoutParams.windowAnimations = 0;
+ layoutParams.setFitInsetsTypes(0 /* types */);
+ return layoutParams;
+ }
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ if (mIsEnabled) {
+ onMotionEvent(motionEvent);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isWithinTouchRegion(int x, int y) {
+ // Disallow if too far from the edge
+ if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {
+ return false;
+ }
+
+ // Disallow if we are in the bottom gesture area
+ if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void cancelGesture(MotionEvent ev) {
+ // Send action cancel to reset all the touch events
+ mAllowGesture = false;
+ MotionEvent cancelEv = MotionEvent.obtain(ev);
+ cancelEv.setAction(MotionEvent.ACTION_CANCEL);
+ mEdgeBackPanel.onMotionEvent(cancelEv);
+ cancelEv.recycle();
+ }
+
+ private void onMotionEvent(MotionEvent ev) {
+ int action = ev.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ boolean isOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset;
+ mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
+ if (mAllowGesture) {
+ mEdgeBackPanel.setIsLeftPanel(isOnLeftEdge);
+ mEdgeBackPanel.onMotionEvent(ev);
+
+ mDownPoint.set(ev.getX(), ev.getY());
+ mThresholdCrossed = false;
+ }
+ } else if (mAllowGesture) {
+ if (!mThresholdCrossed) {
+ if (action == MotionEvent.ACTION_POINTER_DOWN) {
+ // We do not support multi touch for back gesture
+ cancelGesture(ev);
+ return;
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) {
+ cancelGesture(ev);
+ return;
+ }
+ float dx = Math.abs(ev.getX() - mDownPoint.x);
+ float dy = Math.abs(ev.getY() - mDownPoint.y);
+ if (dy > dx && dy > mTouchSlop) {
+ cancelGesture(ev);
+ return;
+
+ } else if (dx > dy && dx > mTouchSlop) {
+ mThresholdCrossed = true;
+ }
+ }
+
+ }
+
+ // forward touch
+ mEdgeBackPanel.onMotionEvent(ev);
+ }
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ if (!mAllowGesture && mGestureCallback != null) {
+ mGestureCallback.onBackGestureAttempted(BackGestureResult.BACK_NOT_STARTED);
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) { }
+
+ @Override
+ public void onDisplayRemoved(int displayId) { }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == mDisplayId) {
+ updateDisplaySize();
+ }
+ }
+
+ private void updateDisplaySize() {
+ mContext.getDisplay().getRealSize(mDisplaySize);
+ if (mEdgeBackPanel != null) {
+ mEdgeBackPanel.setDisplaySize(mDisplaySize);
+ }
+ }
+
+ void setInsets(int leftInset, int rightInset) {
+ mLeftInset = leftInset;
+ mRightInset = rightInset;
+ }
+
+ enum BackGestureResult {
+ UNKNOWN,
+ BACK_COMPLETED_FROM_LEFT,
+ BACK_COMPLETED_FROM_RIGHT,
+ BACK_CANCELLED_FROM_LEFT,
+ BACK_CANCELLED_FROM_RIGHT,
+ BACK_NOT_STARTED,
+ }
+
+ /** Callback to let the UI react to attempted back gestures. */
+ interface BackGestureAttemptCallback {
+ /** Called whenever any touch is completed. */
+ void onBackGestureAttempted(BackGestureResult result);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
new file mode 100644
index 0000000..34eeafc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -0,0 +1,701 @@
+/*
+ * Copyright (C) 2019 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.quickstep.interaction;
+
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import androidx.core.math.MathUtils;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.util.VibratorWrapper;
+
+/** Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java. */
+public class EdgeBackGesturePanel extends View {
+
+ private static final String LOG_TAG = "EdgeBackGesturePanel";
+
+ private static final long DISAPPEAR_FADE_ANIMATION_DURATION_MS = 80;
+ private static final long DISAPPEAR_ARROW_ANIMATION_DURATION_MS = 100;
+
+ /**
+ * The time required since the first vibration effect to automatically trigger a click
+ */
+ private static final int GESTURE_DURATION_FOR_CLICK_MS = 400;
+
+ /**
+ * The basic translation in dp where the arrow resides
+ */
+ private static final int BASE_TRANSLATION_DP = 32;
+
+ /**
+ * The length of the arrow leg measured from the center to the end
+ */
+ private static final int ARROW_LENGTH_DP = 18;
+
+ /**
+ * The angle measured from the xAxis, where the leg is when the arrow rests
+ */
+ private static final int ARROW_ANGLE_WHEN_EXTENDED_DEGREES = 56;
+
+ /**
+ * The angle that is added per 1000 px speed to the angle of the leg
+ */
+ private static final int ARROW_ANGLE_ADDED_PER_1000_SPEED = 4;
+
+ /**
+ * The maximum angle offset allowed due to speed
+ */
+ private static final int ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES = 4;
+
+ /**
+ * The thickness of the arrow. Adjusted to match the home handle (approximately)
+ */
+ private static final float ARROW_THICKNESS_DP = 2.5f;
+
+ /**
+ * The amount of rubber banding we do for the vertical translation
+ */
+ private static final int RUBBER_BAND_AMOUNT = 15;
+
+ /**
+ * The interpolator used to rubberband
+ */
+ private static final Interpolator RUBBER_BAND_INTERPOLATOR =
+ new PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f);
+
+ /**
+ * The amount of rubber banding we do for the translation before base translation
+ */
+ private static final int RUBBER_BAND_AMOUNT_APPEAR = 4;
+
+ /**
+ * The interpolator used to rubberband the appearing of the arrow.
+ */
+ private static final Interpolator RUBBER_BAND_INTERPOLATOR_APPEAR =
+ new PathInterpolator(1.0f / RUBBER_BAND_AMOUNT_APPEAR, 1.0f, 1.0f, 1.0f);
+
+ private final WindowManager mWindowManager;
+ private BackCallback mBackCallback;
+
+ /**
+ * The paint the arrow is drawn with
+ */
+ private final Paint mPaint = new Paint();
+
+ private final float mDensity;
+ private final float mBaseTranslation;
+ private final float mArrowLength;
+ private final float mArrowThickness;
+
+ /**
+ * The minimum delta needed in movement for the arrow to change direction / stop triggering back
+ */
+ private final float mMinDeltaForSwitch;
+ // The closest to y = 0 that the arrow will be displayed.
+ private int mMinArrowPosition;
+ // The amount the arrow is shifted to avoid the finger.
+ private int mFingerOffset;
+
+ private final float mSwipeThreshold;
+ private final Path mArrowPath = new Path();
+ private final Point mDisplaySize = new Point();
+
+ private final SpringAnimation mAngleAnimation;
+ private final SpringAnimation mTranslationAnimation;
+ private final SpringAnimation mVerticalTranslationAnimation;
+ private final SpringForce mAngleAppearForce;
+ private final SpringForce mAngleDisappearForce;
+ private final ValueAnimator mArrowDisappearAnimation;
+ private final SpringForce mRegularTranslationSpring;
+ private final SpringForce mTriggerBackSpring;
+
+ private VelocityTracker mVelocityTracker;
+ private int mArrowPaddingEnd;
+ private WindowManager.LayoutParams mLayoutParams;
+
+ /**
+ * True if the panel is currently on the left of the screen
+ */
+ private boolean mIsLeftPanel;
+
+ private float mStartX;
+ private float mStartY;
+ private float mCurrentAngle;
+ /**
+ * The current translation of the arrow
+ */
+ private float mCurrentTranslation;
+ /**
+ * Where the arrow will be in the resting position.
+ */
+ private float mDesiredTranslation;
+
+ private boolean mDragSlopPassed;
+ private boolean mArrowsPointLeft;
+ private float mMaxTranslation;
+ private boolean mTriggerBack;
+ private float mPreviousTouchTranslation;
+ private float mTotalTouchDelta;
+ private float mVerticalTranslation;
+ private float mDesiredVerticalTranslation;
+ private float mDesiredAngle;
+ private float mAngleOffset;
+ private float mDisappearAmount;
+ private long mVibrationTime;
+ private int mScreenSize;
+
+ private final DynamicAnimation.OnAnimationEndListener mSetGoneEndListener =
+ new DynamicAnimation.OnAnimationEndListener() {
+ @Override
+ public void onAnimationEnd(
+ DynamicAnimation animation, boolean canceled, float value, float velocity) {
+ animation.removeEndListener(this);
+ if (!canceled) {
+ setVisibility(GONE);
+ }
+ }
+ };
+
+ private static final FloatPropertyCompat<EdgeBackGesturePanel> CURRENT_ANGLE =
+ new FloatPropertyCompat<EdgeBackGesturePanel>("currentAngle") {
+ @Override
+ public void setValue(EdgeBackGesturePanel object, float value) {
+ object.setCurrentAngle(value);
+ }
+
+ @Override
+ public float getValue(EdgeBackGesturePanel object) {
+ return object.getCurrentAngle();
+ }
+ };
+
+ private static final FloatPropertyCompat<EdgeBackGesturePanel> CURRENT_TRANSLATION =
+ new FloatPropertyCompat<EdgeBackGesturePanel>("currentTranslation") {
+ @Override
+ public void setValue(EdgeBackGesturePanel object, float value) {
+ object.setCurrentTranslation(value);
+ }
+
+ @Override
+ public float getValue(EdgeBackGesturePanel object) {
+ return object.getCurrentTranslation();
+ }
+ };
+
+ private static final FloatPropertyCompat<EdgeBackGesturePanel> CURRENT_VERTICAL_TRANSLATION =
+ new FloatPropertyCompat<EdgeBackGesturePanel>("verticalTranslation") {
+
+ @Override
+ public void setValue(EdgeBackGesturePanel object, float value) {
+ object.setVerticalTranslation(value);
+ }
+
+ @Override
+ public float getValue(EdgeBackGesturePanel object) {
+ return object.getVerticalTranslation();
+ }
+ };
+
+ public EdgeBackGesturePanel(Context context) {
+ super(context);
+
+ mWindowManager = context.getSystemService(WindowManager.class);
+
+ mDensity = context.getResources().getDisplayMetrics().density;
+
+ mBaseTranslation = dp(BASE_TRANSLATION_DP);
+ mArrowLength = dp(ARROW_LENGTH_DP);
+ mArrowThickness = dp(ARROW_THICKNESS_DP);
+ mMinDeltaForSwitch = dp(32);
+
+ mPaint.setStrokeWidth(mArrowThickness);
+ mPaint.setStrokeCap(Paint.Cap.ROUND);
+ mPaint.setAntiAlias(true);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeJoin(Paint.Join.ROUND);
+
+ mArrowDisappearAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mArrowDisappearAnimation.setDuration(DISAPPEAR_ARROW_ANIMATION_DURATION_MS);
+ mArrowDisappearAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mArrowDisappearAnimation.addUpdateListener(animation -> {
+ mDisappearAmount = (float) animation.getAnimatedValue();
+ invalidate();
+ });
+
+ mAngleAnimation =
+ new SpringAnimation(this, CURRENT_ANGLE);
+ mAngleAppearForce = new SpringForce()
+ .setStiffness(500)
+ .setDampingRatio(0.5f);
+ mAngleDisappearForce = new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
+ .setFinalPosition(90);
+ mAngleAnimation.setSpring(mAngleAppearForce).setMaxValue(90);
+
+ mTranslationAnimation =
+ new SpringAnimation(this, CURRENT_TRANSLATION);
+ mRegularTranslationSpring = new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+ mTriggerBackSpring = new SpringForce()
+ .setStiffness(450)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+ mTranslationAnimation.setSpring(mRegularTranslationSpring);
+ mVerticalTranslationAnimation =
+ new SpringAnimation(this, CURRENT_VERTICAL_TRANSLATION);
+ mVerticalTranslationAnimation.setSpring(
+ new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
+ mPaint.setColor(context.getColor(R.color.back_arrow_color_dark));
+ loadDimens();
+ updateArrowDirection();
+
+ mSwipeThreshold = ResourceUtils.getDimenByName(
+ "navigation_edge_action_drag_threshold", context.getResources(), 16 /* defaultValue */);
+ setVisibility(GONE);
+ }
+
+ void onDestroy() {
+ mWindowManager.removeView(this);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ @SuppressLint("RtlHardcoded")
+ void setIsLeftPanel(boolean isLeftPanel) {
+ mIsLeftPanel = isLeftPanel;
+ mLayoutParams.gravity = mIsLeftPanel
+ ? (Gravity.LEFT | Gravity.TOP)
+ : (Gravity.RIGHT | Gravity.TOP);
+ }
+
+ boolean getIsLeftPanel() {
+ return mIsLeftPanel;
+ }
+
+ void setDisplaySize(Point displaySize) {
+ mDisplaySize.set(displaySize.x, displaySize.y);
+ mScreenSize = Math.min(mDisplaySize.x, mDisplaySize.y);
+ }
+
+ void setBackCallback(BackCallback callback) {
+ mBackCallback = callback;
+ }
+
+ void setLayoutParams(WindowManager.LayoutParams layoutParams) {
+ mLayoutParams = layoutParams;
+ mWindowManager.addView(this, mLayoutParams);
+ }
+
+ private float getCurrentAngle() {
+ return mCurrentAngle;
+ }
+
+ private float getCurrentTranslation() {
+ return mCurrentTranslation;
+ }
+
+ void onMotionEvent(MotionEvent event) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mDragSlopPassed = false;
+ resetOnDown();
+ mStartX = event.getX();
+ mStartY = event.getY();
+ setVisibility(VISIBLE);
+ updatePosition(event.getY());
+ mWindowManager.updateViewLayout(this, mLayoutParams);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ handleMoveEvent(event);
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mTriggerBack) {
+ triggerBack();
+ } else {
+ cancelBack();
+ }
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ cancelBack();
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ break;
+ }
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateArrowDirection();
+ loadDimens();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ float pointerPosition = mCurrentTranslation - mArrowThickness / 2.0f;
+ canvas.save();
+ canvas.translate(
+ mIsLeftPanel ? pointerPosition : getWidth() - pointerPosition,
+ (getHeight() * 0.5f) + mVerticalTranslation);
+
+ // Let's calculate the position of the end based on the angle
+ float x = (polarToCartX(mCurrentAngle) * mArrowLength);
+ float y = (polarToCartY(mCurrentAngle) * mArrowLength);
+ Path arrowPath = calculatePath(x, y);
+
+ canvas.drawPath(arrowPath, mPaint);
+ canvas.restore();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ mMaxTranslation = getWidth() - mArrowPaddingEnd;
+ }
+
+ private void loadDimens() {
+ Resources res = getResources();
+ mArrowPaddingEnd = ResourceUtils.getDimenByName("navigation_edge_panel_padding", res, 8);
+ mMinArrowPosition = ResourceUtils.getDimenByName("navigation_edge_arrow_min_y", res, 64);
+ mFingerOffset = ResourceUtils.getDimenByName("navigation_edge_finger_offset", res, 48);
+ }
+
+ private void updateArrowDirection() {
+ // Both panels arrow point the same way
+ mArrowsPointLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR;
+ invalidate();
+ }
+
+ private float getStaticArrowWidth() {
+ return polarToCartX(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength;
+ }
+
+ private float polarToCartX(float angleInDegrees) {
+ return (float) Math.cos(Math.toRadians(angleInDegrees));
+ }
+
+ private float polarToCartY(float angleInDegrees) {
+ return (float) Math.sin(Math.toRadians(angleInDegrees));
+ }
+
+ private Path calculatePath(float x, float y) {
+ if (!mArrowsPointLeft) {
+ x = -x;
+ }
+ float extent = lerp(1.0f, 0.75f, mDisappearAmount);
+ x = x * extent;
+ y = y * extent;
+ mArrowPath.reset();
+ mArrowPath.moveTo(x, y);
+ mArrowPath.lineTo(0, 0);
+ mArrowPath.lineTo(x, -y);
+ return mArrowPath;
+ }
+
+ private static float lerp(float start, float stop, float amount) {
+ return start + (stop - start) * amount;
+ }
+
+ private void triggerBack() {
+ if (mBackCallback != null) {
+ mBackCallback.triggerBack();
+ }
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.computeCurrentVelocity(1000);
+ // Only do the extra translation if we're not already flinging
+ boolean isSlow = Math.abs(mVelocityTracker.getXVelocity()) < 500;
+ if (isSlow
+ || SystemClock.uptimeMillis() - mVibrationTime >= GESTURE_DURATION_FOR_CLICK_MS) {
+ VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK);
+ }
+
+ // Let's also snap the angle a bit
+ if (mAngleOffset > -4) {
+ mAngleOffset = Math.max(-8, mAngleOffset - 8);
+ updateAngle(true /* animated */);
+ }
+
+ // Finally, after the translation, animate back and disappear the arrow
+ Runnable translationEnd = () -> {
+ // let's snap it back
+ mAngleOffset = Math.max(0, mAngleOffset + 8);
+ updateAngle(true /* animated */);
+
+ mTranslationAnimation.setSpring(mTriggerBackSpring);
+ // Translate the arrow back a bit to make for a nice transition
+ setDesiredTranslation(mDesiredTranslation - dp(32), true /* animated */);
+ animate().alpha(0f).setDuration(DISAPPEAR_FADE_ANIMATION_DURATION_MS)
+ .withEndAction(() -> setVisibility(GONE));
+ mArrowDisappearAnimation.start();
+ };
+ if (mTranslationAnimation.isRunning()) {
+ mTranslationAnimation.addEndListener(new DynamicAnimation.OnAnimationEndListener() {
+ @Override
+ public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
+ float value,
+ float velocity) {
+ animation.removeEndListener(this);
+ if (!canceled) {
+ translationEnd.run();
+ }
+ }
+ });
+ } else {
+ translationEnd.run();
+ }
+ }
+
+ private void cancelBack() {
+ if (mBackCallback != null) {
+ mBackCallback.cancelBack();
+ }
+
+ if (mTranslationAnimation.isRunning()) {
+ mTranslationAnimation.addEndListener(mSetGoneEndListener);
+ } else {
+ setVisibility(GONE);
+ }
+ }
+
+ private void resetOnDown() {
+ animate().cancel();
+ mAngleAnimation.cancel();
+ mTranslationAnimation.cancel();
+ mVerticalTranslationAnimation.cancel();
+ mArrowDisappearAnimation.cancel();
+ mAngleOffset = 0;
+ mTranslationAnimation.setSpring(mRegularTranslationSpring);
+ // Reset the arrow to the side
+ setTriggerBack(false /* triggerBack */, false /* animated */);
+ setDesiredTranslation(0, false /* animated */);
+ setCurrentTranslation(0);
+ updateAngle(false /* animate */);
+ mPreviousTouchTranslation = 0;
+ mTotalTouchDelta = 0;
+ mVibrationTime = 0;
+ setDesiredVerticalTransition(0, false /* animated */);
+ }
+
+ private void handleMoveEvent(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ float touchTranslation = Math.abs(x - mStartX);
+ float yOffset = y - mStartY;
+ float delta = touchTranslation - mPreviousTouchTranslation;
+ if (Math.abs(delta) > 0) {
+ if (Math.signum(delta) == Math.signum(mTotalTouchDelta)) {
+ mTotalTouchDelta += delta;
+ } else {
+ mTotalTouchDelta = delta;
+ }
+ }
+ mPreviousTouchTranslation = touchTranslation;
+
+ // Apply a haptic on drag slop passed
+ if (!mDragSlopPassed && touchTranslation > mSwipeThreshold) {
+ mDragSlopPassed = true;
+ VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK);
+ mVibrationTime = SystemClock.uptimeMillis();
+
+ // Let's show the arrow and animate it in!
+ mDisappearAmount = 0.0f;
+ setAlpha(1f);
+ // And animate it go to back by default!
+ setTriggerBack(true /* triggerBack */, true /* animated */);
+ }
+
+ // Let's make sure we only go to the baseextend and apply rubberbanding afterwards
+ if (touchTranslation > mBaseTranslation) {
+ float diff = touchTranslation - mBaseTranslation;
+ float progress = MathUtils.clamp(diff / (mScreenSize - mBaseTranslation), 0, 1);
+ progress = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
+ * (mMaxTranslation - mBaseTranslation);
+ touchTranslation = mBaseTranslation + progress;
+ } else {
+ float diff = mBaseTranslation - touchTranslation;
+ float progress = MathUtils.clamp(diff / mBaseTranslation, 0, 1);
+ progress = RUBBER_BAND_INTERPOLATOR_APPEAR.getInterpolation(progress)
+ * (mBaseTranslation / RUBBER_BAND_AMOUNT_APPEAR);
+ touchTranslation = mBaseTranslation - progress;
+ }
+ // By default we just assume the current direction is kept
+ boolean triggerBack = mTriggerBack;
+
+ // First lets see if we had continuous motion in one direction for a while
+ if (Math.abs(mTotalTouchDelta) > mMinDeltaForSwitch) {
+ triggerBack = mTotalTouchDelta > 0;
+ }
+
+ // Then, let's see if our velocity tells us to change direction
+ mVelocityTracker.computeCurrentVelocity(1000);
+ float xVelocity = mVelocityTracker.getXVelocity();
+ float yVelocity = mVelocityTracker.getYVelocity();
+ float velocity = (float) Math.hypot(xVelocity, yVelocity);
+ mAngleOffset = Math.min(velocity / 1000 * ARROW_ANGLE_ADDED_PER_1000_SPEED,
+ ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES) * Math.signum(xVelocity);
+ if (mIsLeftPanel && mArrowsPointLeft || !mIsLeftPanel && !mArrowsPointLeft) {
+ mAngleOffset *= -1;
+ }
+
+ // Last if the direction in Y is bigger than X * 2 we also abort
+ if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) {
+ triggerBack = false;
+ }
+ setTriggerBack(triggerBack, true /* animated */);
+
+ if (!mTriggerBack) {
+ touchTranslation = 0;
+ } else if (mIsLeftPanel && mArrowsPointLeft
+ || (!mIsLeftPanel && !mArrowsPointLeft)) {
+ // If we're on the left we should move less, because the arrow is facing the other
+ // direction
+ touchTranslation -= getStaticArrowWidth();
+ }
+ setDesiredTranslation(touchTranslation, true /* animated */);
+ updateAngle(true /* animated */);
+
+ float maxYOffset = getHeight() / 2.0f - mArrowLength;
+ float progress =
+ MathUtils.clamp(Math.abs(yOffset) / (maxYOffset * RUBBER_BAND_AMOUNT), 0, 1);
+ float verticalTranslation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
+ * maxYOffset * Math.signum(yOffset);
+ setDesiredVerticalTransition(verticalTranslation, true /* animated */);
+ }
+
+ private void updatePosition(float touchY) {
+ float position = touchY - mFingerOffset;
+ position = Math.max(position, mMinArrowPosition);
+ position -= mLayoutParams.height / 2.0f;
+ mLayoutParams.y = MathUtils.clamp((int) position, 0, mDisplaySize.y);
+ }
+
+ private void setDesiredVerticalTransition(float verticalTranslation, boolean animated) {
+ if (mDesiredVerticalTranslation != verticalTranslation) {
+ mDesiredVerticalTranslation = verticalTranslation;
+ if (!animated) {
+ setVerticalTranslation(verticalTranslation);
+ } else {
+ mVerticalTranslationAnimation.animateToFinalPosition(verticalTranslation);
+ }
+ invalidate();
+ }
+ }
+
+ private void setVerticalTranslation(float verticalTranslation) {
+ mVerticalTranslation = verticalTranslation;
+ invalidate();
+ }
+
+ private float getVerticalTranslation() {
+ return mVerticalTranslation;
+ }
+
+ private void setDesiredTranslation(float desiredTranslation, boolean animated) {
+ if (mDesiredTranslation != desiredTranslation) {
+ mDesiredTranslation = desiredTranslation;
+ if (!animated) {
+ setCurrentTranslation(desiredTranslation);
+ } else {
+ mTranslationAnimation.animateToFinalPosition(desiredTranslation);
+ }
+ }
+ }
+
+ private void setCurrentTranslation(float currentTranslation) {
+ mCurrentTranslation = currentTranslation;
+ invalidate();
+ }
+
+ private void setTriggerBack(boolean triggerBack, boolean animated) {
+ if (mTriggerBack != triggerBack) {
+ mTriggerBack = triggerBack;
+ mAngleAnimation.cancel();
+ updateAngle(animated);
+ // Whenever the trigger back state changes the existing translation animation should be
+ // cancelled
+ mTranslationAnimation.cancel();
+ }
+ }
+
+ private void updateAngle(boolean animated) {
+ float newAngle = mTriggerBack ? ARROW_ANGLE_WHEN_EXTENDED_DEGREES + mAngleOffset : 90;
+ if (newAngle != mDesiredAngle) {
+ if (!animated) {
+ setCurrentAngle(newAngle);
+ } else {
+ mAngleAnimation.setSpring(mTriggerBack ? mAngleAppearForce : mAngleDisappearForce);
+ mAngleAnimation.animateToFinalPosition(newAngle);
+ }
+ mDesiredAngle = newAngle;
+ }
+ }
+
+ private void setCurrentAngle(float currentAngle) {
+ mCurrentAngle = currentAngle;
+ invalidate();
+ }
+
+ private float dp(float dp) {
+ return mDensity * dp;
+ }
+
+ /** Callback to let the gesture handler react to the detected back gestures. */
+ interface BackCallback {
+ /** Indicates that a Back gesture was recognized. */
+ void triggerBack();
+
+ /** Indicates that the gesture was cancelled. */
+ void cancelBack();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 8081ad7..4815366 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -30,12 +30,11 @@
import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
import java.util.List;
-import java.util.Optional;
/** Shows the gesture interactive sandbox in full screen mode. */
public class GestureSandboxActivity extends FragmentActivity {
- Optional<BackGestureTutorialFragment> mFragment = Optional.empty();
+ private BackGestureTutorialFragment mFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -43,10 +42,10 @@
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.back_gesture_tutorial_activity);
- mFragment = Optional.of(BackGestureTutorialFragment.newInstance(TutorialStep.ENGAGED,
- TutorialType.RIGHT_EDGE_BACK_NAVIGATION));
+ mFragment = BackGestureTutorialFragment.newInstance(
+ TutorialStep.ENGAGED, TutorialType.RIGHT_EDGE_BACK_NAVIGATION);
getSupportFragmentManager().beginTransaction()
- .add(R.id.back_gesture_tutorial_fragment_container, mFragment.get())
+ .add(R.id.back_gesture_tutorial_fragment_container, mFragment)
.commit();
}
@@ -54,6 +53,13 @@
public void onAttachedToWindow() {
super.onAttachedToWindow();
disableSystemGestures();
+ mFragment.onAttachedToWindow();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mFragment.onDetachedFromWindow();
}
@Override
@@ -64,13 +70,6 @@
}
}
- @Override
- public void onBackPressed() {
- if (mFragment.isPresent()) {
- mFragment.get().onBackPressed();
- }
- }
-
private void hideSystemUI() {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
diff --git a/res/layout/work_mode_switch.xml b/res/layout/work_mode_switch.xml
new file mode 100644
index 0000000..9cb7ce8
--- /dev/null
+++ b/res/layout/work_mode_switch.xml
@@ -0,0 +1,38 @@
+<?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.allapps.WorkModeSwitch
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/PrimaryMediumText"
+ android:id="@+id/work_mode_toggle"
+ android:drawableStart="@drawable/ic_corp"
+ android:drawablePadding="16dp"
+ android:drawableTint="?attr/workProfileOverlayTextColor"
+ android:textColor="?attr/workProfileOverlayTextColor"
+ android:layout_alignParentBottom="true"
+ android:ellipsize="end"
+ android:gravity="start"
+ android:lines="1"
+ android:showText="false"
+ android:textSize="16sp"
+ android:background="?attr/allAppsScrimColor"
+ android:text="@string/work_profile_toggle_label"
+ android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding"
+ android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding"
+ android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding"
+ android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding"
+/>
diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
deleted file mode 100644
index 264e273..0000000
--- a/res/layout/work_tab_footer.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?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.views.WorkFooterContainer
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/work_toggle_container"
- android:focusable="true"
- android:orientation="horizontal"
- android:background="?attr/allAppsScrimColor"
- android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding"
- android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding"
- android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding"
- android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding">
-
- <TextView
- style="@style/PrimaryMediumText"
- android:id="@+id/work_mode_label"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:drawableStart="@drawable/ic_corp"
- android:drawablePadding="16dp"
- android:drawableTint="?attr/workProfileOverlayTextColor"
- android:textColor="?attr/workProfileOverlayTextColor"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:gravity="center_vertical"
- android:lines="1"
- android:minHeight="24dp"
- android:paddingEnd="12dp"
- android:text="@string/work_profile_toggle_label"
- android:textSize="16sp"/>
- <com.android.launcher3.allapps.WorkModeSwitch
- android:id="@+id/work_mode_toggle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
-</com.android.launcher3.views.WorkFooterContainer>
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index e6f8a85..21a8fd4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -46,6 +46,7 @@
import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.graphics.IconShape;
@@ -65,7 +66,7 @@
* too aggressive.
*/
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
- IconLabelDotView {
+ IconLabelDotView, DraggableView {
private static final int DISPLAY_WORKSPACE = 0;
private static final int DISPLAY_ALL_APPS = 1;
@@ -103,7 +104,7 @@
private final ActivityContext mActivity;
private Drawable mIcon;
- private final boolean mCenterVertically;
+ private boolean mCenterVertically;
private final int mDisplay;
@@ -701,4 +702,24 @@
public int getIconSize() {
return mIconSize;
}
+
+ @Override
+ public int getViewType() {
+ return DRAGGABLE_ICON;
+ }
+
+ @Override
+ public void getVisualDragBounds(Rect bounds) {
+ DeviceProfile grid = mActivity.getDeviceProfile();
+ BubbleTextView.getIconBounds(this, bounds, grid.iconSizePx);
+ }
+
+ @Override
+ public void prepareDrawDragView() {
+ if (getIcon() instanceof FastBitmapDrawable) {
+ FastBitmapDrawable icon = (FastBitmapDrawable) getIcon();
+ icon.setScale(1f);
+ }
+ setForceHideDot(true);
+ }
}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index f2d07f2..9682d09 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -56,6 +56,7 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.graphics.RotationMode;
@@ -100,6 +101,11 @@
@Thunk final int[] mTmpPoint = new int[2];
@Thunk final int[] mTempLocation = new int[2];
+ // Used to visualize / debug the Grid of the CellLayout
+ private static final boolean VISUALIZE_GRID = false;
+ private Rect mVisualizeGridRect = new Rect();
+ private Paint mVisualizeGridPaint = new Paint();
+
private GridOccupancy mOccupied;
private GridOccupancy mTmpOccupied;
@@ -462,6 +468,37 @@
mFolderLeaveBehind.drawLeaveBehind(canvas);
canvas.restore();
}
+
+ if (VISUALIZE_GRID) {
+ visualizeGrid(canvas);
+ }
+ }
+
+ protected void visualizeGrid(Canvas canvas) {
+ mVisualizeGridRect.set(0, 0, mCellWidth, mCellHeight);
+ mVisualizeGridPaint.setStrokeWidth(4);
+
+ for (int i = 0; i < mCountX; i++) {
+ for (int j = 0; j < mCountY; j++) {
+ canvas.save();
+
+ int transX = i * mCellWidth;
+ int transY = j * mCellHeight;
+
+ canvas.translate(getPaddingLeft() + transX, getPaddingTop() + transY);
+
+ mVisualizeGridPaint.setStyle(Paint.Style.FILL);
+ mVisualizeGridPaint.setColor(Color.argb(80, 255, 100, 100));
+
+ canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint);
+
+ mVisualizeGridPaint.setStyle(Paint.Style.STROKE);
+ mVisualizeGridPaint.setColor(Color.argb(255, 255, 100, 100));
+
+ canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint);
+ canvas.restore();
+ }
+ }
}
@Override
@@ -928,8 +965,8 @@
return false;
}
- void visualizeDropLocation(View v, DragPreviewProvider outlineProvider, int cellX, int cellY,
- int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
+ void visualizeDropLocation(DraggableView v, DragPreviewProvider outlineProvider, int cellX, int
+ cellY, int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
final int oldDragCellX = mDragCell[0];
final int oldDragCellY = mDragCell[1];
@@ -939,9 +976,6 @@
Bitmap dragOutline = outlineProvider.generatedDragOutline;
if (cellX != oldDragCellX || cellY != oldDragCellY) {
- Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
- Rect dragRegion = dragObject.dragView.getDragRegion();
-
mDragCell[0] = cellX;
mDragCell[1] = cellY;
@@ -950,50 +984,27 @@
mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Rect r = mDragOutlines[mDragOutlineCurrent];
+ cellToRect(cellX, cellY, spanX, spanY, r);
+ int left = r.left;
+ int top = r.top;
+
+ int width = dragOutline.getWidth();
+ int height = dragOutline.getHeight();
+
if (resize) {
- cellToRect(cellX, cellY, spanX, spanY, r);
- if (v instanceof LauncherAppWidgetHostView) {
- DeviceProfile profile = mActivity.getWallpaperDeviceProfile();
- Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
- }
- } else {
- // Find the top left corner of the rect the object will occupy
- final int[] topLeft = mTmpPoint;
- cellToPoint(cellX, cellY, topLeft);
-
- int left = topLeft[0];
- int top = topLeft[1];
-
- if (v != null && dragOffset == null) {
- // When drawing the drag outline, it did not account for margin offsets
- // added by the view's parent.
- MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
- left += lp.leftMargin;
- top += lp.topMargin;
-
- // Offsets due to the size difference between the View and the dragOutline.
- // There is a size difference to account for the outer blur, which may lie
- // outside the bounds of the view.
- top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
- // We center about the x axis
- left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
- } else {
- if (dragOffset != null && dragRegion != null) {
- // Center the drag region *horizontally* in the cell and apply a drag
- // outline offset
- left += dragOffset.x + ((mCellWidth * spanX) - dragRegion.width()) / 2;
- int cHeight = getShortcutsAndWidgets().getCellContentHeight();
- int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
- top += dragOffset.y + cellPaddingY;
- } else {
- // Center the drag outline in the cell
- left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
- top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
- }
- }
- r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
+ width = r.width();
+ height = r.height();
}
+ if (v != null && v.getViewType() == DraggableView.DRAGGABLE_ICON) {
+ left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
+ int cHeight = getShortcutsAndWidgets().getCellContentHeight();
+ int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
+ top += cellPaddingY;
+ }
+
+ r.set(left, top, left + width, top + height);
+
Utilities.scaleRectAboutCenter(r, mChildScale);
mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
mDragOutlineAnims[mDragOutlineCurrent].animateIn();
@@ -1879,7 +1890,7 @@
// This method starts or changes the reorder preview animations
private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
- View dragView, int delay, int mode) {
+ View dragView, int mode) {
int childCount = mShortcutsAndWidgets.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = mShortcutsAndWidgets.getChildAt(i);
@@ -1946,6 +1957,8 @@
this.child = child;
this.mode = mode;
+
+ // TODO issue!
setInitialAnimationValues(false);
finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
finalDeltaX = initDeltaX;
@@ -2141,6 +2154,8 @@
*/
private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
int spanY, View dragView, int[] resultDirection) {
+
+ //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
int[] targetDestination = new int[2];
findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
@@ -2251,7 +2266,7 @@
setItemPlacementDirty(false);
} else {
beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
- REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
+ ReorderPreviewAnimation.MODE_PREVIEW);
}
mShortcutsAndWidgets.requestLayout();
}
@@ -2305,7 +2320,7 @@
if (mode == MODE_SHOW_REORDER_HINT) {
if (finalSolution != null) {
- beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
+ beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
ReorderPreviewAnimation.MODE_HINT);
result[0] = finalSolution.cellX;
result[1] = finalSolution.cellY;
@@ -2345,7 +2360,7 @@
setItemPlacementDirty(false);
} else {
beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
- REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
+ ReorderPreviewAnimation.MODE_PREVIEW);
}
}
} else {
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index c03011b..ef02e87 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -23,6 +23,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.FolderNameProvider;
/**
@@ -69,6 +70,10 @@
public FolderNameProvider folderNameProvider;
+ /** The source view (ie. icon, widget etc.) that is being dragged and which the
+ * DragView represents. May be an actual View class or a virtual stand-in */
+ public DraggableView originalView = null;
+
public DragObject(Context context) {
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
folderNameProvider = FolderNameProvider.newInstance(context);
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 1bd8263..c07dd9d 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -145,38 +145,46 @@
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-
- if (child instanceof LauncherAppWidgetHostView) {
- LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
-
- // Scale and center the widget to fit within its cells.
- DeviceProfile profile = mActivity.getDeviceProfile();
- float scaleX = profile.appWidgetScale.x;
- float scaleY = profile.appWidgetScale.y;
-
- lahv.setScaleToFit(Math.min(scaleX, scaleY));
- lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
- -(lp.height - (lp.height * scaleY)) / 2.0f);
- }
-
- int childLeft = lp.x;
- int childTop = lp.y;
- child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
-
- if (lp.dropped) {
- lp.dropped = false;
-
- final int[] cellXY = mTmpCellXY;
- getLocationOnScreen(cellXY);
- mWallpaperManager.sendWallpaperCommand(getWindowToken(),
- WallpaperManager.COMMAND_DROP,
- cellXY[0] + childLeft + lp.width / 2,
- cellXY[1] + childTop + lp.height / 2, 0, null);
- }
+ layoutChild(child);
}
}
}
+ /**
+ * Core logic to layout a child for this ViewGroup.
+ */
+ public void layoutChild(View child) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+ if (child instanceof LauncherAppWidgetHostView) {
+ LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
+
+ // Scale and center the widget to fit within its cells.
+ DeviceProfile profile = mActivity.getDeviceProfile();
+ float scaleX = profile.appWidgetScale.x;
+ float scaleY = profile.appWidgetScale.y;
+
+ lahv.setScaleToFit(Math.min(scaleX, scaleY));
+ lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
+ -(lp.height - (lp.height * scaleY)) / 2.0f);
+ }
+
+ int childLeft = lp.x;
+ int childTop = lp.y;
+ child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
+
+ if (lp.dropped) {
+ lp.dropped = false;
+
+ final int[] cellXY = mTmpCellXY;
+ getLocationOnScreen(cellXY);
+ mWallpaperManager.sendWallpaperCommand(getWindowToken(),
+ WallpaperManager.COMMAND_DROP,
+ cellXY[0] + childLeft + lp.width / 2,
+ cellXY[1] + childTop + lp.height / 2, 0, null);
+ }
+ }
+
+
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == ACTION_DOWN && getAlpha() == 0) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 46493b7..c42e480 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -70,6 +70,7 @@
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.dragndrop.SpringLoadedDragController;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
@@ -81,7 +82,6 @@
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.pageindicators.WorkspacePageIndicator;
import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.WorkspaceTouchListener;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -1452,12 +1452,17 @@
+ "View: " + child + " tag: " + child.getTag();
throw new IllegalStateException(msg);
}
- beginDragShared(child, source, (ItemInfo) dragObject,
+ beginDragShared(child, null, source, (ItemInfo) dragObject,
new DragPreviewProvider(child), options);
}
- public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
- DragPreviewProvider previewProvider, DragOptions dragOptions) {
+ /**
+ * Core functionality for beginning a drag operation for an item that will be dropped within
+ * the workspace
+ */
+ public DragView beginDragShared(View child, DraggableView draggableView, DragSource source,
+ ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) {
+
float iconScale = 1f;
if (child instanceof BubbleTextView) {
Drawable icon = ((BubbleTextView) child).getIcon();
@@ -1466,41 +1471,36 @@
}
}
+ // Clear the pressed state if necessary
child.clearFocus();
child.setPressed(false);
+ if (child instanceof BubbleTextView) {
+ BubbleTextView icon = (BubbleTextView) child;
+ icon.clearPressedBackground();
+ }
+
mOutlineProvider = previewProvider;
// The drag bitmap follows the touch point around on the screen
final Bitmap b = previewProvider.createDragBitmap();
int halfPadding = previewProvider.previewPadding / 2;
-
float scale = previewProvider.getScaleAndPosition(b, mTempXY);
int dragLayerX = mTempXY[0];
int dragLayerY = mTempXY[1];
- DeviceProfile grid = mLauncher.getDeviceProfile();
Point dragVisualizeOffset = null;
- Rect dragRect = null;
- if (child instanceof BubbleTextView) {
- dragRect = new Rect();
- BubbleTextView.getIconBounds(child, dragRect, grid.iconSizePx);
+ Rect dragRect = new Rect();
+
+ if (draggableView == null && child instanceof DraggableView) {
+ draggableView = (DraggableView) child;
+ }
+
+ if (draggableView != null) {
+ draggableView.getVisualDragBounds(dragRect);
dragLayerY += dragRect.top;
- // Note: The dragRect is used to calculate drag layer offsets, but the
- // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
- dragVisualizeOffset = new Point(- halfPadding, halfPadding);
- } else if (child instanceof FolderIcon) {
- int previewSize = grid.folderIconSizePx;
- dragVisualizeOffset = new Point(- halfPadding, halfPadding - child.getPaddingTop());
- dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
- } else if (previewProvider instanceof ShortcutDragPreviewProvider) {
dragVisualizeOffset = new Point(- halfPadding, halfPadding);
}
- // Clear the pressed state if necessary
- if (child instanceof BubbleTextView) {
- BubbleTextView icon = (BubbleTextView) child;
- icon.clearPressedBackground();
- }
if (child.getParent() instanceof ShortcutAndWidgetContainer) {
mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
@@ -1511,13 +1511,13 @@
.showForIcon((BubbleTextView) child);
if (popupContainer != null) {
dragOptions.preDragCondition = popupContainer.createPreDragCondition();
-
mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("dragging started");
}
}
- DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
- dragObject, dragVisualizeOffset, dragRect, scale * iconScale, scale, dragOptions);
+ DragView dv = mDragController.startDrag(b, draggableView, dragLayerX, dragLayerY, source,
+ dragObject, dragVisualizeOffset, dragRect, scale * iconScale,
+ scale, dragOptions);
dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
return dv;
}
@@ -2186,7 +2186,7 @@
item.spanY, child, mTargetCell);
if (!nearestDropOccupied) {
- mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
+ mDragTargetLayout.visualizeDropLocation(d.originalView, mOutlineProvider,
mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d);
} else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
&& !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
@@ -2279,7 +2279,8 @@
private void manageFolderFeedback(float distance, DragObject dragObject) {
if (distance > mMaxDistanceForFolderCreation) {
- if (mDragMode != DRAG_MODE_NONE) {
+ if ((mDragMode == DRAG_MODE_ADD_TO_FOLDER
+ || mDragMode == DRAG_MODE_CREATE_FOLDER)) {
setDragMode(DRAG_MODE_NONE);
}
return;
@@ -2368,7 +2369,7 @@
}
boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
- mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
+ mDragTargetLayout.visualizeDropLocation(dragObject.originalView, mOutlineProvider,
mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject);
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index e085ff0..10a3060 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -62,7 +62,6 @@
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
-import com.android.launcher3.views.WorkFooterContainer;
import java.util.ArrayList;
@@ -91,7 +90,7 @@
private AllAppsPagedView mViewPager;
private FloatingHeaderView mHeader;
- private WorkFooterContainer mWorkFooterContainer;
+ private WorkModeSwitch mWorkModeSwitch;
private SpannableStringBuilder mSearchQueryBuilder = null;
@@ -156,8 +155,8 @@
return mMultiValueAlpha.getProperty(index);
}
- public WorkFooterContainer getWorkFooterContainer() {
- return mWorkFooterContainer;
+ public WorkModeSwitch getWorkModeSwitch() {
+ return mWorkModeSwitch;
}
@@ -195,7 +194,7 @@
}
private void resetWorkProfile() {
- mWorkFooterContainer.refresh();
+ mWorkModeSwitch.refresh();
mAH[AdapterHolder.WORK].setupOverlay();
mAH[AdapterHolder.WORK].applyPadding();
}
@@ -410,9 +409,9 @@
} else {
mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
mAH[AdapterHolder.WORK].recyclerView = null;
- if (mWorkFooterContainer != null) {
- ((ViewGroup) mWorkFooterContainer.getParent()).removeView(mWorkFooterContainer);
- mWorkFooterContainer = null;
+ if (mWorkModeSwitch != null) {
+ ((ViewGroup) mWorkModeSwitch.getParent()).removeView(mWorkModeSwitch);
+ mWorkModeSwitch = null;
}
}
setupHeader();
@@ -422,14 +421,11 @@
}
private void setupWorkToggle() {
- mWorkFooterContainer = (WorkFooterContainer) mLauncher.getLayoutInflater().inflate(
- R.layout.work_tab_footer, findViewById(R.id.work_toggle_container));
- mWorkFooterContainer.setLayoutParams(
- new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- this.addView(mWorkFooterContainer);
- mWorkFooterContainer.setInsets(mInsets);
- mWorkFooterContainer.post(() -> mAH[AdapterHolder.WORK].applyPadding());
+ mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate(
+ R.layout.work_mode_switch, this, false);
+ this.addView(mWorkModeSwitch);
+ mWorkModeSwitch.setInsets(mInsets);
+ mWorkModeSwitch.post(() -> mAH[AdapterHolder.WORK].applyPadding());
}
private void replaceRVContainer(boolean showTabs) {
@@ -469,8 +465,8 @@
findViewById(R.id.tab_work)
.setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
}
- if (mWorkFooterContainer != null) {
- mWorkFooterContainer.setWorkTabVisible(pos == AdapterHolder.WORK);
+ if (mWorkModeSwitch != null) {
+ mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK);
}
}
@@ -648,6 +644,8 @@
if (!mIsWork || recyclerView == null) return;
boolean workDisabled = UserCache.INSTANCE.get(mLauncher).isAnyProfileQuietModeEnabled();
if (mWorkDisabled == workDisabled) return;
+ recyclerView.setContentDescription(
+ workDisabled ? mLauncher.getString(R.string.work_apps_paused_title) : null);
if (workDisabled) {
appsList.updateItemFilter((info, cn) -> false);
recyclerView.addAutoSizedOverlay(
@@ -662,8 +660,7 @@
void applyPadding() {
if (recyclerView != null) {
int bottomOffset =
- mWorkFooterContainer != null && mIsWork ? mWorkFooterContainer.getHeight()
- : 0;
+ mWorkModeSwitch != null && mIsWork ? mWorkModeSwitch.getHeight() : 0;
recyclerView.setPadding(padding.left, padding.top, padding.right,
padding.bottom + bottomOffset);
}
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index aadb297..f935e4d 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -15,7 +15,13 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.Process;
import android.os.UserHandle;
@@ -24,28 +30,45 @@
import android.view.MotionEvent;
import android.widget.Switch;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.pm.UserCache;
import java.lang.ref.WeakReference;
-public class WorkModeSwitch extends Switch {
+/**
+ * Work profile toggle switch shown at the bottom of AllApps work tab
+ */
+public class WorkModeSwitch extends Switch implements Insettable {
+
+ private Rect mInsets = new Rect();
+ protected ObjectAnimator mOpenCloseAnimator;
+
public WorkModeSwitch(Context context) {
super(context);
+ init();
}
public WorkModeSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
+ init();
}
public WorkModeSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
}
@Override
public void setChecked(boolean checked) {
- // No-op, do not change the checked state until broadcast is received.
+
}
@Override
@@ -55,15 +78,24 @@
private void setCheckedInternal(boolean checked) {
super.setChecked(checked);
+ setCompoundDrawablesWithIntrinsicBounds(
+ checked ? R.drawable.ic_corp : R.drawable.ic_corp_off, 0, 0, 0);
}
public void refresh() {
+ if (!shouldShowWorkSwitch()) return;
UserCache userManager = UserCache.INSTANCE.get(getContext());
setCheckedInternal(!userManager.isAnyProfileQuietModeEnabled());
setEnabled(true);
}
@Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ this.setVisibility(shouldShowWorkSwitch() ? VISIBLE : GONE);
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent ev) {
return ev.getActionMasked() == MotionEvent.ACTION_MOVE || super.onTouchEvent(ev);
}
@@ -72,6 +104,24 @@
new SetQuietModeEnabledAsyncTask(enabled, new WeakReference<>(this)).execute();
}
+ @Override
+ public void setInsets(Rect insets) {
+ int bottomInset = insets.bottom - mInsets.bottom;
+ mInsets.set(insets);
+ setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
+ getPaddingBottom() + bottomInset);
+ }
+
+ /**
+ * Animates in/out work profile toggle panel based on the tab user is on
+ */
+ public void setWorkTabVisible(boolean workTabVisible) {
+ if (!shouldShowWorkSwitch()) return;
+
+ mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(ALPHA, workTabVisible ? 1 : 0));
+ mOpenCloseAnimator.start();
+ }
+
private static final class SetQuietModeEnabledAsyncTask
extends AsyncTask<Void, Void, Boolean> {
@@ -122,4 +172,11 @@
}
}
}
+
+ private boolean shouldShowWorkSwitch() {
+ Launcher launcher = Launcher.getLauncher(getContext());
+ return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
+ || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
+ == PackageManager.PERMISSION_GRANTED);
+ }
}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 1539747..d0d9aaf 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -137,6 +137,8 @@
*
* @param b The bitmap to display as the drag image. It will be re-scaled to the
* enlarged size.
+ * @param originalView The source view (ie. icon, widget etc.) that is being dragged
+ * and which the DragView represents
* @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
* @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
* @param source An object representing where the drag originated
@@ -144,7 +146,7 @@
* @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
* Makes dragging feel more precise, e.g. you can clip out a transparent border
*/
- public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
+ public DragView startDrag(Bitmap b, DraggableView originalView, int dragLayerX, int dragLayerY,
DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion,
float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) {
if (PROFILE_DRAWING_DURING_DRAG) {
@@ -170,6 +172,7 @@
mLastDropTarget = null;
mDragObject = new DropTarget.DragObject(mLauncher.getApplicationContext());
+ mDragObject.originalView = originalView;
mIsInPreDrag = mOptions.preDragCondition != null
&& !mOptions.preDragCondition.shouldStartDrag(0);
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 9d07595..e18ca54 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -41,7 +41,6 @@
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
-import android.widget.TextView;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.CellLayout;
@@ -52,7 +51,6 @@
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
@@ -90,6 +88,8 @@
private int mTopViewIndex;
private int mChildCountOnLastUpdate = -1;
+ private Rect mTmpRect = new Rect();
+
// Related to adjacent page hints
private final ViewGroupFocusHelper mFocusIndicatorHelper;
private final WorkspaceAndHotseatScrim mWorkspaceScrim;
@@ -254,59 +254,46 @@
public void animateViewIntoPosition(DragView dragView, final View child, int duration,
View anchorView) {
+
ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
parentChildren.measureChild(child);
+ parentChildren.layoutChild(child);
- Rect r = new Rect();
- getViewRectRelativeToSelf(dragView, r);
+ getViewRectRelativeToSelf(dragView, mTmpRect);
+ final int fromX = mTmpRect.left;
+ final int fromY = mTmpRect.top;
float coord[] = new float[2];
float childScale = child.getScaleX();
+
coord[0] = lp.x + (child.getMeasuredWidth() * (1 - childScale) / 2);
coord[1] = lp.y + (child.getMeasuredHeight() * (1 - childScale) / 2);
// Since the child hasn't necessarily been laid out, we force the lp to be updated with
// the correct coordinates (above) and use these to determine the final location
float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
+
// We need to account for the scale of the child itself, as the above only accounts for
// for the scale in parents.
scale *= childScale;
int toX = Math.round(coord[0]);
int toY = Math.round(coord[1]);
float toScale = scale;
- if (child instanceof TextView) {
- TextView tv = (TextView) child;
- // Account for the source scale of the icon (ie. from AllApps to Workspace, in which
- // the workspace may have smaller icon bounds).
- toScale = scale / dragView.getIntrinsicIconScaleFactor();
- // The child may be scaled (always about the center of the view) so to account for it,
- // we have to offset the position by the scaled size. Once we do that, we can center
- // the drag view about the scaled child view.
- // padding will remain constant (does not scale with size)
- toY += tv.getPaddingTop();
- toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
- if (dragView.getDragVisualizeOffset() != null) {
- toY -= Math.round(toScale * dragView.getDragVisualizeOffset().y);
- }
+ if (child instanceof DraggableView) {
+ DraggableView d = (DraggableView) child;
+ d.getVisualDragBounds(mTmpRect);
- toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
- } else if (child instanceof FolderIcon) {
- // Account for holographic blur padding on the drag view
- toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
- toY -= scale * dragView.getBlurSizeOutline() / 2;
- toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
- // Center in the x coordinate about the target's drawable
- toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
- } else {
- toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
- toX -= (Math.round(scale * (dragView.getMeasuredWidth()
- - child.getMeasuredWidth()))) / 2;
+ // This accounts for the offset of the DragView created by scaling it about its
+ // center as it animates into place.
+ float scaleShiftX = dragView.getMeasuredWidth() * (1 - scale) / 2;
+ float scaleShiftY = dragView.getMeasuredHeight() * (1 - scale) / 2;
+
+ toX += scale * (mTmpRect.left - dragView.getBlurSizeOutline() / 2) - scaleShiftX;
+ toY += scale * (mTmpRect.top - dragView.getBlurSizeOutline() / 2) - scaleShiftY;
}
- final int fromX = r.left;
- final int fromY = r.top;
child.setVisibility(INVISIBLE);
Runnable onCompleteRunnable = () -> child.setVisibility(VISIBLE);
animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
diff --git a/src/com/android/launcher3/dragndrop/DraggableView.java b/src/com/android/launcher3/dragndrop/DraggableView.java
new file mode 100644
index 0000000..df99902
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/DraggableView.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 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.dragndrop;
+
+import android.graphics.Rect;
+
+/**
+ * Interface defining methods required for drawing and previewing DragViews, drag previews, and
+ * related animations
+ */
+public interface DraggableView {
+ int DRAGGABLE_ICON = 0;
+ int DRAGGABLE_WIDGET = 1;
+
+ /**
+ * Static ctr for a simple instance which just returns the view type.
+ */
+ static DraggableView ofType(int type) {
+ return () -> type;
+ }
+
+ /**
+ * Certain handling of DragViews depend only on whether this is an Icon Type item or a Widget
+ * Type item.
+ *
+ * @return DRAGGABLE_ICON or DRAGGABLE_WIDGET as appropriate
+ */
+ int getViewType();
+
+ /**
+ * Before rendering as a DragView bitmap, some views need a preparation step.
+ */
+ default void prepareDrawDragView() { }
+
+ /**
+ * If an actual View subclass, this method returns the rectangle (within the View's coordinates)
+ * of the visual region that should get dragged. This is used to extract exactly that element
+ * as well as to offset that element as appropriate for various animations
+ *
+ * @param bounds Visual bounds in the views coordinates will be written here.
+ */
+ default void getVisualDragBounds(Rect bounds) { }
+}
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 0bb3fba..ea1fbdb 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -20,10 +20,10 @@
import android.annotation.TargetApi;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -33,7 +33,6 @@
import androidx.annotation.Nullable;
import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.ShiftedBitmapDrawable;
@@ -50,6 +49,7 @@
private final Drawable mBadge;
private final Path mMask;
private final ConstantState mConstantState;
+ private static final Rect sTmpRect = new Rect();
private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) {
super(bg, fg);
@@ -72,23 +72,14 @@
public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon(
Launcher launcher, int folderId, Point dragViewSize) {
Preconditions.assertNonUiThread();
- int margin = launcher.getResources()
- .getDimensionPixelSize(R.dimen.blur_size_medium_outline);
-
- // Allocate various bitmaps on the background thread, because why not!
- int width = dragViewSize.x - margin;
- int height = dragViewSize.y - margin;
- if (width <= 0 || height <= 0) {
- return null;
- }
- final Bitmap badge = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// Create the actual drawable on the UI thread to avoid race conditions with
// FolderIcon draw pass
try {
return MAIN_EXECUTOR.submit(() -> {
FolderIcon icon = launcher.findFolderIcon(folderId);
- return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize);
+ return icon == null ? null : createDrawableOnUiThread(icon, dragViewSize);
+
}).get();
} catch (Exception e) {
Log.e(TAG, "Unable to create folder icon", e);
@@ -96,49 +87,50 @@
}
}
- /**
- * Initializes various bitmaps on the UI thread and returns the final drawable.
- */
private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon,
- Bitmap badgeBitmap, Point dragViewSize) {
+ Point dragViewSize) {
Preconditions.assertUIThread();
- float margin = icon.getResources().getDimension(R.dimen.blur_size_medium_outline) / 2;
- Canvas c = new Canvas();
+ icon.getPreviewBounds(sTmpRect);
+
PreviewBackground bg = icon.getFolderBackground();
- // Initialize badge
- c.setBitmap(badgeBitmap);
- bg.drawShadow(c);
- bg.drawBackgroundStroke(c);
- icon.drawDot(c);
+ // assume square
+ assert (dragViewSize.x == dragViewSize.y);
+ final int previewSize = sTmpRect.width();
- // Initialize preview
- final float sizeScaleFactor = 1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction();
- final int previewWidth = (int) (dragViewSize.x * sizeScaleFactor);
- final int previewHeight = (int) (dragViewSize.y * sizeScaleFactor);
+ final int margin = (dragViewSize.x - previewSize) / 2;
+ final float previewShiftX = -sTmpRect.left + margin;
+ final float previewShiftY = -sTmpRect.top + margin;
- final float shiftFactor = AdaptiveIconDrawable.getExtraInsetFraction() / sizeScaleFactor;
- final float previewShiftX = shiftFactor * previewWidth;
- final float previewShiftY = shiftFactor * previewHeight;
-
- Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight,
+ // Initialize badge, which consists of the outline stroke, shadow and dot; these
+ // must be rendered above the foreground
+ Bitmap badgeBmp = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
(canvas) -> {
- int count = canvas.save();
+ canvas.save();
canvas.translate(previewShiftX, previewShiftY);
- icon.getPreviewItemManager().draw(canvas);
- canvas.restoreToCount(count);
+ bg.drawShadow(canvas);
+ bg.drawBackgroundStroke(canvas);
+ icon.drawDot(canvas);
+ canvas.restore();
});
// Initialize mask
Path mask = new Path();
Matrix m = new Matrix();
- m.setTranslate(margin, margin);
+ m.setTranslate(previewShiftX, previewShiftY);
bg.getClipPath().transform(m, mask);
- ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBitmap, margin, margin);
- ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap,
- margin - previewShiftX, margin - previewShiftY);
+ Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
+ (canvas) -> {
+ canvas.save();
+ canvas.translate(previewShiftX, previewShiftY);
+ icon.getPreviewItemManager().draw(canvas);
+ canvas.restore();
+ });
+
+ ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBmp, 0, 0);
+ ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 0, 0);
return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask);
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index ab1ff10..f0d18ae 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -63,6 +63,7 @@
import com.android.launcher3.dragndrop.BaseItemDragListener;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Executors;
@@ -78,7 +79,8 @@
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
-public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView {
+public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
+ DraggableView {
@Thunk ActivityContext mActivity;
@Thunk Folder mFolder;
@@ -230,6 +232,16 @@
mBackground.getBounds(outBounds);
}
+ @Override
+ public int getViewType() {
+ return DRAGGABLE_ICON;
+ }
+
+ @Override
+ public void getVisualDragBounds(Rect bounds) {
+ getPreviewBounds(bounds);
+ }
+
public float getBackgroundStrokeWidth() {
return mBackground.getStrokeWidth();
}
@@ -525,6 +537,10 @@
invalidate();
}
+ public boolean getIconVisible() {
+ return mBackgroundIsVisible;
+ }
+
public PreviewBackground getFolderBackground() {
return mBackground;
}
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 94d30f6..ed9dfbb 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -30,14 +30,12 @@
import android.view.View;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
import java.nio.ByteBuffer;
@@ -53,7 +51,7 @@
// The padding added to the drag view during the preview generation.
public final int previewPadding;
- protected final int blurSizeOutline;
+ public final int blurSizeOutline;
private OutlineGeneratorCallback mOutlineGeneratorCallback;
public Bitmap generatedDragOutline;
@@ -66,56 +64,25 @@
mView = view;
blurSizeOutline =
context.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
-
- if (mView instanceof BubbleTextView) {
- Drawable d = ((BubbleTextView) mView).getIcon();
- Rect bounds = getDrawableBounds(d);
- previewPadding = blurSizeOutline - bounds.left - bounds.top;
- } else {
- previewPadding = blurSizeOutline;
- }
+ previewPadding = blurSizeOutline;
}
/**
* Draws the {@link #mView} into the given {@param destCanvas}.
*/
protected void drawDragView(Canvas destCanvas, float scale) {
- destCanvas.save();
+ int saveCount = destCanvas.save();
destCanvas.scale(scale, scale);
- if (mView instanceof BubbleTextView) {
- Drawable d = ((BubbleTextView) mView).getIcon();
- Rect bounds = getDrawableBounds(d);
- destCanvas.translate(blurSizeOutline / 2 - bounds.left,
- blurSizeOutline / 2 - bounds.top);
- if (d instanceof FastBitmapDrawable) {
- ((FastBitmapDrawable) d).setScale(1);
- }
- d.draw(destCanvas);
- } else {
- final Rect clipRect = mTempRect;
- mView.getDrawingRect(clipRect);
-
- boolean textVisible = false;
- if (mView instanceof FolderIcon) {
- // For FolderIcons the text can bleed into the icon area, and so we need to
- // hide the text completely (which can't be achieved by clipping).
- if (((FolderIcon) mView).getTextVisible()) {
- ((FolderIcon) mView).setTextVisible(false);
- textVisible = true;
- }
- }
- destCanvas.translate(-mView.getScrollX() + blurSizeOutline / 2,
- -mView.getScrollY() + blurSizeOutline / 2);
- destCanvas.clipRect(clipRect);
+ if (mView instanceof DraggableView) {
+ DraggableView dv = (DraggableView) mView;
+ dv.prepareDrawDragView();
+ dv.getVisualDragBounds(mTempRect);
+ destCanvas.translate(blurSizeOutline / 2 - mTempRect.left,
+ blurSizeOutline / 2 - mTempRect.top);
mView.draw(destCanvas);
-
- // Restore text visibility of FolderIcon if necessary
- if (textVisible) {
- ((FolderIcon) mView).setTextVisible(true);
- }
}
- destCanvas.restore();
+ destCanvas.restoreToCount(saveCount);
}
/**
@@ -123,28 +90,15 @@
* Responsibility for the bitmap is transferred to the caller.
*/
public Bitmap createDragBitmap() {
- int width = mView.getWidth();
- int height = mView.getHeight();
-
- if (mView instanceof BubbleTextView) {
- Drawable d = ((BubbleTextView) mView).getIcon();
- Rect bounds = getDrawableBounds(d);
- width = bounds.width();
- height = bounds.height();
- } else if (mView instanceof LauncherAppWidgetHostView) {
- float scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
- width = (int) (mView.getWidth() * scale);
- height = (int) (mView.getHeight() * scale);
-
- if (mView instanceof PendingAppWidgetHostView) {
- // Use hardware renderer as the icon for the pending app widget may be a hw bitmap
- return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
- height + blurSizeOutline, (c) -> drawDragView(c, scale));
- } else {
- // Use software renderer for widgets as we know that they already work
- return BitmapRenderer.createSoftwareBitmap(width + blurSizeOutline,
- height + blurSizeOutline, (c) -> drawDragView(c, scale));
- }
+ int width = 0;
+ int height = 0;
+ if (mView instanceof DraggableView) {
+ ((DraggableView) mView).getVisualDragBounds(mTempRect);
+ width = mTempRect.width();
+ height = mTempRect.height();
+ } else {
+ width = mView.getWidth();
+ height = mView.getHeight();
}
return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 5af5ebb..9bac259 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -59,6 +59,7 @@
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
@@ -662,7 +663,8 @@
iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
- DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+ DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
+ DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), draggableView,
mContainer, sv.getFinalInfo(),
new ShortcutDragPreviewProvider(sv.getIconView(), iconShift),
new DragOptions());
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index 04741a1..b0defd4 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -39,7 +39,7 @@
public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
new MainThreadInitializedObject<>(VibratorWrapper::new);
- private static final VibrationEffect EFFECT_CLICK =
+ public static final VibrationEffect EFFECT_CLICK =
createPredefined(VibrationEffect.EFFECT_CLICK);
/**
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index fa625ed..d41bb86 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -67,7 +67,6 @@
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.ShiftedBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -412,9 +411,8 @@
drawable = originalView.getBackground();
}
} else {
- boolean isFolderIcon = originalView instanceof FolderIcon;
- int width = isFolderIcon ? originalView.getWidth() : (int) pos.width();
- int height = isFolderIcon ? originalView.getHeight() : (int) pos.height();
+ int width = (int) pos.width();
+ int height = (int) pos.height();
if (supportsAdaptiveIcons) {
drawable = getFullDrawable(l, info, width, height, sTmpObjArray);
if (drawable instanceof AdaptiveIconDrawable) {
@@ -499,20 +497,7 @@
}
}
- if (isFolderIcon) {
- ((FolderIcon) originalView).getPreviewBounds(sTmpRect);
- float bgStroke = ((FolderIcon) originalView).getBackgroundStrokeWidth();
- if (mForeground instanceof ShiftedBitmapDrawable) {
- ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground;
- sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke);
- sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke);
- }
- if (mBadge instanceof ShiftedBitmapDrawable) {
- ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mBadge;
- sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke);
- sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke);
- }
- } else {
+ if (!isFolderIcon) {
Utilities.scaleRectAboutCenter(mStartRevealRect,
IconShape.getNormalizationScale());
}
@@ -615,8 +600,9 @@
@WorkerThread
@SuppressWarnings("WrongThread")
private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
- !(drawable instanceof AdaptiveIconDrawable)) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O
+ || !(drawable instanceof AdaptiveIconDrawable)
+ || (drawable instanceof FolderAdaptiveIcon)) {
return 0;
}
int blurSizeOutline =
diff --git a/src/com/android/launcher3/views/WorkFooterContainer.java b/src/com/android/launcher3/views/WorkFooterContainer.java
deleted file mode 100644
index d86d0ff..0000000
--- a/src/com/android/launcher3/views/WorkFooterContainer.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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.views;
-
-import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
-
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.WorkModeSwitch;
-import com.android.launcher3.pm.UserCache;
-
-/**
- * Container to show work footer in all-apps.
- */
-public class WorkFooterContainer extends LinearLayout implements Insettable {
- private Rect mInsets = new Rect();
-
- private WorkModeSwitch mWorkModeSwitch;
- private TextView mWorkModeLabel;
-
- protected final ObjectAnimator mOpenCloseAnimator;
-
- public WorkFooterContainer(Context context) {
- this(context, null, 0);
- }
-
- public WorkFooterContainer(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public WorkFooterContainer(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- updateTranslation();
- this.setVisibility(shouldShowWorkFooter() ? VISIBLE : GONE);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mWorkModeSwitch = findViewById(R.id.work_mode_toggle);
- mWorkModeLabel = findViewById(R.id.work_mode_label);
- }
-
- @Override
- public void offsetTopAndBottom(int offset) {
- super.offsetTopAndBottom(offset);
- updateTranslation();
- }
-
- private void updateTranslation() {
- if (getParent() instanceof View) {
- View parent = (View) getParent();
- int availableBot = parent.getHeight() - parent.getPaddingBottom();
- setTranslationY(Math.max(0, availableBot - getBottom()));
- }
- }
-
- @Override
- public void setInsets(Rect insets) {
- int bottomInset = insets.bottom - mInsets.bottom;
- mInsets.set(insets);
- setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
- getPaddingBottom() + bottomInset);
- }
-
- /**
- * Animates in/out work profile toggle panel based on the tab user is on
- */
- public void setWorkTabVisible(boolean workTabVisible) {
- if (!shouldShowWorkFooter()) return;
-
- mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(ALPHA, workTabVisible ? 1 : 0));
- mOpenCloseAnimator.start();
- }
-
- /**
- * Refreshes views based on current work profile enabled status
- */
- public void refresh() {
- if (!shouldShowWorkFooter()) return;
- boolean anyProfileQuietModeEnabled = UserCache.INSTANCE.get(
- getContext()).isAnyProfileQuietModeEnabled();
-
- mWorkModeLabel.setCompoundDrawablesWithIntrinsicBounds(
- anyProfileQuietModeEnabled ? R.drawable.ic_corp_off : R.drawable.ic_corp, 0, 0, 0);
- mWorkModeSwitch.refresh();
- }
-
- /**
- * Returns work mode switch
- */
- public WorkModeSwitch getWorkModeSwitch() {
- return mWorkModeSwitch;
- }
-
- private boolean shouldShowWorkFooter() {
- Launcher launcher = Launcher.getLauncher(getContext());
- return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
- || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
- == PackageManager.PERMISSION_GRANTED);
- }
-}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index f3fd7ca..c1310e3 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PointF;
+import android.graphics.Rect;
import android.os.Handler;
import android.os.SystemClock;
import android.util.SparseBooleanArray;
@@ -44,6 +45,7 @@
import com.android.launcher3.StylusEventHelper;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
@@ -52,7 +54,7 @@
* {@inheritDoc}
*/
public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
- implements TouchCompleteListener, View.OnLongClickListener {
+ implements TouchCompleteListener, View.OnLongClickListener, DraggableView {
// Related to the auto-advancing of widgets
private static final long ADVANCE_INTERVAL = 20000;
@@ -412,4 +414,18 @@
}
return false;
}
+
+ @Override
+ public int getViewType() {
+ return DRAGGABLE_WIDGET;
+ }
+
+ @Override
+ public void getVisualDragBounds(Rect bounds) {
+ int x = (int) (1 - getScaleToFit()) * getMeasuredWidth() / 2;
+ int y = (int) (1 - getScaleToFit()) * getMeasuredWidth() / 2;
+ int width = (int) getScaleToFit() * getMeasuredWidth();
+ int height = (int) getScaleToFit() * getMeasuredHeight();
+ bounds.set(x, y , x + width, y + height);
+ }
}
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 68b1595..104ad77 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -24,12 +24,15 @@
import android.view.ViewDebug;
import android.view.ViewGroup;
+import com.android.launcher3.dragndrop.DraggableView;
+
import java.util.ArrayList;
/**
* Extension of AppWidgetHostView with support for controlled keyboard navigation.
*/
-public abstract class NavigableAppWidgetHostView extends AppWidgetHostView {
+public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
+ implements DraggableView {
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mChildrenFocused;
@@ -133,4 +136,14 @@
// The host view's background changes when selected, to indicate the focus is inside.
setSelected(childIsFocused);
}
+
+ @Override
+ public int getViewType() {
+ return DRAGGABLE_WIDGET;
+ }
+
+ @Override
+ public void getVisualDragBounds(Rect bounds) {
+ bounds.set(0, 0 , getMeasuredWidth(), getMeasuredHeight());
+ }
}
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 662e627..3c11274 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -32,6 +32,7 @@
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.icons.LauncherIcons;
@@ -79,6 +80,8 @@
mEstimatedCellSize = launcher.getWorkspace().estimateItemSize(mAddInfo);
+ DraggableView draggableView;
+
if (mAddInfo instanceof PendingAddWidgetInfo) {
PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) mAddInfo;
@@ -110,6 +113,7 @@
dragOffset = null;
dragRegion = null;
+ draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_WIDGET);
} else {
PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
@@ -136,6 +140,7 @@
dragRegion.top = (mEstimatedCellSize[1]
- iconSize - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2;
dragRegion.bottom = dragRegion.top + iconSize;
+ draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
}
// Since we are not going through the workspace for starting the drag, set drag related
@@ -148,8 +153,8 @@
+ (int) ((scale * preview.getHeight() - preview.getHeight()) / 2);
// Start the drag
- launcher.getDragController().startDrag(preview, dragLayerX, dragLayerY, source, mAddInfo,
- dragOffset, dragRegion, scale, scale, options);
+ launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY,
+ source, mAddInfo, dragOffset, dragRegion, scale, scale, options);
}
@Override
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index f5dd995..551b533 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -220,6 +220,7 @@
@Before
public void setUp() throws Exception {
+ mDevice.executeShellCommand("setprop vendor.battery.defender.disable 1");
mDevice.executeShellCommand("settings put global stay_on_while_plugged_in 3");
if (hasSystemUiObject("keyguard_status_view")) {
Log.d(TAG, "Before unlocking the phone");
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index db2d974..8d571ff 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -16,6 +16,7 @@
package com.android.launcher3.ui;
import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -31,9 +32,9 @@
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsPagedView;
+import com.android.launcher3.allapps.WorkModeSwitch;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.views.WorkEduView;
-import com.android.launcher3.views.WorkFooterContainer;
import org.junit.After;
import org.junit.Before;
@@ -87,7 +88,7 @@
executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS);
getOnceNotNull("Apps view did not bind",
- launcher -> launcher.getAppsView().getWorkFooterContainer(), 60000);
+ launcher -> launcher.getAppsView().getWorkModeSwitch(), 60000);
UserManager userManager = getFromLauncher(l -> l.getSystemService(UserManager.class));
assertEquals(2, userManager.getUserProfiles().size());
@@ -102,10 +103,10 @@
assertTrue(userManager.isQuietModeEnabled(workProfile));
executeOnLauncher(launcher -> {
- WorkFooterContainer wf = launcher.getAppsView().getWorkFooterContainer();
+ WorkModeSwitch wf = launcher.getAppsView().getWorkModeSwitch();
((AllAppsPagedView) launcher.getAppsView().getContentView()).snapToPageImmediately(
AllAppsContainerView.AdapterHolder.WORK);
- wf.getWorkModeSwitch().toggle();
+ wf.toggle();
});
waitForLauncherCondition("Work toggle did not work",
l -> l.getSystemService(UserManager.class).isQuietModeEnabled(workProfile));
@@ -158,9 +159,11 @@
// dismiss personal edu
mDevice.pressHome();
+ waitForState("Launcher did not go home", () -> NORMAL);
// open work tab
executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+ waitForState("Launcher did not switch to all apps", () -> ALL_APPS);
executeOnLauncher(launcher -> {
AllAppsPagedView pagedView = (AllAppsPagedView) launcher.getAppsView().getContentView();
pagedView.setCurrentPage(WORK_PAGE);