Added logs to debug why multiple instances of RotationTouchHelper are being created to begin with. Ran labtest on CF, passes. Fixes: 177316094 Change-Id: Ibb980980c61965f456a84a9d85a72ec4ec6e0b19
509 lines
20 KiB
Java
509 lines
20 KiB
Java
/*
|
|
* Copyright (C) 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.quickstep;
|
|
|
|
import static android.view.MotionEvent.ACTION_CANCEL;
|
|
import static android.view.MotionEvent.ACTION_DOWN;
|
|
import static android.view.MotionEvent.ACTION_MOVE;
|
|
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
|
|
import static android.view.MotionEvent.ACTION_UP;
|
|
|
|
import static com.android.launcher3.states.RotationHelper.deltaRotation;
|
|
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
|
|
|
|
import android.content.res.Resources;
|
|
import android.graphics.Matrix;
|
|
import android.graphics.Point;
|
|
import android.graphics.RectF;
|
|
import android.util.Log;
|
|
import android.view.MotionEvent;
|
|
import android.view.Surface;
|
|
|
|
import com.android.launcher3.R;
|
|
import com.android.launcher3.ResourceUtils;
|
|
import com.android.launcher3.testing.TestProtocol;
|
|
import com.android.launcher3.util.DisplayController.Info;
|
|
|
|
import java.io.PrintWriter;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
|
|
/**
|
|
* Maintains state for supporting nav bars and tracking their gestures in multiple orientations.
|
|
* See {@link OrientationRectF#applyTransform(MotionEvent, boolean)} for transformation of
|
|
* MotionEvents from one orientation's coordinate space to another's.
|
|
*
|
|
* This class only supports single touch/pointer gesture tracking for touches started in a supported
|
|
* nav bar region.
|
|
*/
|
|
class OrientationTouchTransformer {
|
|
|
|
private static class CurrentDisplay {
|
|
public Point size;
|
|
public int rotation;
|
|
|
|
CurrentDisplay() {
|
|
this.size = new Point(0, 0);
|
|
this.rotation = 0;
|
|
}
|
|
|
|
CurrentDisplay(Point size, int rotation) {
|
|
this.size = size;
|
|
this.rotation = rotation;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "CurrentDisplay:"
|
|
+ " rotation: " + rotation
|
|
+ " size: " + size;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) return true;
|
|
if (o == null || getClass() != o.getClass()) return false;
|
|
|
|
CurrentDisplay display = (CurrentDisplay) o;
|
|
if (rotation != display.rotation) return false;
|
|
|
|
return Objects.equals(size, display.size);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(size, rotation);
|
|
}
|
|
};
|
|
|
|
private static final String TAG = "OrientationTouchTransformer";
|
|
private static final boolean DEBUG = true;
|
|
|
|
private static final int QUICKSTEP_ROTATION_UNINITIALIZED = -1;
|
|
|
|
private final Matrix mTmpMatrix = new Matrix();
|
|
private final float[] mTmpPoint = new float[2];
|
|
|
|
private final Map<CurrentDisplay, OrientationRectF> mSwipeTouchRegions =
|
|
new HashMap<CurrentDisplay, OrientationRectF>();
|
|
private final RectF mAssistantLeftRegion = new RectF();
|
|
private final RectF mAssistantRightRegion = new RectF();
|
|
private final RectF mOneHandedModeRegion = new RectF();
|
|
private CurrentDisplay mCurrentDisplay = new CurrentDisplay();
|
|
private int mNavBarGesturalHeight;
|
|
private final int mNavBarLargerGesturalHeight;
|
|
private boolean mEnableMultipleRegions;
|
|
private Resources mResources;
|
|
private OrientationRectF mLastRectTouched;
|
|
/**
|
|
* The rotation of the last touched nav bar, whether that be through the last region the user
|
|
* touched down on or valid rotation user turned their device to.
|
|
* Note this is different than
|
|
* {@link #mQuickStepStartingRotation} as it always updates its value on every touch whereas
|
|
* mQuickstepStartingRotation only updates when device rotation matches touch rotation.
|
|
*/
|
|
private int mActiveTouchRotation;
|
|
private SysUINavigationMode.Mode mMode;
|
|
private QuickStepContractInfo mContractInfo;
|
|
|
|
/**
|
|
* Represents if we're currently in a swipe "session" of sorts. If value is
|
|
* QUICKSTEP_ROTATION_UNINITIALIZED, then user has not tapped on an active nav region.
|
|
* Otherwise it will be the rotation of the display when the user first interacted with the
|
|
* active nav bar region.
|
|
* The "session" ends when {@link #enableMultipleRegions(boolean, Info)} is
|
|
* called - usually from a timeout or if user starts interacting w/ the foreground app.
|
|
*
|
|
* This is different than {@link #mLastRectTouched} as it can get reset by the system whereas
|
|
* the rect is purely used for tracking touch interactions and usually this "session" will
|
|
* outlast the touch interaction.
|
|
*/
|
|
private int mQuickStepStartingRotation = QUICKSTEP_ROTATION_UNINITIALIZED;
|
|
|
|
/** For testability */
|
|
interface QuickStepContractInfo {
|
|
float getWindowCornerRadius();
|
|
}
|
|
|
|
|
|
OrientationTouchTransformer(Resources resources, SysUINavigationMode.Mode mode,
|
|
QuickStepContractInfo contractInfo) {
|
|
mResources = resources;
|
|
mMode = mode;
|
|
mContractInfo = contractInfo;
|
|
mNavBarGesturalHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
|
|
mNavBarLargerGesturalHeight = ResourceUtils.getDimenByName(
|
|
ResourceUtils.NAVBAR_BOTTOM_GESTURE_LARGER_SIZE, resources,
|
|
mNavBarGesturalHeight);
|
|
}
|
|
|
|
private void refreshTouchRegion(Info info, Resources newRes) {
|
|
// 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
|
|
mResources = newRes;
|
|
mSwipeTouchRegions.clear();
|
|
resetSwipeRegions(info);
|
|
}
|
|
|
|
void setNavigationMode(SysUINavigationMode.Mode newMode, Info info,
|
|
Resources newRes) {
|
|
if (DEBUG) {
|
|
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "setNavigationMode new: " + newMode
|
|
+ " oldMode: " + mMode + " " + this);
|
|
}
|
|
if (mMode == newMode) {
|
|
return;
|
|
}
|
|
this.mMode = newMode;
|
|
refreshTouchRegion(info, newRes);
|
|
}
|
|
|
|
void setGesturalHeight(int newGesturalHeight, Info info, Resources newRes) {
|
|
if (mNavBarGesturalHeight == newGesturalHeight) {
|
|
return;
|
|
}
|
|
mNavBarGesturalHeight = newGesturalHeight;
|
|
refreshTouchRegion(info, newRes);
|
|
}
|
|
|
|
/**
|
|
* Sets the current nav bar region to listen to events for as determined by
|
|
* {@param info}. If multiple nav bar regions are enabled, then this region will be added
|
|
* alongside other regions.
|
|
* Ok to call multiple times
|
|
*
|
|
* @see #enableMultipleRegions(boolean, Info)
|
|
*/
|
|
void createOrAddTouchRegion(Info info) {
|
|
mCurrentDisplay = new CurrentDisplay(info.realSize, info.rotation);
|
|
|
|
if (mQuickStepStartingRotation > QUICKSTEP_ROTATION_UNINITIALIZED
|
|
&& mCurrentDisplay.rotation == mQuickStepStartingRotation) {
|
|
// User already was swiping and the current screen is same rotation as the starting one
|
|
// Remove active nav bars in other rotations except for the one we started out in
|
|
resetSwipeRegions(info);
|
|
return;
|
|
}
|
|
OrientationRectF region = mSwipeTouchRegions.get(mCurrentDisplay);
|
|
if (region != null) {
|
|
return;
|
|
}
|
|
|
|
if (mEnableMultipleRegions) {
|
|
mSwipeTouchRegions.put(mCurrentDisplay, createRegionForDisplay(info));
|
|
} else {
|
|
resetSwipeRegions(info);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Call when we want to start tracking nav bar touch regions in multiple orientations.
|
|
* ALSO, you BETTER call this with {@param enableMultipleRegions} set to false once you're done.
|
|
*
|
|
* @param enableMultipleRegions Set to true to start tracking multiple nav bar regions
|
|
* @param info The current displayInfo which will be the start of the quickswitch gesture
|
|
*/
|
|
void enableMultipleRegions(boolean enableMultipleRegions, Info info) {
|
|
mEnableMultipleRegions = enableMultipleRegions &&
|
|
mMode != SysUINavigationMode.Mode.TWO_BUTTONS;
|
|
if (mEnableMultipleRegions) {
|
|
mQuickStepStartingRotation = info.rotation;
|
|
} else {
|
|
mActiveTouchRotation = 0;
|
|
mQuickStepStartingRotation = QUICKSTEP_ROTATION_UNINITIALIZED;
|
|
}
|
|
resetSwipeRegions(info);
|
|
}
|
|
|
|
/**
|
|
* Call when removing multiple regions to swipe from, but still in active quickswitch mode (task
|
|
* list is still frozen).
|
|
* Ex. This would be called when user has quickswitched to the same app rotation that
|
|
* they started quickswitching in, indicating that extra nav regions can be ignored. Calling
|
|
* this will update the value of {@link #mActiveTouchRotation}
|
|
*
|
|
* @param displayInfo The display whos rotation will be used as the current active rotation
|
|
*/
|
|
void setSingleActiveRegion(Info displayInfo) {
|
|
mActiveTouchRotation = displayInfo.rotation;
|
|
resetSwipeRegions(displayInfo);
|
|
}
|
|
|
|
/**
|
|
* Only saves the swipe region represented by {@param region}, clears the
|
|
* rest from {@link #mSwipeTouchRegions}
|
|
* To be called whenever we want to stop tracking more than one swipe region.
|
|
* Ok to call multiple times.
|
|
*/
|
|
private void resetSwipeRegions(Info region) {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "clearing all regions except rotation: " + mCurrentDisplay.rotation);
|
|
}
|
|
|
|
mCurrentDisplay = new CurrentDisplay(region.realSize, region.rotation);
|
|
OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplay);
|
|
if (DEBUG) {
|
|
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "cached region: " + regionToKeep
|
|
+ " mCurrentDisplay: " + mCurrentDisplay + " " + this);
|
|
}
|
|
if (regionToKeep == null) {
|
|
regionToKeep = createRegionForDisplay(region);
|
|
}
|
|
mSwipeTouchRegions.clear();
|
|
if (DEBUG) {
|
|
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "adding region: " + regionToKeep
|
|
+ " mCurrentDisplay: " + mCurrentDisplay + " " + this);
|
|
}
|
|
mSwipeTouchRegions.put(mCurrentDisplay, regionToKeep);
|
|
updateAssistantRegions(regionToKeep);
|
|
}
|
|
|
|
private void resetSwipeRegions() {
|
|
OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplay);
|
|
mSwipeTouchRegions.clear();
|
|
if (regionToKeep != null) {
|
|
mSwipeTouchRegions.put(mCurrentDisplay, regionToKeep);
|
|
updateAssistantRegions(regionToKeep);
|
|
}
|
|
}
|
|
|
|
private OrientationRectF createRegionForDisplay(Info display) {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "creating rotation region for: " + mCurrentDisplay.rotation
|
|
+ " with mode: " + mMode + " displayRotation: " + display.rotation);
|
|
}
|
|
|
|
Point size = display.realSize;
|
|
int rotation = display.rotation;
|
|
int touchHeight = mNavBarGesturalHeight;
|
|
OrientationRectF orientationRectF =
|
|
new OrientationRectF(0, 0, size.x, size.y, rotation);
|
|
if (mMode == SysUINavigationMode.Mode.NO_BUTTON) {
|
|
orientationRectF.top = orientationRectF.bottom - touchHeight;
|
|
updateAssistantRegions(orientationRectF);
|
|
} else {
|
|
mAssistantLeftRegion.setEmpty();
|
|
mAssistantRightRegion.setEmpty();
|
|
int navbarSize = getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
|
|
if (DEBUG) {
|
|
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "else case mode: " + mMode
|
|
+ " getNavbarSize: " + navbarSize + " rotation: " + rotation + " " + this);
|
|
}
|
|
switch (rotation) {
|
|
case Surface.ROTATION_90:
|
|
orientationRectF.left = orientationRectF.right
|
|
- navbarSize;
|
|
break;
|
|
case Surface.ROTATION_270:
|
|
orientationRectF.right = orientationRectF.left
|
|
+ navbarSize;
|
|
break;
|
|
default:
|
|
orientationRectF.top = orientationRectF.bottom - touchHeight;
|
|
}
|
|
}
|
|
// One handed gestural only active on portrait mode
|
|
mOneHandedModeRegion.set(0, orientationRectF.bottom - mNavBarLargerGesturalHeight,
|
|
size.x, size.y);
|
|
|
|
return orientationRectF;
|
|
}
|
|
|
|
private void updateAssistantRegions(OrientationRectF orientationRectF) {
|
|
int navbarHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
|
|
int assistantWidth = mResources.getDimensionPixelSize(R.dimen.gestures_assistant_width);
|
|
float assistantHeight = Math.max(navbarHeight, mContractInfo.getWindowCornerRadius());
|
|
mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = orientationRectF.bottom;
|
|
mAssistantLeftRegion.top = mAssistantRightRegion.top =
|
|
orientationRectF.bottom - assistantHeight;
|
|
|
|
mAssistantLeftRegion.left = 0;
|
|
mAssistantLeftRegion.right = assistantWidth;
|
|
|
|
mAssistantRightRegion.right = orientationRectF.right;
|
|
mAssistantRightRegion.left = orientationRectF.right - assistantWidth;
|
|
}
|
|
|
|
boolean touchInAssistantRegion(MotionEvent ev) {
|
|
return mAssistantLeftRegion.contains(ev.getX(), ev.getY())
|
|
|| mAssistantRightRegion.contains(ev.getX(), ev.getY());
|
|
|
|
}
|
|
|
|
boolean touchInOneHandedModeRegion(MotionEvent ev) {
|
|
return mOneHandedModeRegion.contains(ev.getX(), ev.getY());
|
|
}
|
|
|
|
private int getNavbarSize(String resName) {
|
|
return ResourceUtils.getNavbarSize(resName, mResources);
|
|
}
|
|
|
|
boolean touchInValidSwipeRegions(float x, float y) {
|
|
if (TestProtocol.sDebugTracing) {
|
|
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "touchInValidSwipeRegions " + x + "," + y + " in "
|
|
+ mLastRectTouched + " this: " + this);
|
|
}
|
|
if (mLastRectTouched != null) {
|
|
return mLastRectTouched.contains(x, y);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int getCurrentActiveRotation() {
|
|
return mActiveTouchRotation;
|
|
}
|
|
|
|
int getQuickStepStartingRotation() {
|
|
return mQuickStepStartingRotation;
|
|
}
|
|
|
|
public void transform(MotionEvent event) {
|
|
int eventAction = event.getActionMasked();
|
|
switch (eventAction) {
|
|
case ACTION_MOVE: {
|
|
if (mLastRectTouched == null) {
|
|
return;
|
|
}
|
|
mLastRectTouched.applyTransform(event, true);
|
|
break;
|
|
}
|
|
case ACTION_CANCEL:
|
|
case ACTION_UP: {
|
|
if (mLastRectTouched == null) {
|
|
return;
|
|
}
|
|
mLastRectTouched.applyTransform(event, true);
|
|
mLastRectTouched = null;
|
|
break;
|
|
}
|
|
case ACTION_POINTER_DOWN:
|
|
case ACTION_DOWN: {
|
|
if (mLastRectTouched != null) {
|
|
return;
|
|
}
|
|
|
|
for (OrientationRectF rect : mSwipeTouchRegions.values()) {
|
|
if (TestProtocol.sDebugTracing) {
|
|
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "transform:DOWN, rect=" + rect);
|
|
}
|
|
if (rect == null) {
|
|
continue;
|
|
}
|
|
if (rect.applyTransform(event, false)) {
|
|
if (TestProtocol.sDebugTracing) {
|
|
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "setting mLastRectTouched");
|
|
}
|
|
mLastRectTouched = rect;
|
|
mActiveTouchRotation = rect.mRotation;
|
|
if (mEnableMultipleRegions
|
|
&& mCurrentDisplay.rotation == mActiveTouchRotation) {
|
|
// TODO(b/154580671) might make this block unnecessary
|
|
// Start a touch session for the default nav region for the display
|
|
mQuickStepStartingRotation = mLastRectTouched.mRotation;
|
|
resetSwipeRegions();
|
|
}
|
|
if (DEBUG) {
|
|
Log.d(TAG, "set active region: " + rect);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void dump(PrintWriter pw) {
|
|
pw.println("OrientationTouchTransformerState: ");
|
|
pw.println(" currentActiveRotation=" + getCurrentActiveRotation());
|
|
pw.println(" lastTouchedRegion=" + mLastRectTouched);
|
|
pw.println(" multipleRegionsEnabled=" + mEnableMultipleRegions);
|
|
StringBuilder regions = new StringBuilder(" currentTouchableRotations=");
|
|
for (CurrentDisplay key: mSwipeTouchRegions.keySet()) {
|
|
OrientationRectF rectF = mSwipeTouchRegions.get(key);
|
|
regions.append(rectF).append(" ");
|
|
}
|
|
pw.println(regions.toString());
|
|
pw.println(" mNavBarGesturalHeight=" + mNavBarGesturalHeight);
|
|
pw.println(" mNavBarLargerGesturalHeight=" + mNavBarLargerGesturalHeight);
|
|
pw.println(" mOneHandedModeRegion=" + mOneHandedModeRegion);
|
|
}
|
|
|
|
private class OrientationRectF extends RectF {
|
|
|
|
private int mRotation;
|
|
private float mHeight;
|
|
private float mWidth;
|
|
|
|
OrientationRectF(float left, float top, float right, float bottom, int rotation) {
|
|
super(left, top, right, bottom);
|
|
this.mRotation = rotation;
|
|
mHeight = bottom;
|
|
mWidth = right;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
String s = super.toString();
|
|
s += " rotation: " + mRotation;
|
|
return s;
|
|
}
|
|
|
|
@Override
|
|
public boolean contains(float x, float y) {
|
|
// Mark bottom right as included in the Rect (copied from Rect src, added "=" in "<=")
|
|
return left < right && top < bottom // check for empty first
|
|
&& x >= left && x <= right && y >= top && y <= bottom;
|
|
}
|
|
|
|
boolean applyTransform(MotionEvent event, boolean forceTransform) {
|
|
mTmpMatrix.reset();
|
|
postDisplayRotation(deltaRotation(mCurrentDisplay.rotation, mRotation),
|
|
mHeight, mWidth, mTmpMatrix);
|
|
if (forceTransform) {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "Transforming rotation due to forceTransform, "
|
|
+ "mCurrentRotation: " + mCurrentDisplay.rotation
|
|
+ "mRotation: " + mRotation
|
|
+ " this: " + this);
|
|
}
|
|
event.transform(mTmpMatrix);
|
|
return true;
|
|
}
|
|
mTmpPoint[0] = event.getX();
|
|
mTmpPoint[1] = event.getY();
|
|
mTmpMatrix.mapPoints(mTmpPoint);
|
|
|
|
if (DEBUG) {
|
|
Log.d(TAG, "original: " + event.getX() + ", " + event.getY()
|
|
+ " new: " + mTmpPoint[0] + ", " + mTmpPoint[1]
|
|
+ " rect: " + this + " forceTransform: " + forceTransform
|
|
+ " contains: " + contains(mTmpPoint[0], mTmpPoint[1])
|
|
+ " this: " + this);
|
|
}
|
|
|
|
if (contains(mTmpPoint[0], mTmpPoint[1])) {
|
|
event.transform(mTmpMatrix);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|