Merging from ub-launcher3-rvc-dev @ build 6436292

Bug:150504032
Test: manual, presubmit on the source branch
x20/teams/android-launcher/merge/ub-launcher3-rvc-dev_rvc-dev_6436292.html

Change-Id: I28d6ec42b7a6a8e779abb306cc29c9801910206f
This commit is contained in:
Santiago Etchebehere
2020-04-27 22:40:31 -07:00
193 changed files with 2093 additions and 982 deletions
@@ -161,6 +161,8 @@ public abstract class BaseQuickstepLauncher extends Launcher
@Override
protected void setupViews() {
super.setupViews();
SysUINavigationMode.INSTANCE.get(this).updateMode();
mActionsView = findViewById(R.id.overview_actions_view);
((RecentsView) getOverviewPanel()).init(mActionsView);
@@ -16,6 +16,8 @@
package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import android.content.Context;
@@ -76,7 +78,7 @@ public class AllAppsState extends LauncherState {
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
ScaleAndTranslation scaleAndTranslation = LauncherState.OVERVIEW
.getWorkspaceScaleAndTranslation(launcher);
if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) {
if (SysUINavigationMode.getMode(launcher) == NO_BUTTON && !ENABLE_OVERVIEW_ACTIONS.get()) {
float normalScale = 1;
// Scale down halfway to where we'd be in overview, to prepare for a potential pause.
scaleAndTranslation.scale = (scaleAndTranslation.scale + normalScale) / 2;
@@ -22,10 +22,12 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.TimeInterpolator;
@@ -132,7 +134,12 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
return TouchInteractionService.isConnected() ?
mLauncher.getStateManager().getLastState() : NORMAL;
} else if (fromState == OVERVIEW) {
return isDragTowardPositive ? ALL_APPS : NORMAL;
LauncherState positiveDragTarget = ALL_APPS;
if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(mLauncher)) {
// Don't allow swiping up to all apps.
positiveDragTarget = OVERVIEW;
}
return isDragTowardPositive ? positiveDragTarget : NORMAL;
} else if (fromState == NORMAL && isDragTowardPositive) {
int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
return mAllowDragToOverview && TouchInteractionService.isConnected()
@@ -91,6 +91,11 @@ public interface BaseActivityInterface<T extends BaseDraggingActivity> {
return activity != null && activity.hasBeenResumed();
}
default boolean isStarted() {
BaseDraggingActivity activity = getCreatedActivity();
return activity != null && activity.isStarted();
}
@UiThread
@Nullable
<T extends View> T getVisibleRecentsView();
@@ -99,6 +99,10 @@ class OrientationTouchTransformer {
return;
}
this.mMode = newMode;
// Swipe touch regions are independent of nav mode, so we have to clear them explicitly
// here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
// It tries to cache and reuse swipe regions whenever possible based only on rotation
mSwipeTouchRegions.clear();
resetSwipeRegions(info);
}
@@ -21,6 +21,7 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static com.android.quickstep.util.RecentsOrientedState.isFixedRotationTransformEnabled;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
@@ -35,7 +36,6 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_S
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -51,8 +51,6 @@ import androidx.annotation.BinderThread;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DefaultDisplay;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.util.NavBarPosition;
@@ -176,7 +174,7 @@ public class RecentsAnimationDeviceState implements
}
private void setupOrientationSwipeHandler() {
if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
if (!isFixedRotationTransformEnabled(mContext)) {
return;
}
@@ -74,15 +74,20 @@ public class SysUINavigationMode {
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Mode oldMode = mMode;
initializeMode();
if (mMode != oldMode) {
dispatchModeChange();
}
updateMode();
}
}, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
}
/** Updates navigation mode when needed. */
public void updateMode() {
Mode oldMode = mMode;
initializeMode();
if (mMode != oldMode) {
dispatchModeChange();
}
}
private void initializeMode() {
int modeInt = getSystemIntegerRes(mContext, NAV_BAR_INTERACTION_MODE_RES_NAME);
for(Mode m : Mode.values()) {
@@ -22,6 +22,7 @@ import android.view.View;
import com.android.launcher3.R;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
/** A {@link TutorialController} for the Back tutorial. */
final class BackGestureTutorialController extends TutorialController {
@@ -34,7 +35,7 @@ final class BackGestureTutorialController extends TutorialController {
void transitToController() {
super.transitToController();
if (mTutorialType != BACK_NAVIGATION_COMPLETE) {
mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
showHandCoachingAnimation();
}
}
@@ -95,16 +96,10 @@ final class BackGestureTutorialController extends TutorialController {
public void onBackGestureAttempted(BackGestureResult result) {
switch (mTutorialType) {
case RIGHT_EDGE_BACK_NAVIGATION:
if (result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
hideHandCoachingAnimation();
mTutorialFragment.changeController(LEFT_EDGE_BACK_NAVIGATION);
}
handleAttemptFromRight(result);
break;
case LEFT_EDGE_BACK_NAVIGATION:
if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT) {
hideHandCoachingAnimation();
mTutorialFragment.changeController(BACK_NAVIGATION_COMPLETE);
}
handleAttemptFromLeft(result);
break;
case BACK_NAVIGATION_COMPLETE:
if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
@@ -114,4 +109,57 @@ final class BackGestureTutorialController extends TutorialController {
break;
}
}
private void handleAttemptFromRight(BackGestureResult result) {
switch (result) {
case BACK_COMPLETED_FROM_RIGHT:
hideFeedback();
hideHandCoachingAnimation();
showRippleEffect(
() -> mTutorialFragment.changeController(LEFT_EDGE_BACK_NAVIGATION));
break;
case BACK_CANCELLED_FROM_RIGHT:
showFeedback(R.string.back_gesture_feedback_cancelled_right_edge);
break;
case BACK_COMPLETED_FROM_LEFT:
case BACK_CANCELLED_FROM_LEFT:
case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE:
showFeedback(R.string.back_gesture_feedback_swipe_too_far_from_right_edge);
break;
case BACK_NOT_STARTED_IN_NAV_BAR_REGION:
showFeedback(R.string.back_gesture_feedback_swipe_in_nav_bar);
break;
}
}
private void handleAttemptFromLeft(BackGestureResult result) {
switch (result) {
case BACK_COMPLETED_FROM_LEFT:
hideFeedback();
hideHandCoachingAnimation();
showRippleEffect(
() -> mTutorialFragment.changeController(BACK_NAVIGATION_COMPLETE));
break;
case BACK_CANCELLED_FROM_LEFT:
showFeedback(R.string.back_gesture_feedback_cancelled_left_edge);
break;
case BACK_COMPLETED_FROM_RIGHT:
case BACK_CANCELLED_FROM_RIGHT:
case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE:
showFeedback(R.string.back_gesture_feedback_swipe_too_far_from_left_edge);
break;
case BACK_NOT_STARTED_IN_NAV_BAR_REGION:
showFeedback(R.string.back_gesture_feedback_swipe_in_nav_bar);
break;
}
}
@Override
public void onNavBarGestureAttempted(NavBarGestureResult result) {
if (mTutorialType == BACK_NAVIGATION_COMPLETE) {
if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
mTutorialFragment.closeTutorial();
}
}
}
}
@@ -15,6 +15,9 @@
*/
package com.android.quickstep.interaction;
import android.view.MotionEvent;
import android.view.View;
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
@@ -29,4 +32,17 @@ public class BackGestureTutorialFragment extends TutorialFragment {
TutorialController createController(TutorialType type) {
return new BackGestureTutorialController(this, type);
}
@Override
Class<? extends TutorialController> getControllerClass() {
return BackGestureTutorialController.class;
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN && mTutorialController != null) {
mTutorialController.setRippleHotspot(motionEvent.getX(), motionEvent.getY());
}
return super.onTouch(view, motionEvent);
}
}
@@ -58,6 +58,7 @@ public class EdgeBackGestureHandler implements OnTouchListener {
private final PointF mDownPoint = new PointF();
private boolean mThresholdCrossed = false;
private boolean mAllowGesture = false;
private BackGestureResult mDisallowedGestureReason;
private boolean mIsEnabled;
private int mLeftInset;
private int mRightInset;
@@ -145,11 +146,13 @@ public class EdgeBackGestureHandler implements OnTouchListener {
private boolean isWithinTouchRegion(int x, int y) {
// Disallow if too far from the edge
if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {
mDisallowedGestureReason = BackGestureResult.BACK_NOT_STARTED_TOO_FAR_FROM_EDGE;
return false;
}
// Disallow if we are in the bottom gesture area
if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
mDisallowedGestureReason = BackGestureResult.BACK_NOT_STARTED_IN_NAV_BAR_REGION;
return false;
}
@@ -169,12 +172,12 @@ public class EdgeBackGestureHandler implements OnTouchListener {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
boolean isOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset;
mDisallowedGestureReason = BackGestureResult.UNKNOWN;
mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
mDownPoint.set(ev.getX(), ev.getY());
if (mAllowGesture) {
mEdgeBackPanel.setIsLeftPanel(isOnLeftEdge);
mEdgeBackPanel.onMotionEvent(ev);
mDownPoint.set(ev.getX(), ev.getY());
mThresholdCrossed = false;
}
} else if (mAllowGesture) {
@@ -193,7 +196,6 @@ public class EdgeBackGestureHandler implements OnTouchListener {
if (dy > dx && dy > mTouchSlop) {
cancelGesture(ev);
return;
} else if (dx > dy && dx > mTouchSlop) {
mThresholdCrossed = true;
}
@@ -206,8 +208,10 @@ public class EdgeBackGestureHandler implements OnTouchListener {
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
if (!mAllowGesture && mGestureCallback != null) {
mGestureCallback.onBackGestureAttempted(BackGestureResult.BACK_NOT_STARTED);
float dx = Math.abs(ev.getX() - mDownPoint.x);
float dy = Math.abs(ev.getY() - mDownPoint.y);
if (dx > dy && dx > mTouchSlop && !mAllowGesture && mGestureCallback != null) {
mGestureCallback.onBackGestureAttempted(mDisallowedGestureReason);
}
}
}
@@ -223,7 +227,8 @@ public class EdgeBackGestureHandler implements OnTouchListener {
BACK_COMPLETED_FROM_RIGHT,
BACK_CANCELLED_FROM_LEFT,
BACK_CANCELLED_FROM_RIGHT,
BACK_NOT_STARTED,
BACK_NOT_STARTED_TOO_FAR_FROM_EDGE,
BACK_NOT_STARTED_IN_NAV_BAR_REGION,
}
/** Callback to let the UI react to attempted back gestures. */
@@ -21,6 +21,7 @@ import android.view.View;
import com.android.launcher3.R;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
/** A {@link TutorialController} for the Home tutorial. */
final class HomeGestureTutorialController extends TutorialController {
@@ -33,7 +34,7 @@ final class HomeGestureTutorialController extends TutorialController {
void transitToController() {
super.transitToController();
if (mTutorialType != HOME_NAVIGATION_COMPLETE) {
mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
showHandCoachingAnimation();
}
}
@@ -82,4 +83,33 @@ final class HomeGestureTutorialController extends TutorialController {
break;
}
}
@Override
public void onNavBarGestureAttempted(NavBarGestureResult result) {
switch (mTutorialType) {
case HOME_NAVIGATION:
switch (result) {
case HOME_GESTURE_COMPLETED:
hideHandCoachingAnimation();
mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE);
break;
case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
break;
case OVERVIEW_GESTURE_COMPLETED:
showFeedback(R.string.home_gesture_feedback_overview_detected);
break;
case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
showFeedback(R.string.home_gesture_feedback_wrong_swipe_direction);
break;
}
break;
case HOME_NAVIGATION_COMPLETE:
if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
mTutorialFragment.closeTutorial();
}
break;
}
}
}
@@ -29,4 +29,9 @@ public class HomeGestureTutorialFragment extends TutorialFragment {
TutorialController createController(TutorialType type) {
return new HomeGestureTutorialController(this, type);
}
@Override
Class<? extends TutorialController> getControllerClass() {
return HomeGestureTutorialController.class;
}
}
@@ -0,0 +1,121 @@
/*
* 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 static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_GESTURE_COMPLETED;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_NOT_STARTED_TOO_FAR_FROM_EDGE;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.view.Display;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.View.OnTouchListener;
import com.android.launcher3.ResourceUtils;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.util.NavBarPosition;
import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
/** Utility class to handle home gestures. */
public class NavBarGestureHandler implements OnTouchListener {
private static final String LOG_TAG = "NavBarGestureHandler";
private final Point mDisplaySize = new Point();
private final TriggerSwipeUpTouchTracker mSwipeUpTouchTracker;
private int mBottomGestureHeight;
private boolean mTouchCameFromNavBar;
private NavBarGestureAttemptCallback mGestureCallback;
NavBarGestureHandler(Context context) {
final Display display = context.getDisplay();
final int displayRotation;
if (display == null) {
displayRotation = Surface.ROTATION_0;
} else {
displayRotation = display.getRotation();
display.getRealSize(mDisplaySize);
}
mSwipeUpTouchTracker =
new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
new NavBarPosition(Mode.NO_BUTTON, displayRotation),
null /*onInterceptTouch*/, this::onSwipeUp);
final Resources resources = context.getResources();
mBottomGestureHeight =
ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, resources);
}
void registerNavBarGestureAttemptCallback(NavBarGestureAttemptCallback callback) {
mGestureCallback = callback;
}
void unregisterNavBarGestureAttemptCallback() {
mGestureCallback = null;
}
private void onSwipeUp(boolean wasFling) {
if (mGestureCallback == null) {
return;
}
if (mTouchCameFromNavBar) {
mGestureCallback.onNavBarGestureAttempted(wasFling
? HOME_GESTURE_COMPLETED : OVERVIEW_GESTURE_COMPLETED);
} else {
mGestureCallback.onNavBarGestureAttempted(wasFling
? HOME_NOT_STARTED_TOO_FAR_FROM_EDGE : OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE);
}
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
int action = motionEvent.getAction();
boolean intercepted = mSwipeUpTouchTracker.interceptedTouch();
if (action == MotionEvent.ACTION_DOWN) {
mTouchCameFromNavBar = motionEvent.getRawY() >= mDisplaySize.y - mBottomGestureHeight;
mSwipeUpTouchTracker.init();
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
if (mGestureCallback != null && !intercepted && mTouchCameFromNavBar) {
mGestureCallback.onNavBarGestureAttempted(
HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION);
intercepted = true;
}
}
mSwipeUpTouchTracker.onMotionEvent(motionEvent);
return intercepted;
}
enum NavBarGestureResult {
UNKNOWN,
HOME_GESTURE_COMPLETED,
OVERVIEW_GESTURE_COMPLETED,
HOME_NOT_STARTED_TOO_FAR_FROM_EDGE,
OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE,
HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION // Side swipe on nav bar.
}
/** Callback to let the UI react to attempted nav bar gestures. */
interface NavBarGestureAttemptCallback {
/** Called whenever any touch is completed. */
void onNavBarGestureAttempted(NavBarGestureResult result);
}
}
@@ -15,6 +15,7 @@
*/
package com.android.quickstep.interaction;
import android.graphics.drawable.RippleDrawable;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
@@ -26,20 +27,30 @@ import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
abstract class TutorialController {
abstract class TutorialController implements BackGestureAttemptCallback,
NavBarGestureAttemptCallback {
private static final int FEEDBACK_VISIBLE_MS = 3000;
private static final int FEEDBACK_ANIMATION_MS = 500;
private static final int RIPPLE_VISIBLE_MS = 300;
final TutorialFragment mTutorialFragment;
final TutorialType mTutorialType;
TutorialType mTutorialType;
final ImageButton mCloseButton;
final TextView mTitleTextView;
final TextView mSubtitleTextView;
final TextView mFeedbackView;
final View mRippleView;
final RippleDrawable mRippleDrawable;
final TutorialHandAnimation mHandCoachingAnimation;
final ImageView mHandCoachingView;
final Button mActionTextButton;
final Button mActionButton;
private final Runnable mHideFeedbackRunnable;
TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
mTutorialFragment = tutorialFragment;
@@ -50,15 +61,24 @@ abstract class TutorialController {
mCloseButton.setOnClickListener(button -> mTutorialFragment.closeTutorial());
mTitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_title_view);
mSubtitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_subtitle_view);
mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
mRippleDrawable = (RippleDrawable) mRippleView.getBackground();
mHandCoachingAnimation = tutorialFragment.getHandAnimation();
mHandCoachingView = rootView.findViewById(R.id.gesture_tutorial_fragment_hand_coaching);
mHandCoachingView.bringToFront();
mActionTextButton =
rootView.findViewById(R.id.gesture_tutorial_fragment_action_text_button);
mActionButton = rootView.findViewById(R.id.gesture_tutorial_fragment_action_button);
mHideFeedbackRunnable =
() -> mFeedbackView.animate().alpha(0).setDuration(FEEDBACK_ANIMATION_MS)
.withEndAction(this::showHandCoachingAnimation).start();
}
abstract void onBackGestureAttempted(BackGestureResult result);
void setTutorialType(TutorialType tutorialType) {
mTutorialType = tutorialType;
}
@Nullable
Integer getTitleStringId() {
@@ -80,16 +100,52 @@ abstract class TutorialController {
return null;
}
void showFeedback(int resId) {
hideHandCoachingAnimation();
mFeedbackView.setText(resId);
mFeedbackView.animate().alpha(1).setDuration(FEEDBACK_ANIMATION_MS).start();
mFeedbackView.removeCallbacks(mHideFeedbackRunnable);
mFeedbackView.postDelayed(mHideFeedbackRunnable, FEEDBACK_VISIBLE_MS);
}
void hideFeedback() {
mFeedbackView.setText(null);
mFeedbackView.removeCallbacks(mHideFeedbackRunnable);
mFeedbackView.clearAnimation();
mFeedbackView.setAlpha(0);
}
void setRippleHotspot(float x, float y) {
mRippleDrawable.setHotspot(x, y);
}
void showRippleEffect(@Nullable Runnable onCompleteRunnable) {
mRippleDrawable.setState(
new int[] {android.R.attr.state_pressed, android.R.attr.state_enabled});
mRippleView.postDelayed(() -> {
mRippleDrawable.setState(new int[] {});
if (onCompleteRunnable != null) {
onCompleteRunnable.run();
}
}, RIPPLE_VISIBLE_MS);
}
void onActionButtonClicked(View button) {}
void onActionTextButtonClicked(View button) {}
void showHandCoachingAnimation() {
mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
}
void hideHandCoachingAnimation() {
mHandCoachingAnimation.stop();
mHandCoachingView.setVisibility(View.INVISIBLE);
}
@CallSuper
void transitToController() {
hideFeedback();
updateTitles();
updateActionButtons();
}
@@ -21,7 +21,9 @@ import android.graphics.Insets;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -31,13 +33,11 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.android.launcher3.R;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import com.android.quickstep.interaction.TutorialController.TutorialType;
import java.net.URISyntaxException;
abstract class TutorialFragment extends Fragment implements BackGestureAttemptCallback {
abstract class TutorialFragment extends Fragment implements OnTouchListener {
private static final String LOG_TAG = "TutorialFragment";
private static final String SYSTEM_NAVIGATION_SETTING_INTENT =
@@ -52,6 +52,7 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa
View mRootView;
TutorialHandAnimation mHandCoachingAnimation;
EdgeBackGestureHandler mEdgeBackGestureHandler;
NavBarGestureHandler mNavBarGestureHandler;
public static TutorialFragment newInstance(TutorialType tutorialType) {
TutorialFragment fragment = getFragmentForTutorialType(tutorialType);
@@ -85,19 +86,22 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa
abstract TutorialController createController(TutorialType type);
abstract Class<? extends TutorialController> getControllerClass();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE);
mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext());
mEdgeBackGestureHandler.registerBackGestureAttemptCallback(this);
mNavBarGestureHandler = new NavBarGestureHandler(getContext());
}
@Override
public void onDestroy() {
super.onDestroy();
mEdgeBackGestureHandler.unregisterBackGestureAttemptCallback();
mNavBarGestureHandler.unregisterNavBarGestureAttemptCallback();
}
@Override
@@ -111,7 +115,7 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa
mEdgeBackGestureHandler.setInsets(systemInsets.left, systemInsets.right);
return insets;
});
mRootView.setOnTouchListener(mEdgeBackGestureHandler);
mRootView.setOnTouchListener(this);
mHandCoachingAnimation = new TutorialHandAnimation(getContext(), mRootView,
getHandAnimationResId());
return mRootView;
@@ -129,6 +133,13 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa
mHandCoachingAnimation.stop();
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
// Note: Using logical or to ensure both functions get called.
return mEdgeBackGestureHandler.onTouch(view, motionEvent)
| mNavBarGestureHandler.onTouch(view, motionEvent);
}
void onAttachedToWindow() {
mEdgeBackGestureHandler.setViewGroupParent((ViewGroup) getRootView());
}
@@ -138,8 +149,14 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa
}
void changeController(TutorialType tutorialType) {
mTutorialController = createController(tutorialType);
if (getControllerClass().isInstance(mTutorialController)) {
mTutorialController.setTutorialType(tutorialType);
} else {
mTutorialController = createController(tutorialType);
}
mTutorialController.transitToController();
mEdgeBackGestureHandler.registerBackGestureAttemptCallback(mTutorialController);
mNavBarGestureHandler.registerNavBarGestureAttemptCallback(mTutorialController);
mTutorialType = tutorialType;
}
@@ -157,13 +174,6 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa
return mHandCoachingAnimation;
}
@Override
public void onBackGestureAttempted(BackGestureResult result) {
if (mTutorialController != null) {
mTutorialController.onBackGestureAttempted(result);
}
}
void closeTutorial() {
FragmentActivity activity = getActivity();
if (activity != null) {
@@ -45,6 +45,7 @@ final class TutorialHandAnimation {
/** [Re]starts animation for the given tutorial. */
void startLoopedAnimation(TutorialType tutorialType) {
mHandCoachingView.setVisibility(View.VISIBLE);
if (mGestureAnimation.isRunning()) {
stop();
}
@@ -35,6 +35,11 @@ public class NavBarPosition {
mDisplayRotation = info.rotation;
}
public NavBarPosition(SysUINavigationMode.Mode mode, int displayRotation) {
mMode = mode;
mDisplayRotation = displayRotation;
}
public boolean isRightEdge() {
return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90;
}
@@ -41,9 +41,6 @@ public class QuickstepOnboardingPrefs extends OnboardingPrefs<BaseQuickstepLaunc
if (!getBoolean(HOME_BOUNCE_SEEN)) {
mStateManager.addStateListener(new StateListener() {
@Override
public void onStateTransitionStart(LauncherState toState) { }
@Override
public void onStateTransitionComplete(LauncherState finalState) {
boolean swipeUpEnabled = SysUINavigationMode.INSTANCE
@@ -69,9 +66,6 @@ public class QuickstepOnboardingPrefs extends OnboardingPrefs<BaseQuickstepLaunc
}
if (!shelfBounceSeen) {
mStateManager.addStateListener(new StateListener() {
@Override
public void onStateTransitionStart(LauncherState toState) { }
@Override
public void onStateTransitionComplete(LauncherState finalState) {
LauncherState prevState = mStateManager.getLastState();
@@ -87,9 +81,6 @@ public class QuickstepOnboardingPrefs extends OnboardingPrefs<BaseQuickstepLaunc
if (!hasReachedMaxCount(ALL_APPS_COUNT)) {
mStateManager.addStateListener(new StateListener() {
@Override
public void onStateTransitionStart(LauncherState toState) { }
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if (finalState == ALL_APPS) {
@@ -16,17 +16,13 @@
package com.android.quickstep.util;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM;
import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
import static com.android.launcher3.states.RotationHelper.FIXED_ROTATION_TRANSFORM_SETTING_NAME;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -44,19 +40,18 @@ import android.os.Handler;
import android.provider.Settings;
import android.util.Log;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
import android.view.Surface;
import androidx.annotation.IntDef;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.PortraitPagedViewHandler;
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.List;
import java.util.function.IntConsumer;
/**
* Container to hold orientation/rotation related information for Launcher.
@@ -71,6 +66,8 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
private static final String TAG = "RecentsOrientedState";
private static final boolean DEBUG = false;
private static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform";
private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
@@ -87,46 +84,79 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
private @SurfaceRotation int mDisplayRotation = ROTATION_0;
private @SurfaceRotation int mLauncherRotation = Surface.ROTATION_0;
public interface SystemRotationChangeListener {
void onSystemRotationChanged(boolean enabled);
}
// Launcher activity supports multiple orientation, but fallback activity does not
private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY = 1 << 0;
// Multiple orientation is only supported if density is < 600
private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY = 1 << 1;
// Feature flag controlling the multi-orientation feature
private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG = 1 << 2;
// Shared prefs for rotation, only if activity supports it
private static final int FLAG_HOME_ROTATION_ALLOWED_IN_PREFS = 1 << 3;
// If the user has enabled system rotation
private static final int FLAG_SYSTEM_ROTATION_ALLOWED = 1 << 4;
// Whether to rotation sensor is supported on the device
private static final int FLAG_ROTATION_WATCHER_SUPPORTED = 1 << 5;
// Whether to enable rotation watcher when multi-rotation is supported
private static final int FLAG_ROTATION_WATCHER_ENABLED = 1 << 6;
/**
* If {@code true} we default to {@link PortraitPagedViewHandler} and don't support any fake
* launcher orientations.
*/
private boolean mDisableMultipleOrientations;
private boolean mIsHomeRotationAllowed;
private boolean mIsSystemRotationAllowed;
private static final int MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE =
FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY
| FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY
| FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG;
private static final int MASK_ACTIVITY_ROTATING =
FLAG_HOME_ROTATION_ALLOWED_IN_PREFS | FLAG_SYSTEM_ROTATION_ALLOWED;
// State for which rotation watcher will be enabled.
// We skip it when home rotation is enabled as in that case, activity itself rotates
private static final int VALUE_ROTATION_WATCHER_ENABLED =
MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE | FLAG_SYSTEM_ROTATION_ALLOWED
| FLAG_ROTATION_WATCHER_SUPPORTED | FLAG_ROTATION_WATCHER_ENABLED;
private final ContentResolver mContentResolver;
private final SharedPreferences mSharedPrefs;
private final boolean mAllowConfigurationDefaultValue;
private List<SystemRotationChangeListener> mSystemRotationChangeListeners = new ArrayList<>();
private final OrientationEventListener mOrientationListener;
private final Matrix mTmpMatrix = new Matrix();
private final Matrix mTmpInverseMatrix = new Matrix();
public RecentsOrientedState(Context context) {
private int mFlags;
private int mPreviousRotation = ROTATION_0;
/**
* @param rotationChangeListener Callback for receiving rotation events when rotation watcher
* is enabled
* @see #setRotationWatcherEnabled(boolean)
*/
public RecentsOrientedState(Context context, boolean rotationSupportedByActivity,
IntConsumer rotationChangeListener) {
mContentResolver = context.getContentResolver();
mSharedPrefs = Utilities.getPrefs(context);
mOrientationListener = new OrientationEventListener(context) {
@Override
public void onOrientationChanged(int degrees) {
int newRotation = getRotationForUserDegreesRotated(degrees);
if (newRotation != mPreviousRotation) {
mPreviousRotation = newRotation;
rotationChangeListener.accept(newRotation);
}
}
};
mFlags = rotationSupportedByActivity ? FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY : 0;
Resources res = context.getResources();
int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
* res.getDisplayMetrics().densityDpi / DENSITY_DEVICE_STABLE;
mAllowConfigurationDefaultValue = originalSmallestWidth >= 600;
boolean isForcedRotation = Utilities.getFeatureFlagsPrefs(context)
.getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true)
&& !mAllowConfigurationDefaultValue;
UI_HELPER_EXECUTOR.execute(() -> {
if (context.checkSelfPermission(WRITE_SECURE_SETTINGS) == PERMISSION_GRANTED) {
Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
isForcedRotation ? 1 : 0);
}
});
disableMultipleOrientations(!isForcedRotation);
if (originalSmallestWidth < 600) {
mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
}
if (isFixedRotationTransformEnabled(context)) {
mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG;
}
if (mOrientationListener.canDetectOrientation()) {
mFlags |= FLAG_ROTATION_WATCHER_SUPPORTED;
}
}
/**
@@ -140,10 +170,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
public boolean update(
@SurfaceRotation int touchRotation, @SurfaceRotation int displayRotation,
@SurfaceRotation int launcherRotation) {
if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
return false;
}
if (mDisableMultipleOrientations) {
if (!isMultipleOrientationSupportedByDevice()) {
return false;
}
if (mDisplayRotation == displayRotation && mTouchRotation == touchRotation
@@ -155,8 +182,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
mDisplayRotation = displayRotation;
mTouchRotation = touchRotation;
if ((mIsHomeRotationAllowed && mIsSystemRotationAllowed) ||
mLauncherRotation == mTouchRotation) {
if (canLauncherRotate() || mLauncherRotation == mTouchRotation) {
// TODO(b/153476489) Need to determine when launcher is rotated
mOrientationHandler = PagedOrientationHandler.HOME_ROTATED;
if (DEBUG) {
@@ -178,14 +204,25 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
return true;
}
/**
* Setting this preference renders future calls to {@link #update(int, int, int)} as a no-op.
*/
public void disableMultipleOrientations(boolean disable) {
mDisableMultipleOrientations = disable;
if (disable) {
mDisplayRotation = mTouchRotation = ROTATION_0;
mOrientationHandler = PagedOrientationHandler.PORTRAIT;
private void setFlag(int mask, boolean enabled) {
boolean wasRotationEnabled = !TestProtocol.sDisableSensorRotation
&& mFlags == VALUE_ROTATION_WATCHER_ENABLED;
if (enabled) {
mFlags |= mask;
} else {
mFlags &= ~mask;
}
boolean isRotationEnabled = !TestProtocol.sDisableSensorRotation
&& mFlags == VALUE_ROTATION_WATCHER_ENABLED;
if (wasRotationEnabled != isRotationEnabled) {
UI_HELPER_EXECUTOR.execute(() -> {
if (isRotationEnabled) {
mOrientationListener.enable();
} else {
mOrientationListener.disable();
}
});
}
}
@@ -195,47 +232,49 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
}
private void updateAutoRotateSetting() {
try {
mIsSystemRotationAllowed = Settings.System.getInt(mContentResolver,
Settings.System.ACCELEROMETER_ROTATION) == 1;
} catch (Settings.SettingNotFoundException e) {
Log.e(TAG, "autorotate setting not found", e);
}
for (SystemRotationChangeListener listener : mSystemRotationChangeListeners) {
listener.onSystemRotationChanged(mIsSystemRotationAllowed);
}
setFlag(FLAG_SYSTEM_ROTATION_ALLOWED, Settings.System.getInt(mContentResolver,
Settings.System.ACCELEROMETER_ROTATION, 1) == 1);
}
private void updateHomeRotationSetting() {
mIsHomeRotationAllowed = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
mAllowConfigurationDefaultValue);
}
public void addSystemRotationChangeListener(SystemRotationChangeListener listener) {
mSystemRotationChangeListeners.add(listener);
listener.onSystemRotationChanged(mIsSystemRotationAllowed);
}
public void removeSystemRotationChangeListener(SystemRotationChangeListener listener) {
mSystemRotationChangeListeners.remove(listener);
setFlag(FLAG_HOME_ROTATION_ALLOWED_IN_PREFS,
mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false));
}
/**
* Initializes aany system values and registers corresponding change listeners. It must be
* paired with {@link #destroy()} call
*/
public void init() {
mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
mContentResolver.registerContentObserver(
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
false, mSystemAutoRotateObserver);
if (isMultipleOrientationSupportedByDevice()) {
mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
mContentResolver.registerContentObserver(
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
false, mSystemAutoRotateObserver);
}
initWithoutListeners();
}
/**
* Unregisters any previously registered listeners.
*/
public void destroy() {
if (isMultipleOrientationSupportedByDevice()) {
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
}
setRotationWatcherEnabled(false);
}
/**
* Initializes the OrientationState without attaching any listeners. This can be used when
* the object is short lived.
*/
public void initWithoutListeners() {
updateAutoRotateSetting();
updateHomeRotationSetting();
}
public void destroy() {
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
mSystemRotationChangeListeners.clear();
}
@SurfaceRotation
public int getDisplayRotation() {
return mDisplayRotation;
@@ -251,20 +290,24 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
return mLauncherRotation;
}
public boolean areMultipleLayoutOrientationsDisabled() {
return mDisableMultipleOrientations;
}
public boolean isSystemRotationAllowed() {
return mIsSystemRotationAllowed;
public boolean isMultipleOrientationSupportedByDevice() {
return (mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
== MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE;
}
public boolean isHomeRotationAllowed() {
return mIsHomeRotationAllowed;
return (mFlags & FLAG_HOME_ROTATION_ALLOWED_IN_PREFS) != 0;
}
public boolean canLauncherRotate() {
return isSystemRotationAllowed() && isHomeRotationAllowed();
return (mFlags & MASK_ACTIVITY_ROTATING) == MASK_ACTIVITY_ROTATING;
}
/**
* Enables or disables the rotation watcher for listening to rotation callbacks
*/
public void setRotationWatcherEnabled(boolean isEnabled) {
setFlag(FLAG_ROTATION_WATCHER_ENABLED, isEnabled);
}
public int getTouchRotationDegrees() {
@@ -388,4 +431,13 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
break;
}
}
/**
* Returns true if system can keep Launcher fixed to portrait layout even if the
* foreground app is rotated
*/
public static boolean isFixedRotationTransformEnabled(Context context) {
return Settings.Global.getInt(
context.getContentResolver(), FIXED_ROTATION_TRANSFORM_SETTING_NAME, 1) == 1;
}
}