diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java index d58ab5d5b6..ff98701171 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java @@ -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 { @@ -114,4 +115,13 @@ final class BackGestureTutorialController extends TutorialController { break; } } + + @Override + public void onNavBarGestureAttempted(NavBarGestureResult result) { + if (mTutorialType == BACK_NAVIGATION_COMPLETE) { + if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) { + mTutorialFragment.closeTutorial(); + } + } + } } diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java index 0bf996dc8e..95b3c7924b 100644 --- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java @@ -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 { @@ -82,4 +83,21 @@ final class HomeGestureTutorialController extends TutorialController { break; } } + + @Override + public void onNavBarGestureAttempted(NavBarGestureResult result) { + switch (mTutorialType) { + case HOME_NAVIGATION: + if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) { + hideHandCoachingAnimation(); + mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE); + } + break; + case HOME_NAVIGATION_COMPLETE: + if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) { + mTutorialFragment.closeTutorial(); + } + break; + } + } } diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java new file mode 100644 index 0000000000..6d8caa2ec5 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java @@ -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); + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java index f0cb567161..69c61ce769 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java @@ -26,9 +26,11 @@ 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 { final TutorialFragment mTutorialFragment; final TutorialType mTutorialType; @@ -58,8 +60,6 @@ abstract class TutorialController { mActionButton = rootView.findViewById(R.id.gesture_tutorial_fragment_action_button); } - abstract void onBackGestureAttempted(BackGestureResult result); - @Nullable Integer getTitleStringId() { return null; @@ -86,6 +86,7 @@ abstract class TutorialController { void hideHandCoachingAnimation() { mHandCoachingAnimation.stop(); + mHandCoachingView.setVisibility(View.INVISIBLE); } @CallSuper diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java index 6346a9bd9f..3d025257c2 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java @@ -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); @@ -91,13 +92,14 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa 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 +113,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 +131,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()); } @@ -140,6 +149,8 @@ abstract class TutorialFragment extends Fragment implements BackGestureAttemptCa void changeController(TutorialType tutorialType) { mTutorialController = createController(tutorialType); mTutorialController.transitToController(); + mEdgeBackGestureHandler.registerBackGestureAttemptCallback(mTutorialController); + mNavBarGestureHandler.registerNavBarGestureAttemptCallback(mTutorialController); mTutorialType = tutorialType; } @@ -157,13 +168,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) { diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialHandAnimation.java b/quickstep/src/com/android/quickstep/interaction/TutorialHandAnimation.java index 5362aaf1b6..c810e43426 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialHandAnimation.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialHandAnimation.java @@ -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(); } diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java index 74e6b29295..0a98e1bfea 100644 --- a/quickstep/src/com/android/quickstep/util/NavBarPosition.java +++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java @@ -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; }