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:
@@ -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;
|
||||
|
||||
+8
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user