Merge "Refactor QuickStepController into Gestures"
This commit is contained in:
committed by
Android (Google) Code Review
commit
b4c2dc0c1f
@@ -36,7 +36,7 @@ public interface NavGesture extends Plugin {
|
||||
|
||||
public boolean onInterceptTouchEvent(MotionEvent event);
|
||||
|
||||
public void setBarState(boolean vertical, boolean isRtl);
|
||||
public void setBarState(boolean isRtl, int navBarPosition);
|
||||
|
||||
public void onDraw(Canvas canvas);
|
||||
|
||||
|
||||
@@ -263,6 +263,16 @@ public class ButtonDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
public void setTranslation(int x, int y, int z) {
|
||||
final int N = mViews.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
final View view = mViews.get(i);
|
||||
view.setTranslationX(x);
|
||||
view.setTranslationY(y);
|
||||
view.setTranslationZ(z);
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayList<View> getViews() {
|
||||
return mViews;
|
||||
}
|
||||
@@ -276,6 +286,11 @@ public class ButtonDispatcher {
|
||||
if (mImageDrawable != null) {
|
||||
mImageDrawable.setCallback(mCurrentView);
|
||||
}
|
||||
if (mCurrentView != null) {
|
||||
mCurrentView.setTranslationX(0);
|
||||
mCurrentView.setTranslationY(0);
|
||||
mCurrentView.setTranslationZ(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void setVertical(boolean vertical) {
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.systemui.statusbar.phone;
|
||||
|
||||
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.systemui.recents.OverviewProxyService;
|
||||
|
||||
/**
|
||||
* A back action when triggered will execute a back command
|
||||
*/
|
||||
public class NavigationBackAction extends NavigationGestureAction {
|
||||
|
||||
private static final String PULL_HOME_GO_BACK_PROP = "quickstepcontroller_homegoesback";
|
||||
private static final String BACK_AFTER_END_PROP =
|
||||
"quickstepcontroller_homegoesbackwhenend";
|
||||
private static final String NAVBAR_EXPERIMENTS_DISABLED = "navbarexperiments_disabled";
|
||||
private static final long BACK_BUTTON_FADE_OUT_ALPHA = 60;
|
||||
private static final long BACK_GESTURE_POLL_TIMEOUT = 1000;
|
||||
|
||||
private final Handler mHandler = new Handler();
|
||||
|
||||
private final Runnable mExecuteBackRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isEnabled() && canPerformAction()) {
|
||||
performBack();
|
||||
mHandler.postDelayed(this, BACK_GESTURE_POLL_TIMEOUT);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public NavigationBackAction(@NonNull NavigationBarView navigationBarView,
|
||||
@NonNull OverviewProxyService service) {
|
||||
super(navigationBarView, service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int requiresTouchDownHitTarget() {
|
||||
return HIT_TARGET_HOME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresDragWithHitTarget() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPerformAction() {
|
||||
return mProxySender.getBackButtonAlpha() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return swipeHomeGoBackGestureEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onGestureStart(MotionEvent event) {
|
||||
if (!QuickStepController.shouldhideBackButton(getContext())) {
|
||||
mNavigationBarView.getBackButton().setAlpha(0 /* alpha */, true /* animate */,
|
||||
BACK_BUTTON_FADE_OUT_ALPHA);
|
||||
}
|
||||
mHandler.removeCallbacks(mExecuteBackRunnable);
|
||||
if (!shouldExecuteBackOnUp()) {
|
||||
performBack();
|
||||
mHandler.postDelayed(mExecuteBackRunnable, BACK_GESTURE_POLL_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onGestureEnd() {
|
||||
mHandler.removeCallbacks(mExecuteBackRunnable);
|
||||
if (!QuickStepController.shouldhideBackButton(getContext())) {
|
||||
mNavigationBarView.getBackButton().setAlpha(
|
||||
mProxySender.getBackButtonAlpha(), true /* animate */);
|
||||
}
|
||||
if (shouldExecuteBackOnUp()) {
|
||||
performBack();
|
||||
}
|
||||
}
|
||||
|
||||
private void performBack() {
|
||||
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
|
||||
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
|
||||
mNavigationBarView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
||||
}
|
||||
|
||||
private boolean swipeHomeGoBackGestureEnabled() {
|
||||
return !getGlobalBoolean(NAVBAR_EXPERIMENTS_DISABLED)
|
||||
&& getGlobalBoolean(PULL_HOME_GO_BACK_PROP);
|
||||
}
|
||||
|
||||
private boolean shouldExecuteBackOnUp() {
|
||||
return !getGlobalBoolean(NAVBAR_EXPERIMENTS_DISABLED)
|
||||
&& getGlobalBoolean(BACK_AFTER_END_PROP);
|
||||
}
|
||||
|
||||
private void sendEvent(int action, int code) {
|
||||
long when = SystemClock.uptimeMillis();
|
||||
final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
|
||||
0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
|
||||
KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
|
||||
InputDevice.SOURCE_KEYBOARD);
|
||||
InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
||||
}
|
||||
}
|
||||
@@ -38,9 +38,11 @@ import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemProperties;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Display;
|
||||
import android.view.MotionEvent;
|
||||
@@ -49,6 +51,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManagerGlobal;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
@@ -143,6 +146,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
private RecentsOnboarding mRecentsOnboarding;
|
||||
private NotificationPanelView mPanelView;
|
||||
|
||||
private QuickScrubAction mQuickScrubAction;
|
||||
private QuickStepAction mQuickStepAction;
|
||||
private NavigationBackAction mBackAction;
|
||||
|
||||
/**
|
||||
* Helper that is responsible for showing the right toast when a disallowed activity operation
|
||||
* occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in
|
||||
@@ -299,6 +306,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
|
||||
mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
|
||||
mDeadZone = new DeadZone(this);
|
||||
|
||||
mQuickScrubAction = new QuickScrubAction(this, mOverviewProxyService);
|
||||
mQuickStepAction = new QuickStepAction(this, mOverviewProxyService);
|
||||
mBackAction = new NavigationBackAction(this, mOverviewProxyService);
|
||||
}
|
||||
|
||||
public BarTransitions getBarTransitions() {
|
||||
@@ -313,6 +324,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
mPanelView = panel;
|
||||
if (mGestureHelper instanceof QuickStepController) {
|
||||
((QuickStepController) mGestureHelper).setComponents(this);
|
||||
((QuickStepController) mGestureHelper).setGestureActions(mQuickStepAction,
|
||||
null /* swipeDownAction*/, mBackAction, mQuickScrubAction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,24 +769,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
mRecentsOnboarding.hide(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the button at the given {@param x} and {@param y}.
|
||||
*/
|
||||
ButtonDispatcher getButtonAtPosition(int x, int y) {
|
||||
for (int i = 0; i < mButtonDispatchers.size(); i++) {
|
||||
ButtonDispatcher button = mButtonDispatchers.valueAt(i);
|
||||
View buttonView = button.getCurrentView();
|
||||
if (buttonView != null) {
|
||||
buttonView.getHitRect(mTmpRect);
|
||||
offsetDescendantRectToMyCoords(buttonView, mTmpRect);
|
||||
if (mTmpRect.contains(x, y)) {
|
||||
return button;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishInflate() {
|
||||
mNavigationInflaterView = findViewById(R.id.navigation_inflater);
|
||||
@@ -908,7 +903,13 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
private void updateTaskSwitchHelper() {
|
||||
if (mGestureHelper == null) return;
|
||||
boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
|
||||
mGestureHelper.setBarState(mVertical, isRtl);
|
||||
int navBarPos = 0;
|
||||
try {
|
||||
navBarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition();
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Failed to get nav bar position.", e);
|
||||
}
|
||||
mGestureHelper.setBarState(isRtl, navBarPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1112,6 +1113,14 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
|
||||
mContextualButtonGroup.dump(pw);
|
||||
if (mGestureHelper != null) {
|
||||
pw.println("Navigation Gesture Actions {");
|
||||
pw.print(" "); pw.println("QuickScrub Enabled=" + mQuickScrubAction.isEnabled());
|
||||
pw.print(" "); pw.println("QuickScrub Active=" + mQuickScrubAction.isActive());
|
||||
pw.print(" "); pw.println("QuickStep Enabled=" + mQuickStepAction.isEnabled());
|
||||
pw.print(" "); pw.println("QuickStep Active=" + mQuickStepAction.isActive());
|
||||
pw.print(" "); pw.println("Back Gesture Enabled=" + mBackAction.isEnabled());
|
||||
pw.print(" "); pw.println("Back Gesture Active=" + mBackAction.isActive());
|
||||
pw.println("}");
|
||||
mGestureHelper.dump(pw);
|
||||
}
|
||||
mRecentsOnboarding.dump(pw);
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.systemui.statusbar.phone;
|
||||
|
||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
|
||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
|
||||
|
||||
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import android.view.WindowManagerPolicyConstants;
|
||||
import com.android.systemui.recents.OverviewProxyService;
|
||||
|
||||
/**
|
||||
* A gesture action that would be triggered and reassigned by {@link QuickStepController}
|
||||
*/
|
||||
public abstract class NavigationGestureAction {
|
||||
|
||||
protected final NavigationBarView mNavigationBarView;
|
||||
protected final OverviewProxyService mProxySender;
|
||||
|
||||
protected int mNavigationBarPosition;
|
||||
protected boolean mDragHorizontalPositive;
|
||||
protected boolean mDragVerticalPositive;
|
||||
private boolean mIsActive;
|
||||
|
||||
public NavigationGestureAction(@NonNull NavigationBarView navigationBarView,
|
||||
@NonNull OverviewProxyService service) {
|
||||
mNavigationBarView = navigationBarView;
|
||||
mProxySender = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass event that the state of the bar (such as rotation) has changed
|
||||
* @param changed if rotation or drag positive direction (such as ltr) has changed
|
||||
* @param navBarPos position of navigation bar
|
||||
* @param dragHorPositive direction of positive horizontal drag, could change with ltr changes
|
||||
* @param dragVerPositive direction of positive vertical drag, could change with ltr changes
|
||||
*/
|
||||
public void setBarState(boolean changed, int navBarPos, boolean dragHorPositive,
|
||||
boolean dragVerPositive) {
|
||||
mNavigationBarPosition = navBarPos;
|
||||
mDragHorizontalPositive = dragHorPositive;
|
||||
mDragVerticalPositive = dragVerPositive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the state of the action. Called when touch down occurs over the Navigation Bar.
|
||||
*/
|
||||
public void reset() {
|
||||
mIsActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the gesture and the action will be active
|
||||
* @param event the event that caused the gesture
|
||||
*/
|
||||
public void startGesture(MotionEvent event) {
|
||||
mIsActive = true;
|
||||
onGestureStart(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gesture has ended with action cancel or up and this action will not be active
|
||||
*/
|
||||
public void endGesture() {
|
||||
mIsActive = false;
|
||||
onGestureEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the action is currently active based on the gesture that triggered it. Only one action
|
||||
* can occur at a time
|
||||
* @return whether or not if this action has been triggered
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return mIsActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether or not this action can run if notification shade is shown
|
||||
*/
|
||||
public boolean canRunWhenNotificationsShowing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether or not this action triggers when starting a gesture from a certain hit target
|
||||
* If {@link HIT_TARGET_NONE} is specified then action does not need to be triggered by button
|
||||
*/
|
||||
public int requiresTouchDownHitTarget() {
|
||||
return HIT_TARGET_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether or not to move the button that started gesture over with user input drag
|
||||
*/
|
||||
public boolean requiresDragWithHitTarget() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell if the action is able to execute. Note that {@link #isEnabled()} must be true for this
|
||||
* to be checked. The difference between this and {@link #isEnabled()} is that this dependent
|
||||
* on the state of the navigation bar
|
||||
* @return true if action can execute after gesture activates based on current states
|
||||
*/
|
||||
public boolean canPerformAction() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell if action is enabled. Compared to {@link #canPerformAction()} this is based on settings
|
||||
* if the action is disabled for a particular gesture. For example a back action can be enabled
|
||||
* however if there is nothing to back to then {@link #canPerformAction()} should return false.
|
||||
* In this way if the action requires {@link #requiresDragWithHitTarget()} then if enabled, the
|
||||
* button can be dragged with a large dampening factor during the gesture but will not activate
|
||||
* the action.
|
||||
* @return true if this action is enabled and can run
|
||||
*/
|
||||
public abstract boolean isEnabled();
|
||||
|
||||
protected void onDarkIntensityChange(float intensity) {
|
||||
}
|
||||
|
||||
protected void onDraw(Canvas canvas) {
|
||||
}
|
||||
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
}
|
||||
|
||||
/**
|
||||
* When gesture starts, this will run to execute the action
|
||||
* @param event the event that triggered the gesture
|
||||
*/
|
||||
protected abstract void onGestureStart(MotionEvent event);
|
||||
|
||||
/**
|
||||
* Channels motion move events to the action to track the user inputs
|
||||
* @param x the x position
|
||||
* @param y the y position
|
||||
*/
|
||||
public void onGestureMove(int x, int y) {
|
||||
}
|
||||
|
||||
/**
|
||||
* When gesture ends, this will run from action up or cancel
|
||||
*/
|
||||
protected void onGestureEnd() {
|
||||
}
|
||||
|
||||
protected Context getContext() {
|
||||
return mNavigationBarView.getContext();
|
||||
}
|
||||
|
||||
protected boolean isNavBarVertical() {
|
||||
return mNavigationBarPosition == NAV_BAR_LEFT || mNavigationBarPosition == NAV_BAR_RIGHT;
|
||||
}
|
||||
|
||||
protected boolean getGlobalBoolean(@NonNull String key) {
|
||||
return QuickStepController.getBoolGlobalSetting(getContext(), key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.systemui.statusbar.phone;
|
||||
|
||||
import static com.android.systemui.Interpolators.ALPHA_IN;
|
||||
import static com.android.systemui.Interpolators.ALPHA_OUT;
|
||||
import static com.android.systemui.recents.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
|
||||
import static com.android.systemui.recents.OverviewProxyService.TAG_OPS;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RadialGradient;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Shader;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import android.util.FloatProperty;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.recents.OverviewProxyService;
|
||||
import com.android.systemui.shared.recents.utilities.Utilities;
|
||||
|
||||
/**
|
||||
* QuickScrub action to send to launcher to start quickscrub gesture
|
||||
*/
|
||||
public class QuickScrubAction extends NavigationGestureAction {
|
||||
private static final String TAG = "QuickScrubAction";
|
||||
|
||||
private static final float TRACK_SCALE = 0.95f;
|
||||
private static final float GRADIENT_WIDTH = .75f;
|
||||
private static final int ANIM_IN_DURATION_MS = 150;
|
||||
private static final int ANIM_OUT_DURATION_MS = 134;
|
||||
|
||||
private AnimatorSet mTrackAnimator;
|
||||
private View mCurrentNavigationBarView;
|
||||
|
||||
private float mTrackScale = TRACK_SCALE;
|
||||
private float mTrackAlpha;
|
||||
private float mHighlightCenter;
|
||||
private float mDarkIntensity;
|
||||
|
||||
private final int mTrackThickness;
|
||||
private final int mTrackEndPadding;
|
||||
private final Paint mTrackPaint = new Paint();
|
||||
private final Rect mTrackRect = new Rect();
|
||||
|
||||
private final FloatProperty<QuickScrubAction> mTrackAlphaProperty =
|
||||
new FloatProperty<QuickScrubAction>("TrackAlpha") {
|
||||
@Override
|
||||
public void setValue(QuickScrubAction action, float alpha) {
|
||||
mTrackAlpha = alpha;
|
||||
mNavigationBarView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(QuickScrubAction action) {
|
||||
return mTrackAlpha;
|
||||
}
|
||||
};
|
||||
|
||||
private final FloatProperty<QuickScrubAction> mTrackScaleProperty =
|
||||
new FloatProperty<QuickScrubAction>("TrackScale") {
|
||||
@Override
|
||||
public void setValue(QuickScrubAction action, float scale) {
|
||||
mTrackScale = scale;
|
||||
mNavigationBarView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(QuickScrubAction action) {
|
||||
return mTrackScale;
|
||||
}
|
||||
};
|
||||
|
||||
private final FloatProperty<QuickScrubAction> mNavBarAlphaProperty =
|
||||
new FloatProperty<QuickScrubAction>("NavBarAlpha") {
|
||||
@Override
|
||||
public void setValue(QuickScrubAction action, float alpha) {
|
||||
if (mCurrentNavigationBarView != null) {
|
||||
mCurrentNavigationBarView.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(QuickScrubAction action) {
|
||||
if (mCurrentNavigationBarView != null) {
|
||||
return mCurrentNavigationBarView.getAlpha();
|
||||
}
|
||||
return 1f;
|
||||
}
|
||||
};
|
||||
|
||||
private AnimatorListenerAdapter mQuickScrubEndListener = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (mCurrentNavigationBarView != null) {
|
||||
mCurrentNavigationBarView.setAlpha(1f);
|
||||
}
|
||||
mCurrentNavigationBarView = null;
|
||||
updateHighlight();
|
||||
}
|
||||
};
|
||||
|
||||
public QuickScrubAction(@NonNull NavigationBarView navigationBarView,
|
||||
@NonNull OverviewProxyService service) {
|
||||
super(navigationBarView, service);
|
||||
mTrackPaint.setAntiAlias(true);
|
||||
mTrackPaint.setDither(true);
|
||||
|
||||
final Resources res = navigationBarView.getResources();
|
||||
mTrackThickness = res.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_thickness);
|
||||
mTrackEndPadding = res.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_edge_padding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBarState(boolean changed, int navBarPos, boolean dragHorPositive,
|
||||
boolean dragVerPositive) {
|
||||
super.setBarState(changed, navBarPos, dragHorPositive, dragVerPositive);
|
||||
if (changed && isActive()) {
|
||||
// End quickscrub if the state changes mid-transition
|
||||
endQuickScrub(false /* animate */);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
|
||||
// End any existing quickscrub animations before starting the new transition
|
||||
if (mTrackAnimator != null) {
|
||||
mTrackAnimator.end();
|
||||
mTrackAnimator = null;
|
||||
}
|
||||
mCurrentNavigationBarView = mNavigationBarView.getCurrentView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
final int paddingLeft = mNavigationBarView.getPaddingLeft();
|
||||
final int paddingTop = mNavigationBarView.getPaddingTop();
|
||||
final int paddingRight = mNavigationBarView.getPaddingRight();
|
||||
final int paddingBottom = mNavigationBarView.getPaddingBottom();
|
||||
final int width = (right - left) - paddingRight - paddingLeft;
|
||||
final int height = (bottom - top) - paddingBottom - paddingTop;
|
||||
final int x1, x2, y1, y2;
|
||||
if (isNavBarVertical()) {
|
||||
x1 = (width - mTrackThickness) / 2 + paddingLeft;
|
||||
x2 = x1 + mTrackThickness;
|
||||
y1 = paddingTop + mTrackEndPadding;
|
||||
y2 = y1 + height - 2 * mTrackEndPadding;
|
||||
} else {
|
||||
y1 = (height - mTrackThickness) / 2 + paddingTop;
|
||||
y2 = y1 + mTrackThickness;
|
||||
x1 = mNavigationBarView.getPaddingStart() + mTrackEndPadding;
|
||||
x2 = x1 + width - 2 * mTrackEndPadding;
|
||||
}
|
||||
mTrackRect.set(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDarkIntensityChange(float intensity) {
|
||||
mDarkIntensity = intensity;
|
||||
updateHighlight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
mTrackPaint.setAlpha(Math.round(255f * mTrackAlpha));
|
||||
|
||||
// Scale the track, but apply the inverse scale from the nav bar
|
||||
final float radius = mTrackRect.height() / 2;
|
||||
canvas.save();
|
||||
float translate = Utilities.clamp(mHighlightCenter, mTrackRect.left, mTrackRect.right);
|
||||
canvas.translate(translate, 0);
|
||||
canvas.scale(mTrackScale / mNavigationBarView.getScaleX(),
|
||||
1f / mNavigationBarView.getScaleY(),
|
||||
mTrackRect.centerX(), mTrackRect.centerY());
|
||||
canvas.drawRoundRect(mTrackRect.left - translate, mTrackRect.top,
|
||||
mTrackRect.right - translate, mTrackRect.bottom, radius, radius, mTrackPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return mNavigationBarView.isQuickScrubEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onGestureStart(MotionEvent event) {
|
||||
updateHighlight();
|
||||
ObjectAnimator trackAnimator = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||
PropertyValuesHolder.ofFloat(mTrackAlphaProperty, 1f),
|
||||
PropertyValuesHolder.ofFloat(mTrackScaleProperty, 1f));
|
||||
trackAnimator.setInterpolator(ALPHA_IN);
|
||||
trackAnimator.setDuration(ANIM_IN_DURATION_MS);
|
||||
ObjectAnimator navBarAnimator = ObjectAnimator.ofFloat(this, mNavBarAlphaProperty, 0f);
|
||||
navBarAnimator.setInterpolator(ALPHA_OUT);
|
||||
navBarAnimator.setDuration(ANIM_OUT_DURATION_MS);
|
||||
mTrackAnimator = new AnimatorSet();
|
||||
mTrackAnimator.playTogether(trackAnimator, navBarAnimator);
|
||||
mTrackAnimator.start();
|
||||
|
||||
// Disable slippery for quick scrub to not cancel outside the nav bar
|
||||
mNavigationBarView.updateSlippery();
|
||||
|
||||
try {
|
||||
mProxySender.getProxy().onQuickScrubStart();
|
||||
if (DEBUG_OVERVIEW_PROXY) {
|
||||
Log.d(TAG_OPS, "Quick Scrub Start");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to send start of quick scrub.", e);
|
||||
}
|
||||
mProxySender.notifyQuickScrubStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGestureMove(int x, int y) {
|
||||
int trackSize, offset;
|
||||
if (isNavBarVertical()) {
|
||||
trackSize = mTrackRect.height();
|
||||
offset = y - mTrackRect.top;
|
||||
} else {
|
||||
offset = x - mTrackRect.left;
|
||||
trackSize = mTrackRect.width();
|
||||
}
|
||||
if (!mDragHorizontalPositive || !mDragVerticalPositive) {
|
||||
offset -= isNavBarVertical() ? mTrackRect.height() : mTrackRect.width();
|
||||
}
|
||||
float scrubFraction = Utilities.clamp(Math.abs(offset) * 1f / trackSize, 0, 1);
|
||||
try {
|
||||
mProxySender.getProxy().onQuickScrubProgress(scrubFraction);
|
||||
if (DEBUG_OVERVIEW_PROXY) {
|
||||
Log.d(TAG_OPS, "Quick Scrub Progress:" + scrubFraction);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to send progress of quick scrub.", e);
|
||||
}
|
||||
mHighlightCenter = x;
|
||||
mNavigationBarView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onGestureEnd() {
|
||||
endQuickScrub(true /* animate */);
|
||||
}
|
||||
|
||||
private void endQuickScrub(boolean animate) {
|
||||
animateEnd();
|
||||
try {
|
||||
mProxySender.getProxy().onQuickScrubEnd();
|
||||
if (DEBUG_OVERVIEW_PROXY) {
|
||||
Log.d(TAG_OPS, "Quick Scrub End");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to send end of quick scrub.", e);
|
||||
}
|
||||
if (!animate) {
|
||||
if (mTrackAnimator != null) {
|
||||
mTrackAnimator.end();
|
||||
mTrackAnimator = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateHighlight() {
|
||||
if (mTrackRect.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int colorBase, colorGrad;
|
||||
if (mDarkIntensity > 0.5f) {
|
||||
colorBase = getContext().getColor(R.color.quick_step_track_background_background_dark);
|
||||
colorGrad = getContext().getColor(R.color.quick_step_track_background_foreground_dark);
|
||||
} else {
|
||||
colorBase = getContext().getColor(R.color.quick_step_track_background_background_light);
|
||||
colorGrad = getContext().getColor(R.color.quick_step_track_background_foreground_light);
|
||||
}
|
||||
final RadialGradient mHighlight = new RadialGradient(0, mTrackRect.height() / 2,
|
||||
mTrackRect.width() * GRADIENT_WIDTH, colorGrad, colorBase,
|
||||
Shader.TileMode.CLAMP);
|
||||
mTrackPaint.setShader(mHighlight);
|
||||
}
|
||||
|
||||
private void animateEnd() {
|
||||
if (mTrackAnimator != null) {
|
||||
mTrackAnimator.cancel();
|
||||
}
|
||||
|
||||
ObjectAnimator trackAnimator = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||
PropertyValuesHolder.ofFloat(mTrackAlphaProperty, 0f),
|
||||
PropertyValuesHolder.ofFloat(mTrackScaleProperty, TRACK_SCALE));
|
||||
trackAnimator.setInterpolator(ALPHA_OUT);
|
||||
trackAnimator.setDuration(ANIM_OUT_DURATION_MS);
|
||||
ObjectAnimator navBarAnimator = ObjectAnimator.ofFloat(this, mNavBarAlphaProperty, 1f);
|
||||
navBarAnimator.setInterpolator(ALPHA_IN);
|
||||
navBarAnimator.setDuration(ANIM_IN_DURATION_MS);
|
||||
mTrackAnimator = new AnimatorSet();
|
||||
mTrackAnimator.playTogether(trackAnimator, navBarAnimator);
|
||||
mTrackAnimator.addListener(mQuickScrubEndListener);
|
||||
mTrackAnimator.start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.systemui.statusbar.phone;
|
||||
|
||||
import static com.android.systemui.recents.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
|
||||
import static com.android.systemui.recents.OverviewProxyService.TAG_OPS;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.systemui.recents.OverviewProxyService;
|
||||
|
||||
/**
|
||||
* QuickStep action to send to launcher to start overview
|
||||
*/
|
||||
public class QuickStepAction extends NavigationGestureAction {
|
||||
private static final String TAG = "QuickStepAction";
|
||||
|
||||
public QuickStepAction(@NonNull NavigationBarView navigationBarView,
|
||||
@NonNull OverviewProxyService service) {
|
||||
super(navigationBarView, service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRunWhenNotificationsShowing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return mNavigationBarView.isQuickStepSwipeUpEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGestureStart(MotionEvent event) {
|
||||
try {
|
||||
mProxySender.getProxy().onQuickStep(event);
|
||||
if (DEBUG_OVERVIEW_PROXY) {
|
||||
Log.d(TAG_OPS, "Quick Step Start");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to send quick step started.", e);
|
||||
}
|
||||
mProxySender.notifyQuickStepStarted();
|
||||
}
|
||||
}
|
||||
@@ -18,187 +18,96 @@ package com.android.systemui.statusbar.phone;
|
||||
|
||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
|
||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
|
||||
import static com.android.systemui.Interpolators.ALPHA_IN;
|
||||
import static com.android.systemui.Interpolators.ALPHA_OUT;
|
||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
|
||||
|
||||
import static com.android.systemui.recents.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
|
||||
import static com.android.systemui.recents.OverviewProxyService.TAG_OPS;
|
||||
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
|
||||
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE;
|
||||
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
|
||||
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
|
||||
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RadialGradient;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Shader;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.Settings;
|
||||
import android.util.FloatProperty;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewPropertyAnimator;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.Interpolators;
|
||||
import com.android.systemui.recents.OverviewProxyService;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysUiServiceProvider;
|
||||
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.recents.OverviewProxyService;
|
||||
import com.android.systemui.SysUiServiceProvider;
|
||||
import com.android.systemui.shared.recents.IOverviewProxy;
|
||||
import com.android.systemui.shared.recents.utilities.Utilities;
|
||||
import com.android.systemui.shared.system.NavigationBarCompat;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* Class to detect gestures on the navigation bar and implement quick scrub.
|
||||
* Note that the variables in this class horizontal and vertical represents horizontal always
|
||||
* aligned with along the navigation bar).
|
||||
*/
|
||||
public class QuickStepController implements GestureHelper {
|
||||
|
||||
private static final String TAG = "QuickStepController";
|
||||
private static final int ANIM_IN_DURATION_MS = 150;
|
||||
private static final int ANIM_OUT_DURATION_MS = 134;
|
||||
private static final float TRACK_SCALE = 0.95f;
|
||||
private static final float GRADIENT_WIDTH = .75f;
|
||||
|
||||
/** Experiment to swipe home button left to execute a back key press */
|
||||
private static final String PULL_HOME_GO_BACK_PROP = "quickstepcontroller_homegoesback";
|
||||
private static final String HIDE_BACK_BUTTON_PROP = "quickstepcontroller_hideback";
|
||||
private static final String BACK_AFTER_END_PROP
|
||||
= "quickstepcontroller_homegoesbackwhenend";
|
||||
private static final String NAVBAR_EXPERIMENTS_DISABLED = "navbarexperiments_disabled";
|
||||
private static final long BACK_BUTTON_FADE_OUT_ALPHA = 60;
|
||||
private static final long BACK_BUTTON_FADE_IN_ALPHA = 150;
|
||||
private static final long BACK_GESTURE_POLL_TIMEOUT = 1000;
|
||||
|
||||
/** When the home-swipe-back gesture is disallowed, make it harder to pull */
|
||||
private static final float DISALLOW_GESTURE_DAMPING_FACTOR = 0.16f;
|
||||
|
||||
private static final int ACTION_SWIPE_UP_INDEX = 0;
|
||||
private static final int ACTION_SWIPE_DOWN_INDEX = 1;
|
||||
private static final int ACTION_SWIPE_LEFT_INDEX = 2;
|
||||
private static final int ACTION_SWIPE_RIGHT_INDEX = 3;
|
||||
private static final int MAX_GESTURES = 4;
|
||||
|
||||
private NavigationBarView mNavigationBarView;
|
||||
|
||||
private boolean mQuickScrubActive;
|
||||
private boolean mAllowGestureDetection;
|
||||
private boolean mBackGestureActive;
|
||||
private boolean mCanPerformBack;
|
||||
private boolean mQuickStepStarted;
|
||||
private boolean mNotificationsVisibleOnDown;
|
||||
private int mTouchDownX;
|
||||
private int mTouchDownY;
|
||||
private boolean mDragPositive;
|
||||
private boolean mIsVertical;
|
||||
private boolean mDragHPositive;
|
||||
private boolean mDragVPositive;
|
||||
private boolean mIsRTL;
|
||||
private float mTrackAlpha;
|
||||
private float mTrackScale = TRACK_SCALE;
|
||||
private int mNavBarPosition;
|
||||
private float mDarkIntensity;
|
||||
private RadialGradient mHighlight;
|
||||
private float mHighlightCenter;
|
||||
private AnimatorSet mTrackAnimator;
|
||||
private ViewPropertyAnimator mHomeAnimator;
|
||||
private ViewPropertyAnimator mDragBtnAnimator;
|
||||
private ButtonDispatcher mHitTarget;
|
||||
private View mCurrentNavigationBarView;
|
||||
private boolean mIsInScreenPinning;
|
||||
private boolean mGestureHorizontalDragsButton;
|
||||
private boolean mGestureVerticalDragsButton;
|
||||
private boolean mGestureTrackPositive;
|
||||
|
||||
private NavigationGestureAction mCurrentAction;
|
||||
private NavigationGestureAction[] mGestureActions = new NavigationGestureAction[MAX_GESTURES];
|
||||
|
||||
private final Handler mHandler = new Handler();
|
||||
private final Rect mTrackRect = new Rect();
|
||||
private final OverviewProxyService mOverviewEventSender;
|
||||
private final int mTrackThickness;
|
||||
private final int mTrackEndPadding;
|
||||
private final int mHomeBackGestureDragLimit;
|
||||
private final Context mContext;
|
||||
private final StatusBar mStatusBar;
|
||||
private final Matrix mTransformGlobalMatrix = new Matrix();
|
||||
private final Matrix mTransformLocalMatrix = new Matrix();
|
||||
private final Paint mTrackPaint = new Paint();
|
||||
|
||||
private final FloatProperty<QuickStepController> mTrackAlphaProperty =
|
||||
new FloatProperty<QuickStepController>("TrackAlpha") {
|
||||
@Override
|
||||
public void setValue(QuickStepController controller, float alpha) {
|
||||
mTrackAlpha = alpha;
|
||||
mNavigationBarView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(QuickStepController controller) {
|
||||
return mTrackAlpha;
|
||||
}
|
||||
};
|
||||
|
||||
private final FloatProperty<QuickStepController> mTrackScaleProperty =
|
||||
new FloatProperty<QuickStepController>("TrackScale") {
|
||||
@Override
|
||||
public void setValue(QuickStepController controller, float scale) {
|
||||
mTrackScale = scale;
|
||||
mNavigationBarView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(QuickStepController controller) {
|
||||
return mTrackScale;
|
||||
}
|
||||
};
|
||||
|
||||
private final FloatProperty<QuickStepController> mNavBarAlphaProperty =
|
||||
new FloatProperty<QuickStepController>("NavBarAlpha") {
|
||||
@Override
|
||||
public void setValue(QuickStepController controller, float alpha) {
|
||||
if (mCurrentNavigationBarView != null) {
|
||||
mCurrentNavigationBarView.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(QuickStepController controller) {
|
||||
if (mCurrentNavigationBarView != null) {
|
||||
return mCurrentNavigationBarView.getAlpha();
|
||||
}
|
||||
return 1f;
|
||||
}
|
||||
};
|
||||
|
||||
private AnimatorListenerAdapter mQuickScrubEndListener = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
resetQuickScrub();
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable mExecuteBackRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (canPerformHomeBackGesture()) {
|
||||
performBack();
|
||||
mHandler.postDelayed(this, BACK_GESTURE_POLL_TIMEOUT);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public QuickStepController(Context context) {
|
||||
final Resources res = context.getResources();
|
||||
mContext = context;
|
||||
mStatusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
|
||||
mOverviewEventSender = Dependency.get(OverviewProxyService.class);
|
||||
mTrackThickness = res.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_thickness);
|
||||
mTrackEndPadding = res.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_edge_padding);
|
||||
mHomeBackGestureDragLimit =
|
||||
res.getDimensionPixelSize(R.dimen.nav_home_back_gesture_drag_limit);
|
||||
mTrackPaint.setAntiAlias(true);
|
||||
mTrackPaint.setDither(true);
|
||||
}
|
||||
|
||||
public void setComponents(NavigationBarView navigationBarView) {
|
||||
@@ -209,6 +118,31 @@ public class QuickStepController implements GestureHelper {
|
||||
: View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set each gesture an action. After set the gestures triggered will run the actions attached.
|
||||
* @param swipeUpAction action after swiping up
|
||||
* @param swipeDownAction action after swiping down
|
||||
* @param swipeLeftAction action after swiping left
|
||||
* @param swipeRightAction action after swiping right
|
||||
*/
|
||||
public void setGestureActions(@Nullable NavigationGestureAction swipeUpAction,
|
||||
@Nullable NavigationGestureAction swipeDownAction,
|
||||
@Nullable NavigationGestureAction swipeLeftAction,
|
||||
@Nullable NavigationGestureAction swipeRightAction) {
|
||||
mGestureActions[ACTION_SWIPE_UP_INDEX] = swipeUpAction;
|
||||
mGestureActions[ACTION_SWIPE_DOWN_INDEX] = swipeDownAction;
|
||||
mGestureActions[ACTION_SWIPE_LEFT_INDEX] = swipeLeftAction;
|
||||
mGestureActions[ACTION_SWIPE_RIGHT_INDEX] = swipeRightAction;
|
||||
|
||||
// Set the current state to all actions
|
||||
for (NavigationGestureAction action: mGestureActions) {
|
||||
if (action != null) {
|
||||
action.setBarState(true, mNavBarPosition, mDragHPositive, mDragVPositive);
|
||||
action.onDarkIntensityChange(mDarkIntensity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if we want to intercept touch events for quick scrub and prevent proxying the
|
||||
* event to the overview service.
|
||||
@@ -242,8 +176,10 @@ public class QuickStepController implements GestureHelper {
|
||||
private boolean handleTouchEvent(MotionEvent event) {
|
||||
final boolean deadZoneConsumed =
|
||||
mNavigationBarView.getDownHitTarget() == HIT_TARGET_DEAD_ZONE;
|
||||
if (mOverviewEventSender.getProxy() == null || (!mNavigationBarView.isQuickScrubEnabled()
|
||||
&& !mNavigationBarView.isQuickStepSwipeUpEnabled())) {
|
||||
|
||||
// Requires proxy and an active gesture or able to perform any gesture to continue
|
||||
if (mOverviewEventSender.getProxy() == null
|
||||
|| (mCurrentAction == null && !canPerformAnyAction())) {
|
||||
return deadZoneConsumed;
|
||||
}
|
||||
mNavigationBarView.requestUnbufferedDispatch(event);
|
||||
@@ -255,33 +191,45 @@ public class QuickStepController implements GestureHelper {
|
||||
int y = (int) event.getY();
|
||||
mIsInScreenPinning = mNavigationBarView.inScreenPinning();
|
||||
|
||||
// End any existing quickscrub animations before starting the new transition
|
||||
if (mTrackAnimator != null) {
|
||||
mTrackAnimator.end();
|
||||
mTrackAnimator = null;
|
||||
for (NavigationGestureAction gestureAction: mGestureActions) {
|
||||
if (gestureAction != null) {
|
||||
gestureAction.reset();
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentNavigationBarView = mNavigationBarView.getCurrentView();
|
||||
mHitTarget = mNavigationBarView.getButtonAtPosition(x, y);
|
||||
// Valid buttons to drag over
|
||||
switch (mNavigationBarView.getDownHitTarget()) {
|
||||
case HIT_TARGET_BACK:
|
||||
mHitTarget = mNavigationBarView.getBackButton();
|
||||
break;
|
||||
case HIT_TARGET_HOME:
|
||||
mHitTarget = mNavigationBarView.getHomeButton();
|
||||
break;
|
||||
case HIT_TARGET_OVERVIEW:
|
||||
mHitTarget = mNavigationBarView.getRecentsButton();
|
||||
break;
|
||||
default:
|
||||
mHitTarget = null;
|
||||
break;
|
||||
}
|
||||
if (mHitTarget != null) {
|
||||
// Pre-emptively delay the touch feedback for the button that we just touched
|
||||
mHitTarget.setDelayTouchFeedback(true);
|
||||
}
|
||||
mTouchDownX = x;
|
||||
mTouchDownY = y;
|
||||
mGestureHorizontalDragsButton = false;
|
||||
mGestureVerticalDragsButton = false;
|
||||
mTransformGlobalMatrix.set(Matrix.IDENTITY_MATRIX);
|
||||
mTransformLocalMatrix.set(Matrix.IDENTITY_MATRIX);
|
||||
mNavigationBarView.transformMatrixToGlobal(mTransformGlobalMatrix);
|
||||
mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
|
||||
mQuickStepStarted = false;
|
||||
mBackGestureActive = false;
|
||||
mAllowGestureDetection = true;
|
||||
mNotificationsVisibleOnDown = !mNavigationBarView.isNotificationsFullyCollapsed();
|
||||
mCanPerformBack = canPerformHomeBackGesture();
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
if (mQuickStepStarted || !mAllowGestureDetection){
|
||||
if (!mAllowGestureDetection) {
|
||||
break;
|
||||
}
|
||||
int x = (int) event.getX();
|
||||
@@ -289,108 +237,132 @@ public class QuickStepController implements GestureHelper {
|
||||
int xDiff = Math.abs(x - mTouchDownX);
|
||||
int yDiff = Math.abs(y - mTouchDownY);
|
||||
|
||||
boolean exceededScrubTouchSlop, exceededSwipeUpTouchSlop;
|
||||
int pos, touchDown, offset, trackSize;
|
||||
boolean exceededSwipeHorizontalTouchSlop, exceededSwipeVerticalTouchSlop;
|
||||
int posH, touchDownH, posV, touchDownV;
|
||||
|
||||
if (mIsVertical) {
|
||||
exceededScrubTouchSlop =
|
||||
if (isNavBarVertical()) {
|
||||
exceededSwipeHorizontalTouchSlop =
|
||||
yDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && yDiff > xDiff;
|
||||
exceededSwipeUpTouchSlop =
|
||||
exceededSwipeVerticalTouchSlop =
|
||||
xDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && xDiff > yDiff;
|
||||
pos = y;
|
||||
touchDown = mTouchDownY;
|
||||
offset = pos - mTrackRect.top;
|
||||
trackSize = mTrackRect.height();
|
||||
posH = y;
|
||||
touchDownH = mTouchDownY;
|
||||
posV = x;
|
||||
touchDownV = mTouchDownX;
|
||||
} else {
|
||||
exceededScrubTouchSlop =
|
||||
exceededSwipeHorizontalTouchSlop =
|
||||
xDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && xDiff > yDiff;
|
||||
exceededSwipeUpTouchSlop =
|
||||
exceededSwipeVerticalTouchSlop =
|
||||
yDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && yDiff > xDiff;
|
||||
pos = x;
|
||||
touchDown = mTouchDownX;
|
||||
offset = pos - mTrackRect.left;
|
||||
trackSize = mTrackRect.width();
|
||||
}
|
||||
// Decide to start quickstep if dragging away from the navigation bar, otherwise in
|
||||
// the parallel direction, decide to start quickscrub. Only one may run.
|
||||
if (!mBackGestureActive && !mQuickScrubActive && exceededSwipeUpTouchSlop) {
|
||||
if (mNavigationBarView.isQuickStepSwipeUpEnabled()
|
||||
&& !mNotificationsVisibleOnDown) {
|
||||
startQuickStep(event);
|
||||
}
|
||||
break;
|
||||
posH = x;
|
||||
touchDownH = mTouchDownX;
|
||||
posV = y;
|
||||
touchDownV = mTouchDownY;
|
||||
}
|
||||
|
||||
// Do not handle quick scrub if disabled
|
||||
if (!mNavigationBarView.isQuickScrubEnabled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!mDragPositive) {
|
||||
offset -= mIsVertical ? mTrackRect.height() : mTrackRect.width();
|
||||
}
|
||||
|
||||
final boolean allowDrag = !mDragPositive
|
||||
? offset < 0 && pos < touchDown : offset >= 0 && pos > touchDown;
|
||||
float scrubFraction = Utilities.clamp(Math.abs(offset) * 1f / trackSize, 0, 1);
|
||||
if (!mQuickScrubActive && !mBackGestureActive && exceededScrubTouchSlop) {
|
||||
// Passing the drag slop then touch slop will start quick step
|
||||
if (allowDrag) {
|
||||
startQuickScrub();
|
||||
} else if (swipeHomeGoBackGestureEnabled(mContext)
|
||||
&& mNavigationBarView.getDownHitTarget() == HIT_TARGET_HOME
|
||||
&& mDragPositive ? pos < touchDown : pos > touchDown) {
|
||||
startBackGesture();
|
||||
}
|
||||
}
|
||||
|
||||
if (mQuickScrubActive && (mDragPositive && offset >= 0
|
||||
|| !mDragPositive && offset <= 0)) {
|
||||
try {
|
||||
mOverviewEventSender.getProxy().onQuickScrubProgress(scrubFraction);
|
||||
if (DEBUG_OVERVIEW_PROXY) {
|
||||
Log.d(TAG_OPS, "Quick Scrub Progress:" + scrubFraction);
|
||||
if (mCurrentAction != null) {
|
||||
// Gesture started, provide positions to the current action
|
||||
mCurrentAction.onGestureMove(x, y);
|
||||
} else {
|
||||
// Detect gesture and try to execute an action, only one can run at a time
|
||||
if (exceededSwipeVerticalTouchSlop) {
|
||||
if (mDragVPositive ? (posV < touchDownV) : (posV > touchDownV)) {
|
||||
// Swiping up gesture
|
||||
tryToStartGesture(mGestureActions[ACTION_SWIPE_UP_INDEX],
|
||||
false /* alignedWithNavBar */, false /* positiveDirection */,
|
||||
event);
|
||||
} else {
|
||||
// Swiping down gesture
|
||||
tryToStartGesture(mGestureActions[ACTION_SWIPE_DOWN_INDEX],
|
||||
false /* alignedWithNavBar */, true /* positiveDirection */,
|
||||
event);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to send progress of quick scrub.", e);
|
||||
}
|
||||
mHighlightCenter = x;
|
||||
mNavigationBarView.invalidate();
|
||||
} else if (mBackGestureActive) {
|
||||
int diff = pos - touchDown;
|
||||
// If dragging the incorrect direction after starting back gesture or unable
|
||||
// to execute back functionality, then move home but dampen its distance
|
||||
if (!mCanPerformBack || (mDragPositive ? diff > 0 : diff < 0)) {
|
||||
diff *= DISALLOW_GESTURE_DAMPING_FACTOR;
|
||||
} if (Math.abs(diff) > mHomeBackGestureDragLimit) {
|
||||
// Once the user drags the home button past a certain limit, the distance
|
||||
// will lessen as the home button dampens showing that it was pulled too far
|
||||
float distanceAfterDragLimit = (Math.abs(diff) - mHomeBackGestureDragLimit)
|
||||
* DISALLOW_GESTURE_DAMPING_FACTOR;
|
||||
diff = (int)(distanceAfterDragLimit + mHomeBackGestureDragLimit);
|
||||
if (mDragPositive) {
|
||||
diff *= -1;
|
||||
} else if (exceededSwipeHorizontalTouchSlop) {
|
||||
if (mDragHPositive ? (posH < touchDownH) : (posH > touchDownH)) {
|
||||
// Swiping left (ltr) gesture
|
||||
tryToStartGesture(mGestureActions[ACTION_SWIPE_LEFT_INDEX],
|
||||
true /* alignedWithNavBar */, false /* positiveDirection */,
|
||||
event);
|
||||
} else {
|
||||
// Swiping right (ltr) gesture
|
||||
tryToStartGesture(mGestureActions[ACTION_SWIPE_RIGHT_INDEX],
|
||||
true /* alignedWithNavBar */, true /* positiveDirection */,
|
||||
event);
|
||||
}
|
||||
}
|
||||
moveHomeButton(diff);
|
||||
}
|
||||
|
||||
handleDragHitTarget(mGestureHorizontalDragsButton ? posH : posV,
|
||||
mGestureHorizontalDragsButton ? touchDownH : touchDownV);
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
endQuickScrub(true /* animate */);
|
||||
endBackGesture();
|
||||
if (mCurrentAction != null) {
|
||||
mCurrentAction.endGesture();
|
||||
mCurrentAction = null;
|
||||
}
|
||||
|
||||
// Return the hit target back to its original position
|
||||
if (mHitTarget != null) {
|
||||
final View button = mHitTarget.getCurrentView();
|
||||
if (mGestureHorizontalDragsButton || mGestureVerticalDragsButton) {
|
||||
mDragBtnAnimator = button.animate().setDuration(BACK_BUTTON_FADE_IN_ALPHA)
|
||||
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
|
||||
if (mGestureVerticalDragsButton ^ isNavBarVertical()) {
|
||||
mDragBtnAnimator.translationY(0);
|
||||
} else {
|
||||
mDragBtnAnimator.translationX(0);
|
||||
}
|
||||
mDragBtnAnimator.start();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (shouldProxyEvents(action)) {
|
||||
proxyMotionEvents(event);
|
||||
}
|
||||
return mBackGestureActive || mQuickScrubActive || mQuickStepStarted || deadZoneConsumed;
|
||||
return mCurrentAction != null || deadZoneConsumed;
|
||||
}
|
||||
|
||||
private void handleDragHitTarget(int position, int touchDown) {
|
||||
// Drag the hit target if gesture action requires it
|
||||
if (mHitTarget != null && (mGestureVerticalDragsButton || mGestureHorizontalDragsButton)) {
|
||||
final View button = mHitTarget.getCurrentView();
|
||||
if (mDragBtnAnimator != null) {
|
||||
mDragBtnAnimator.cancel();
|
||||
mDragBtnAnimator = null;
|
||||
}
|
||||
|
||||
int diff = position - touchDown;
|
||||
// If dragging the incorrect direction after starting gesture or unable to
|
||||
// execute tried action, then move the button but dampen its distance
|
||||
if (mCurrentAction == null || (mGestureTrackPositive ? diff < 0 : diff > 0)) {
|
||||
diff *= DISALLOW_GESTURE_DAMPING_FACTOR;
|
||||
} else if (Math.abs(diff) > mHomeBackGestureDragLimit) {
|
||||
// Once the user drags the button past a certain limit, the distance will
|
||||
// lessen as the button dampens that it was pulled too far
|
||||
float distanceAfterDragLimit = (Math.abs(diff) - mHomeBackGestureDragLimit)
|
||||
* DISALLOW_GESTURE_DAMPING_FACTOR;
|
||||
diff = (int) (distanceAfterDragLimit + mHomeBackGestureDragLimit);
|
||||
if (!mGestureTrackPositive) {
|
||||
diff *= -1;
|
||||
}
|
||||
}
|
||||
if (mGestureVerticalDragsButton ^ isNavBarVertical()) {
|
||||
button.setTranslationY(diff);
|
||||
} else {
|
||||
button.setTranslationX(diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldProxyEvents(int action) {
|
||||
if (!mBackGestureActive && !mQuickScrubActive && !mIsInScreenPinning) {
|
||||
final boolean actionValid = (mCurrentAction == null
|
||||
|| (mGestureActions[ACTION_SWIPE_UP_INDEX] != null
|
||||
&& mGestureActions[ACTION_SWIPE_UP_INDEX].isActive()));
|
||||
if (actionValid && !mIsInScreenPinning) {
|
||||
// Allow down, cancel and up events, move and other events are passed if notifications
|
||||
// are not showing and disabled gestures (such as long press) are not executed
|
||||
switch (action) {
|
||||
@@ -407,46 +379,18 @@ public class QuickStepController implements GestureHelper {
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
if (!mNavigationBarView.isQuickScrubEnabled()) {
|
||||
return;
|
||||
if (mCurrentAction != null) {
|
||||
mCurrentAction.onDraw(canvas);
|
||||
}
|
||||
mTrackPaint.setAlpha(Math.round(255f * mTrackAlpha));
|
||||
|
||||
// Scale the track, but apply the inverse scale from the nav bar
|
||||
final float radius = mTrackRect.height() / 2;
|
||||
canvas.save();
|
||||
float translate = Utilities.clamp(mHighlightCenter, mTrackRect.left, mTrackRect.right);
|
||||
canvas.translate(translate, 0);
|
||||
canvas.scale(mTrackScale / mNavigationBarView.getScaleX(),
|
||||
1f / mNavigationBarView.getScaleY(),
|
||||
mTrackRect.centerX(), mTrackRect.centerY());
|
||||
canvas.drawRoundRect(mTrackRect.left - translate, mTrackRect.top,
|
||||
mTrackRect.right - translate, mTrackRect.bottom, radius, radius, mTrackPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
final int paddingLeft = mNavigationBarView.getPaddingLeft();
|
||||
final int paddingTop = mNavigationBarView.getPaddingTop();
|
||||
final int paddingRight = mNavigationBarView.getPaddingRight();
|
||||
final int paddingBottom = mNavigationBarView.getPaddingBottom();
|
||||
final int width = (right - left) - paddingRight - paddingLeft;
|
||||
final int height = (bottom - top) - paddingBottom - paddingTop;
|
||||
final int x1, x2, y1, y2;
|
||||
if (mIsVertical) {
|
||||
x1 = (width - mTrackThickness) / 2 + paddingLeft;
|
||||
x2 = x1 + mTrackThickness;
|
||||
y1 = paddingTop + mTrackEndPadding;
|
||||
y2 = y1 + height - 2 * mTrackEndPadding;
|
||||
} else {
|
||||
y1 = (height - mTrackThickness) / 2 + paddingTop;
|
||||
y2 = y1 + mTrackThickness;
|
||||
x1 = mNavigationBarView.getPaddingStart() + mTrackEndPadding;
|
||||
x2 = x1 + width - 2 * mTrackEndPadding;
|
||||
for (NavigationGestureAction action: mGestureActions) {
|
||||
if (action != null) {
|
||||
action.onLayout(changed, left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
mTrackRect.set(x1, y1, x2, y2);
|
||||
updateHighlight();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -456,119 +400,104 @@ public class QuickStepController implements GestureHelper {
|
||||
|
||||
// When in quick scrub, invalidate gradient if changing intensity from black to white and
|
||||
// vice-versa
|
||||
if (mNavigationBarView.isQuickScrubEnabled()
|
||||
if (mCurrentAction != null && mNavigationBarView.isQuickScrubEnabled()
|
||||
&& Math.round(intensity) != Math.round(oldIntensity)) {
|
||||
updateHighlight();
|
||||
mCurrentAction.onDarkIntensityChange(mDarkIntensity);
|
||||
}
|
||||
mNavigationBarView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBarState(boolean isVertical, boolean isRTL) {
|
||||
final boolean changed = (mIsVertical != isVertical) || (mIsRTL != isRTL);
|
||||
if (changed) {
|
||||
// End quickscrub if the state changes mid-transition
|
||||
endQuickScrub(false /* animate */);
|
||||
}
|
||||
mIsVertical = isVertical;
|
||||
public void setBarState(boolean isRTL, int navBarPosition) {
|
||||
final boolean changed = (mIsRTL != isRTL) || (mNavBarPosition != navBarPosition);
|
||||
mIsRTL = isRTL;
|
||||
try {
|
||||
int navbarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition();
|
||||
mDragPositive = navbarPos == NAV_BAR_LEFT || navbarPos == NAV_BAR_BOTTOM;
|
||||
if (isRTL) {
|
||||
mDragPositive = !mDragPositive;
|
||||
mNavBarPosition = navBarPosition;
|
||||
|
||||
// Determine the drag directions depending on location of nav bar
|
||||
switch (navBarPosition) {
|
||||
case NAV_BAR_LEFT:
|
||||
mDragHPositive = !isRTL;
|
||||
mDragVPositive = false;
|
||||
break;
|
||||
case NAV_BAR_RIGHT:
|
||||
mDragHPositive = isRTL;
|
||||
mDragVPositive = true;
|
||||
break;
|
||||
case NAV_BAR_BOTTOM:
|
||||
mDragHPositive = !isRTL;
|
||||
mDragVPositive = true;
|
||||
break;
|
||||
}
|
||||
|
||||
for (NavigationGestureAction action: mGestureActions) {
|
||||
if (action != null) {
|
||||
action.setBarState(changed, mNavBarPosition, mDragHPositive, mDragVPositive);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Failed to get nav bar position.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNavigationButtonLongPress(View v) {
|
||||
mAllowGestureDetection = false;
|
||||
mHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(PrintWriter pw) {
|
||||
pw.println("QuickStepController {");
|
||||
pw.print(" "); pw.println("mQuickScrubActive=" + mQuickScrubActive);
|
||||
pw.print(" "); pw.println("mQuickStepStarted=" + mQuickStepStarted);
|
||||
pw.print(" "); pw.println("mAllowGestureDetection=" + mAllowGestureDetection);
|
||||
pw.print(" "); pw.println("mBackGestureActive=" + mBackGestureActive);
|
||||
pw.print(" "); pw.println("mCanPerformBack=" + mCanPerformBack);
|
||||
pw.print(" "); pw.println("mNotificationsVisibleOnDown=" + mNotificationsVisibleOnDown);
|
||||
pw.print(" "); pw.println("mIsVertical=" + mIsVertical);
|
||||
pw.print(" "); pw.println("mNavBarPosition=" + mNavBarPosition);
|
||||
pw.print(" "); pw.println("mIsRTL=" + mIsRTL);
|
||||
pw.print(" "); pw.println("mIsInScreenPinning=" + mIsInScreenPinning);
|
||||
pw.println("}");
|
||||
}
|
||||
|
||||
private void startQuickStep(MotionEvent event) {
|
||||
if (mIsInScreenPinning) {
|
||||
mNavigationBarView.showPinningEscapeToast();
|
||||
mAllowGestureDetection = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mQuickStepStarted = true;
|
||||
event.transform(mTransformGlobalMatrix);
|
||||
try {
|
||||
mOverviewEventSender.getProxy().onQuickStep(event);
|
||||
if (DEBUG_OVERVIEW_PROXY) {
|
||||
Log.d(TAG_OPS, "Quick Step Start");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to send quick step started.", e);
|
||||
} finally {
|
||||
event.transform(mTransformLocalMatrix);
|
||||
}
|
||||
mOverviewEventSender.notifyQuickStepStarted();
|
||||
mHandler.removeCallbacksAndMessages(null);
|
||||
|
||||
if (mHitTarget != null) {
|
||||
mHitTarget.abortCurrentGesture();
|
||||
}
|
||||
|
||||
if (mQuickScrubActive) {
|
||||
animateEnd();
|
||||
}
|
||||
public NavigationGestureAction getCurrentAction() {
|
||||
return mCurrentAction;
|
||||
}
|
||||
|
||||
private void startQuickScrub() {
|
||||
private void tryToStartGesture(NavigationGestureAction action, boolean alignedWithNavBar,
|
||||
boolean positiveDirection, MotionEvent event) {
|
||||
if (action == null) {
|
||||
return;
|
||||
}
|
||||
if (mIsInScreenPinning) {
|
||||
mNavigationBarView.showPinningEscapeToast();
|
||||
mAllowGestureDetection = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mQuickScrubActive) {
|
||||
updateHighlight();
|
||||
mQuickScrubActive = true;
|
||||
ObjectAnimator trackAnimator = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||
PropertyValuesHolder.ofFloat(mTrackAlphaProperty, 1f),
|
||||
PropertyValuesHolder.ofFloat(mTrackScaleProperty, 1f));
|
||||
trackAnimator.setInterpolator(ALPHA_IN);
|
||||
trackAnimator.setDuration(ANIM_IN_DURATION_MS);
|
||||
ObjectAnimator navBarAnimator = ObjectAnimator.ofFloat(this, mNavBarAlphaProperty, 0f);
|
||||
navBarAnimator.setInterpolator(ALPHA_OUT);
|
||||
navBarAnimator.setDuration(ANIM_OUT_DURATION_MS);
|
||||
mTrackAnimator = new AnimatorSet();
|
||||
mTrackAnimator.playTogether(trackAnimator, navBarAnimator);
|
||||
mTrackAnimator.start();
|
||||
|
||||
// Disable slippery for quick scrub to not cancel outside the nav bar
|
||||
mNavigationBarView.updateSlippery();
|
||||
|
||||
try {
|
||||
mOverviewEventSender.getProxy().onQuickScrubStart();
|
||||
if (DEBUG_OVERVIEW_PROXY) {
|
||||
Log.d(TAG_OPS, "Quick Scrub Start");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to send start of quick scrub.", e);
|
||||
// Start new action from gesture if is able to start and depending on notifications
|
||||
// visibility and starting touch down target. If the action is enabled, then also check if
|
||||
// can perform the action so that if action requires the button to be dragged, then the
|
||||
// gesture will have a large dampening factor and prevent action from running.
|
||||
final boolean validHitTarget = action.requiresTouchDownHitTarget() == HIT_TARGET_NONE
|
||||
|| action.requiresTouchDownHitTarget() == mNavigationBarView.getDownHitTarget();
|
||||
if (mCurrentAction == null && validHitTarget && action.isEnabled()
|
||||
&& (!mNotificationsVisibleOnDown || action.canRunWhenNotificationsShowing())) {
|
||||
if (action.canPerformAction()) {
|
||||
mCurrentAction = action;
|
||||
event.transform(mTransformGlobalMatrix);
|
||||
action.startGesture(event);
|
||||
event.transform(mTransformLocalMatrix);
|
||||
}
|
||||
|
||||
// Handle direction of the hit target drag from the axis that started the gesture
|
||||
if (action.requiresDragWithHitTarget()) {
|
||||
if (alignedWithNavBar) {
|
||||
mGestureHorizontalDragsButton = true;
|
||||
mGestureVerticalDragsButton = false;
|
||||
if (positiveDirection) {
|
||||
mGestureTrackPositive = mDragHPositive;
|
||||
}
|
||||
} else {
|
||||
mGestureVerticalDragsButton = true;
|
||||
mGestureHorizontalDragsButton = false;
|
||||
if (positiveDirection) {
|
||||
mGestureTrackPositive = mDragVPositive;
|
||||
}
|
||||
}
|
||||
}
|
||||
mOverviewEventSender.notifyQuickScrubStarted();
|
||||
|
||||
if (mHitTarget != null) {
|
||||
mHitTarget.abortCurrentGesture();
|
||||
@@ -576,148 +505,13 @@ public class QuickStepController implements GestureHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private void endQuickScrub(boolean animate) {
|
||||
if (mQuickScrubActive) {
|
||||
animateEnd();
|
||||
try {
|
||||
mOverviewEventSender.getProxy().onQuickScrubEnd();
|
||||
if (DEBUG_OVERVIEW_PROXY) {
|
||||
Log.d(TAG_OPS, "Quick Scrub End");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to send end of quick scrub.", e);
|
||||
private boolean canPerformAnyAction() {
|
||||
for (NavigationGestureAction action: mGestureActions) {
|
||||
if (action != null && action.isEnabled()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!animate) {
|
||||
if (mTrackAnimator != null) {
|
||||
mTrackAnimator.end();
|
||||
mTrackAnimator = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startBackGesture() {
|
||||
if (!mBackGestureActive) {
|
||||
mBackGestureActive = true;
|
||||
mNavigationBarView.getHomeButton().abortCurrentGesture();
|
||||
final boolean runBackMidGesture = !shouldExecuteBackOnUp(mContext);
|
||||
if (mCanPerformBack) {
|
||||
if (!shouldhideBackButton(mContext)) {
|
||||
mNavigationBarView.getBackButton().setAlpha(0 /* alpha */, true /* animate */,
|
||||
BACK_BUTTON_FADE_OUT_ALPHA);
|
||||
}
|
||||
if (runBackMidGesture) {
|
||||
performBack();
|
||||
}
|
||||
}
|
||||
mHandler.removeCallbacks(mExecuteBackRunnable);
|
||||
if (runBackMidGesture) {
|
||||
mHandler.postDelayed(mExecuteBackRunnable, BACK_GESTURE_POLL_TIMEOUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void endBackGesture() {
|
||||
if (mBackGestureActive) {
|
||||
mHandler.removeCallbacks(mExecuteBackRunnable);
|
||||
mHomeAnimator = mNavigationBarView.getHomeButton().getCurrentView()
|
||||
.animate()
|
||||
.setDuration(BACK_BUTTON_FADE_IN_ALPHA)
|
||||
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
|
||||
if (mIsVertical) {
|
||||
mHomeAnimator.translationY(0);
|
||||
} else {
|
||||
mHomeAnimator.translationX(0);
|
||||
}
|
||||
mHomeAnimator.start();
|
||||
if (!shouldhideBackButton(mContext)) {
|
||||
mNavigationBarView.getBackButton().setAlpha(
|
||||
mOverviewEventSender.getBackButtonAlpha(), true /* animate */);
|
||||
}
|
||||
if (shouldExecuteBackOnUp(mContext)) {
|
||||
performBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void animateEnd() {
|
||||
if (mTrackAnimator != null) {
|
||||
mTrackAnimator.cancel();
|
||||
}
|
||||
|
||||
ObjectAnimator trackAnimator = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||
PropertyValuesHolder.ofFloat(mTrackAlphaProperty, 0f),
|
||||
PropertyValuesHolder.ofFloat(mTrackScaleProperty, TRACK_SCALE));
|
||||
trackAnimator.setInterpolator(ALPHA_OUT);
|
||||
trackAnimator.setDuration(ANIM_OUT_DURATION_MS);
|
||||
ObjectAnimator navBarAnimator = ObjectAnimator.ofFloat(this, mNavBarAlphaProperty, 1f);
|
||||
navBarAnimator.setInterpolator(ALPHA_IN);
|
||||
navBarAnimator.setDuration(ANIM_IN_DURATION_MS);
|
||||
mTrackAnimator = new AnimatorSet();
|
||||
mTrackAnimator.playTogether(trackAnimator, navBarAnimator);
|
||||
mTrackAnimator.addListener(mQuickScrubEndListener);
|
||||
mTrackAnimator.start();
|
||||
}
|
||||
|
||||
private void resetQuickScrub() {
|
||||
mQuickScrubActive = false;
|
||||
mAllowGestureDetection = false;
|
||||
if (mCurrentNavigationBarView != null) {
|
||||
mCurrentNavigationBarView.setAlpha(1f);
|
||||
}
|
||||
mCurrentNavigationBarView = null;
|
||||
updateHighlight();
|
||||
}
|
||||
|
||||
private void moveHomeButton(float pos) {
|
||||
if (mHomeAnimator != null) {
|
||||
mHomeAnimator.cancel();
|
||||
mHomeAnimator = null;
|
||||
}
|
||||
final View homeButton = mNavigationBarView.getHomeButton().getCurrentView();
|
||||
if (mIsVertical) {
|
||||
homeButton.setTranslationY(pos);
|
||||
} else {
|
||||
homeButton.setTranslationX(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateHighlight() {
|
||||
if (mTrackRect.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int colorBase, colorGrad;
|
||||
if (mDarkIntensity > 0.5f) {
|
||||
colorBase = mContext.getColor(R.color.quick_step_track_background_background_dark);
|
||||
colorGrad = mContext.getColor(R.color.quick_step_track_background_foreground_dark);
|
||||
} else {
|
||||
colorBase = mContext.getColor(R.color.quick_step_track_background_background_light);
|
||||
colorGrad = mContext.getColor(R.color.quick_step_track_background_foreground_light);
|
||||
}
|
||||
mHighlight = new RadialGradient(0, mTrackRect.height() / 2,
|
||||
mTrackRect.width() * GRADIENT_WIDTH, colorGrad, colorBase,
|
||||
Shader.TileMode.CLAMP);
|
||||
mTrackPaint.setShader(mHighlight);
|
||||
}
|
||||
|
||||
private boolean canPerformHomeBackGesture() {
|
||||
return swipeHomeGoBackGestureEnabled(mContext)
|
||||
&& mOverviewEventSender.getBackButtonAlpha() > 0;
|
||||
}
|
||||
|
||||
private void performBack() {
|
||||
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
|
||||
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
|
||||
mNavigationBarView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
||||
}
|
||||
|
||||
private void sendEvent(int action, int code) {
|
||||
long when = SystemClock.uptimeMillis();
|
||||
final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
|
||||
0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
|
||||
KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
|
||||
InputDevice.SOURCE_KEYBOARD);
|
||||
InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean proxyMotionEvents(MotionEvent event) {
|
||||
@@ -740,22 +534,15 @@ public class QuickStepController implements GestureHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean getBoolGlobalSetting(Context context, String key) {
|
||||
protected boolean isNavBarVertical() {
|
||||
return mNavBarPosition == NAV_BAR_LEFT || mNavBarPosition == NAV_BAR_RIGHT;
|
||||
}
|
||||
|
||||
static boolean getBoolGlobalSetting(Context context, String key) {
|
||||
return Settings.Global.getInt(context.getContentResolver(), key, 0) != 0;
|
||||
}
|
||||
|
||||
public static boolean swipeHomeGoBackGestureEnabled(Context context) {
|
||||
return !getBoolGlobalSetting(context, NAVBAR_EXPERIMENTS_DISABLED)
|
||||
&& getBoolGlobalSetting(context, PULL_HOME_GO_BACK_PROP);
|
||||
}
|
||||
|
||||
public static boolean shouldhideBackButton(Context context) {
|
||||
return swipeHomeGoBackGestureEnabled(context)
|
||||
&& getBoolGlobalSetting(context, HIDE_BACK_BUTTON_PROP);
|
||||
}
|
||||
|
||||
public static boolean shouldExecuteBackOnUp(Context context) {
|
||||
return !getBoolGlobalSetting(context, NAVBAR_EXPERIMENTS_DISABLED)
|
||||
&& getBoolGlobalSetting(context, BACK_AFTER_END_PROP);
|
||||
return getBoolGlobalSetting(context, HIDE_BACK_BUTTON_PROP);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,618 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.systemui.statusbar.phone;
|
||||
|
||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
|
||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
|
||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
|
||||
|
||||
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE;
|
||||
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
|
||||
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.anyFloat;
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.recents.OverviewProxyService;
|
||||
import com.android.systemui.shared.recents.IOverviewProxy;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper.RunWithLooper;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/** atest QuickStepControllerTest */
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@RunWithLooper(setAsMainLooper = true)
|
||||
@SmallTest
|
||||
public class QuickStepControllerTest extends SysuiTestCase {
|
||||
private QuickStepController mController;
|
||||
private NavigationBarView mNavigationBarView;
|
||||
private StatusBar mStatusBar;
|
||||
private OverviewProxyService mProxyService;
|
||||
private IOverviewProxy mProxy;
|
||||
private Resources mResources;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
final ButtonDispatcher backButton = mock(ButtonDispatcher.class);
|
||||
mResources = mock(Resources.class);
|
||||
|
||||
mProxyService = mock(OverviewProxyService.class);
|
||||
mProxy = mock(IOverviewProxy.Stub.class);
|
||||
doReturn(mProxy).when(mProxyService).getProxy();
|
||||
mDependency.injectTestDependency(OverviewProxyService.class, mProxyService);
|
||||
|
||||
mStatusBar = mock(StatusBar.class);
|
||||
doReturn(false).when(mStatusBar).isKeyguardShowing();
|
||||
mContext.putComponent(StatusBar.class, mStatusBar);
|
||||
|
||||
mNavigationBarView = mock(NavigationBarView.class);
|
||||
doReturn(false).when(mNavigationBarView).inScreenPinning();
|
||||
doReturn(true).when(mNavigationBarView).isNotificationsFullyCollapsed();
|
||||
doReturn(true).when(mNavigationBarView).isQuickScrubEnabled();
|
||||
doReturn(HIT_TARGET_NONE).when(mNavigationBarView).getDownHitTarget();
|
||||
doReturn(backButton).when(mNavigationBarView).getBackButton();
|
||||
doReturn(mResources).when(mNavigationBarView).getResources();
|
||||
|
||||
mController = new QuickStepController(mContext);
|
||||
mController.setComponents(mNavigationBarView);
|
||||
mController.setBarState(false /* isRTL */, NAV_BAR_BOTTOM);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoActionsNoGestures() throws Exception {
|
||||
MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1);
|
||||
assertFalse(mController.onInterceptTouchEvent(ev));
|
||||
verify(mNavigationBarView, never()).requestUnbufferedDispatch(ev);
|
||||
assertNull(mController.getCurrentAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasActionDetectGesturesTouchdown() throws Exception {
|
||||
MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1);
|
||||
|
||||
// Add enabled gesture action
|
||||
NavigationGestureAction action = mockAction(true);
|
||||
mController.setGestureActions(action, null /* swipeDownAction */,
|
||||
null /* swipeLeftAction */, null /* swipeRightAction */);
|
||||
|
||||
assertFalse(mController.onInterceptTouchEvent(ev));
|
||||
verify(mNavigationBarView, times(1)).requestUnbufferedDispatch(ev);
|
||||
verify(action, times(1)).reset();
|
||||
verify(mProxy, times(1)).onPreMotionEvent(mNavigationBarView.getDownHitTarget());
|
||||
verify(mProxy, times(1)).onMotionEvent(ev);
|
||||
assertNull(mController.getCurrentAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProxyDisconnectedNoGestures() throws Exception {
|
||||
MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1);
|
||||
|
||||
// Add enabled gesture action
|
||||
mController.setGestureActions(mockAction(true), null /* swipeDownAction */,
|
||||
null /* swipeLeftAction */, null /* swipeRightAction */);
|
||||
|
||||
// Set the gesture on deadzone
|
||||
doReturn(null).when(mProxyService).getProxy();
|
||||
|
||||
assertFalse(mController.onInterceptTouchEvent(ev));
|
||||
verify(mNavigationBarView, never()).requestUnbufferedDispatch(ev);
|
||||
assertNull(mController.getCurrentAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoActionsNoGesturesOverDeadzone() throws Exception {
|
||||
MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1);
|
||||
|
||||
// Touched over deadzone
|
||||
doReturn(HIT_TARGET_DEAD_ZONE).when(mNavigationBarView).getDownHitTarget();
|
||||
|
||||
assertTrue(mController.onInterceptTouchEvent(ev));
|
||||
verify(mNavigationBarView, never()).requestUnbufferedDispatch(ev);
|
||||
assertNull(mController.getCurrentAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTouchIgnoredDownEventAfterOnIntercept() {
|
||||
mController.setGestureActions(mockAction(true), null /* swipeDownAction */,
|
||||
null /* swipeLeftAction */, null /* swipeRightAction */);
|
||||
|
||||
MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1);
|
||||
assertFalse(touch(ev));
|
||||
verify(mNavigationBarView, times(1)).requestUnbufferedDispatch(ev);
|
||||
|
||||
// OnTouch event for down is ignored, so requestUnbufferedDispatch ran once from before
|
||||
assertFalse(mNavigationBarView.onTouchEvent(ev));
|
||||
verify(mNavigationBarView, times(1)).requestUnbufferedDispatch(ev);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGesturesCallCorrectAction() throws Exception {
|
||||
NavigationGestureAction swipeUp = mockAction(true);
|
||||
NavigationGestureAction swipeDown = mockAction(true);
|
||||
NavigationGestureAction swipeLeft = mockAction(true);
|
||||
NavigationGestureAction swipeRight = mockAction(true);
|
||||
mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight);
|
||||
|
||||
// Swipe Up
|
||||
assertGestureTriggersAction(swipeUp, 1, 100, 5, 1);
|
||||
// Swipe Down
|
||||
assertGestureTriggersAction(swipeDown, 1, 1, 5, 100);
|
||||
// Swipe Left
|
||||
assertGestureTriggersAction(swipeLeft, 100, 1, 5, 1);
|
||||
// Swipe Right
|
||||
assertGestureTriggersAction(swipeRight, 1, 1, 100, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGesturesCallCorrectActionLandscape() throws Exception {
|
||||
NavigationGestureAction swipeUp = mockAction(true);
|
||||
NavigationGestureAction swipeDown = mockAction(true);
|
||||
NavigationGestureAction swipeLeft = mockAction(true);
|
||||
NavigationGestureAction swipeRight = mockAction(true);
|
||||
mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight);
|
||||
|
||||
// In landscape
|
||||
mController.setBarState(false /* isRTL */, NAV_BAR_RIGHT);
|
||||
|
||||
// Swipe Up
|
||||
assertGestureTriggersAction(swipeRight, 1, 100, 5, 1);
|
||||
// Swipe Down
|
||||
assertGestureTriggersAction(swipeLeft, 1, 1, 5, 100);
|
||||
// Swipe Left
|
||||
assertGestureTriggersAction(swipeUp, 100, 1, 5, 1);
|
||||
// Swipe Right
|
||||
assertGestureTriggersAction(swipeDown, 1, 1, 100, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGesturesCallCorrectActionSeascape() throws Exception {
|
||||
mController.setBarState(false /* isRTL */, NAV_BAR_LEFT);
|
||||
NavigationGestureAction swipeUp = mockAction(true);
|
||||
NavigationGestureAction swipeDown = mockAction(true);
|
||||
NavigationGestureAction swipeLeft = mockAction(true);
|
||||
NavigationGestureAction swipeRight = mockAction(true);
|
||||
mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight);
|
||||
|
||||
// Swipe Up
|
||||
assertGestureTriggersAction(swipeLeft, 1, 100, 5, 1);
|
||||
// Swipe Down
|
||||
assertGestureTriggersAction(swipeRight, 1, 1, 5, 100);
|
||||
// Swipe Left
|
||||
assertGestureTriggersAction(swipeDown, 100, 1, 5, 1);
|
||||
// Swipe Right
|
||||
assertGestureTriggersAction(swipeUp, 1, 1, 100, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGesturesCallCorrectActionRTL() throws Exception {
|
||||
mController.setBarState(true /* isRTL */, NAV_BAR_BOTTOM);
|
||||
|
||||
// The swipe gestures below are for LTR, so RTL in portrait will be swapped
|
||||
NavigationGestureAction swipeUp = mockAction(true);
|
||||
NavigationGestureAction swipeDown = mockAction(true);
|
||||
NavigationGestureAction swipeLeft = mockAction(true);
|
||||
NavigationGestureAction swipeRight = mockAction(true);
|
||||
mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight);
|
||||
|
||||
// Swipe Up in RTL
|
||||
assertGestureTriggersAction(swipeUp, 1, 100, 5, 1);
|
||||
// Swipe Down in RTL
|
||||
assertGestureTriggersAction(swipeDown, 1, 1, 5, 100);
|
||||
// Swipe Left in RTL
|
||||
assertGestureTriggersAction(swipeRight, 100, 1, 5, 1);
|
||||
// Swipe Right in RTL
|
||||
assertGestureTriggersAction(swipeLeft, 1, 1, 100, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGesturesCallCorrectActionLandscapeRTL() throws Exception {
|
||||
mController.setBarState(true /* isRTL */, NAV_BAR_RIGHT);
|
||||
|
||||
// The swipe gestures below are for LTR, so RTL in landscape will be swapped
|
||||
NavigationGestureAction swipeUp = mockAction(true);
|
||||
NavigationGestureAction swipeDown = mockAction(true);
|
||||
NavigationGestureAction swipeLeft = mockAction(true);
|
||||
NavigationGestureAction swipeRight = mockAction(true);
|
||||
mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight);
|
||||
|
||||
// Swipe Up
|
||||
assertGestureTriggersAction(swipeLeft, 1, 100, 5, 1);
|
||||
// Swipe Down
|
||||
assertGestureTriggersAction(swipeRight, 1, 1, 5, 100);
|
||||
// Swipe Left
|
||||
assertGestureTriggersAction(swipeUp, 100, 1, 5, 1);
|
||||
// Swipe Right
|
||||
assertGestureTriggersAction(swipeDown, 1, 1, 100, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGesturesCallCorrectActionSeascapeRTL() throws Exception {
|
||||
mController.setBarState(true /* isRTL */, NAV_BAR_LEFT);
|
||||
|
||||
// The swipe gestures below are for LTR, so RTL in seascape will be swapped
|
||||
NavigationGestureAction swipeUp = mockAction(true);
|
||||
NavigationGestureAction swipeDown = mockAction(true);
|
||||
NavigationGestureAction swipeLeft = mockAction(true);
|
||||
NavigationGestureAction swipeRight = mockAction(true);
|
||||
mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight);
|
||||
|
||||
// Swipe Up
|
||||
assertGestureTriggersAction(swipeRight, 1, 100, 5, 1);
|
||||
// Swipe Down
|
||||
assertGestureTriggersAction(swipeLeft, 1, 1, 5, 100);
|
||||
// Swipe Left
|
||||
assertGestureTriggersAction(swipeDown, 100, 1, 5, 1);
|
||||
// Swipe Right
|
||||
assertGestureTriggersAction(swipeUp, 1, 1, 100, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionPreventByPinnedState() throws Exception {
|
||||
// Screen is pinned
|
||||
doReturn(true).when(mNavigationBarView).inScreenPinning();
|
||||
|
||||
// Add enabled gesture action
|
||||
NavigationGestureAction action = mockAction(true);
|
||||
mController.setGestureActions(action, null /* swipeDownAction */,
|
||||
null /* swipeLeftAction */, null /* swipeRightAction */);
|
||||
|
||||
// Touch down to begin swipe
|
||||
MotionEvent downEvent = event(MotionEvent.ACTION_DOWN, 1, 100);
|
||||
assertFalse(touch(downEvent));
|
||||
verify(mProxy, never()).onPreMotionEvent(mNavigationBarView.getDownHitTarget());
|
||||
verify(mProxy, never()).onMotionEvent(downEvent);
|
||||
|
||||
// Move to start gesture, but pinned so it should not trigger action
|
||||
MotionEvent moveEvent = event(MotionEvent.ACTION_MOVE, 1, 1);
|
||||
assertFalse(touch(moveEvent));
|
||||
assertNull(mController.getCurrentAction());
|
||||
verify(mNavigationBarView, times(1)).showPinningEscapeToast();
|
||||
verify(action, never()).onGestureStart(moveEvent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionPreventedNotificationsShown() throws Exception {
|
||||
NavigationGestureAction action = mockAction(true);
|
||||
doReturn(false).when(action).canRunWhenNotificationsShowing();
|
||||
mController.setGestureActions(action, null /* swipeDownAction */,
|
||||
null /* swipeLeftAction */, null /* swipeRightAction */);
|
||||
|
||||
// Show the notifications
|
||||
doReturn(false).when(mNavigationBarView).isNotificationsFullyCollapsed();
|
||||
|
||||
// Swipe up
|
||||
assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100));
|
||||
assertFalse(touch(MotionEvent.ACTION_MOVE, 1, 1));
|
||||
assertNull(mController.getCurrentAction());
|
||||
assertFalse(touch(MotionEvent.ACTION_UP, 1, 1));
|
||||
|
||||
// Hide the notifications
|
||||
doReturn(true).when(mNavigationBarView).isNotificationsFullyCollapsed();
|
||||
|
||||
// Swipe up
|
||||
assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100));
|
||||
assertTrue(touch(MotionEvent.ACTION_MOVE, 1, 1));
|
||||
assertEquals(action, mController.getCurrentAction());
|
||||
assertFalse(touch(MotionEvent.ACTION_UP, 1, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionCannotPerform() throws Exception {
|
||||
NavigationGestureAction action = mockAction(true);
|
||||
mController.setGestureActions(action, null /* swipeDownAction */,
|
||||
null /* swipeLeftAction */, null /* swipeRightAction */);
|
||||
|
||||
// Cannot perform action
|
||||
doReturn(false).when(action).canPerformAction();
|
||||
|
||||
// Swipe up
|
||||
assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100));
|
||||
assertFalse(touch(MotionEvent.ACTION_MOVE, 1, 1));
|
||||
assertNull(mController.getCurrentAction());
|
||||
assertFalse(touch(MotionEvent.ACTION_UP, 1, 1));
|
||||
|
||||
// Cannot perform action
|
||||
doReturn(true).when(action).canPerformAction();
|
||||
|
||||
// Swipe up
|
||||
assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100));
|
||||
assertTrue(touch(MotionEvent.ACTION_MOVE, 1, 1));
|
||||
assertEquals(action, mController.getCurrentAction());
|
||||
assertFalse(touch(MotionEvent.ACTION_UP, 1, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQuickScrub() throws Exception {
|
||||
QuickScrubAction action = spy(new QuickScrubAction(mNavigationBarView, mProxyService));
|
||||
mController.setGestureActions(null /* swipeUpAction */, null /* swipeDownAction */,
|
||||
null /* swipeLeftAction */, action);
|
||||
int y = 20;
|
||||
|
||||
// Set the layout and other padding to make sure the scrub fraction is calculated correctly
|
||||
action.onLayout(true, 0, 0, 400, 100);
|
||||
doReturn(0).when(mNavigationBarView).getPaddingLeft();
|
||||
doReturn(0).when(mNavigationBarView).getPaddingRight();
|
||||
doReturn(0).when(mNavigationBarView).getPaddingStart();
|
||||
doReturn(0).when(mResources)
|
||||
.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_edge_padding);
|
||||
|
||||
// Quickscrub disabled, so the action should be disabled
|
||||
doReturn(false).when(mNavigationBarView).isQuickScrubEnabled();
|
||||
assertFalse(action.isEnabled());
|
||||
doReturn(true).when(mNavigationBarView).isQuickScrubEnabled();
|
||||
|
||||
// Touch down
|
||||
MotionEvent downEvent = event(MotionEvent.ACTION_DOWN, 0, y);
|
||||
assertFalse(touch(downEvent));
|
||||
assertNull(mController.getCurrentAction());
|
||||
verify(mProxy, times(1)).onPreMotionEvent(mNavigationBarView.getDownHitTarget());
|
||||
verify(mProxy, times(1)).onMotionEvent(downEvent);
|
||||
|
||||
// Move to start trigger action from gesture
|
||||
MotionEvent moveEvent1 = event(MotionEvent.ACTION_MOVE, 100, y);
|
||||
assertTrue(touch(moveEvent1));
|
||||
assertEquals(action, mController.getCurrentAction());
|
||||
verify(action, times(1)).onGestureStart(moveEvent1);
|
||||
verify(mProxy, times(1)).onQuickScrubStart();
|
||||
verify(mProxyService, times(1)).notifyQuickScrubStarted();
|
||||
verify(mNavigationBarView, times(1)).updateSlippery();
|
||||
|
||||
// Move again for scrub
|
||||
MotionEvent moveEvent2 = event(MotionEvent.ACTION_MOVE, 200, y);
|
||||
assertTrue(touch(moveEvent2));
|
||||
assertEquals(action, mController.getCurrentAction());
|
||||
verify(action, times(1)).onGestureMove(200, y);
|
||||
verify(mProxy, times(1)).onQuickScrubProgress(1f / 2);
|
||||
|
||||
// Action up
|
||||
MotionEvent upEvent = event(MotionEvent.ACTION_UP, 1, y);
|
||||
assertFalse(touch(upEvent));
|
||||
assertNull(mController.getCurrentAction());
|
||||
verify(action, times(1)).onGestureEnd();
|
||||
verify(mProxy, times(1)).onQuickScrubEnd();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQuickStep() throws Exception {
|
||||
QuickStepAction action = new QuickStepAction(mNavigationBarView, mProxyService);
|
||||
mController.setGestureActions(action, null /* swipeDownAction */,
|
||||
null /* swipeLeftAction */, null /* swipeRightAction */);
|
||||
|
||||
// Notifications are up, should prevent quickstep
|
||||
doReturn(false).when(mNavigationBarView).isNotificationsFullyCollapsed();
|
||||
|
||||
// Swipe up
|
||||
assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100));
|
||||
assertNull(mController.getCurrentAction());
|
||||
assertFalse(touch(MotionEvent.ACTION_MOVE, 1, 1));
|
||||
assertNull(mController.getCurrentAction());
|
||||
assertFalse(touch(MotionEvent.ACTION_UP, 1, 1));
|
||||
doReturn(true).when(mNavigationBarView).isNotificationsFullyCollapsed();
|
||||
|
||||
// Quickstep disabled, so the action should be disabled
|
||||
doReturn(false).when(mNavigationBarView).isQuickStepSwipeUpEnabled();
|
||||
assertFalse(action.isEnabled());
|
||||
doReturn(true).when(mNavigationBarView).isQuickStepSwipeUpEnabled();
|
||||
|
||||
// Swipe up should call proxy events
|
||||
MotionEvent downEvent = event(MotionEvent.ACTION_DOWN, 1, 100);
|
||||
assertFalse(touch(downEvent));
|
||||
assertNull(mController.getCurrentAction());
|
||||
verify(mProxy, times(1)).onPreMotionEvent(mNavigationBarView.getDownHitTarget());
|
||||
verify(mProxy, times(1)).onMotionEvent(downEvent);
|
||||
|
||||
MotionEvent moveEvent = event(MotionEvent.ACTION_MOVE, 1, 1);
|
||||
assertTrue(touch(moveEvent));
|
||||
assertEquals(action, mController.getCurrentAction());
|
||||
verify(mProxy, times(1)).onQuickStep(moveEvent);
|
||||
verify(mProxyService, times(1)).notifyQuickStepStarted();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongPressPreventDetection() throws Exception {
|
||||
NavigationGestureAction action = mockAction(true);
|
||||
mController.setGestureActions(action, null /* swipeDownAction */,
|
||||
null /* swipeLeftAction */, null /* swipeRightAction */);
|
||||
|
||||
// Start the drag up
|
||||
assertFalse(touch(MotionEvent.ACTION_DOWN, 100, 1));
|
||||
assertNull(mController.getCurrentAction());
|
||||
|
||||
// Long press something on the navigation bar such as Home button
|
||||
mNavigationBarView.onNavigationButtonLongPress(mock(View.class));
|
||||
|
||||
// Swipe right will not start any gestures
|
||||
MotionEvent motionMoveEvent = event(MotionEvent.ACTION_MOVE, 1, 1);
|
||||
assertFalse(touch(motionMoveEvent));
|
||||
assertNull(mController.getCurrentAction());
|
||||
verify(action, never()).startGesture(motionMoveEvent);
|
||||
|
||||
// Touch up
|
||||
assertFalse(touch(MotionEvent.ACTION_UP, 1, 1));
|
||||
verify(action, never()).endGesture();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHitTargetDragged() throws Exception {
|
||||
ButtonDispatcher button = mock(ButtonDispatcher.class);
|
||||
View buttonView = spy(new View(mContext));
|
||||
doReturn(buttonView).when(button).getCurrentView();
|
||||
|
||||
NavigationGestureAction action = mockAction(true);
|
||||
mController.setGestureActions(action, action, action, action);
|
||||
|
||||
// Setup getting the hit target
|
||||
doReturn(HIT_TARGET_HOME).when(action).requiresTouchDownHitTarget();
|
||||
doReturn(true).when(action).requiresDragWithHitTarget();
|
||||
doReturn(HIT_TARGET_HOME).when(mNavigationBarView).getDownHitTarget();
|
||||
doReturn(button).when(mNavigationBarView).getHomeButton();
|
||||
|
||||
// Portrait
|
||||
assertGestureDragsHitTargetAllDirections(buttonView, false /* isRTL */, NAV_BAR_BOTTOM);
|
||||
|
||||
// Portrait RTL
|
||||
assertGestureDragsHitTargetAllDirections(buttonView, true /* isRTL */, NAV_BAR_BOTTOM);
|
||||
|
||||
// Landscape
|
||||
assertGestureDragsHitTargetAllDirections(buttonView, false /* isRTL */, NAV_BAR_RIGHT);
|
||||
|
||||
// Landscape RTL
|
||||
assertGestureDragsHitTargetAllDirections(buttonView, true /* isRTL */, NAV_BAR_RIGHT);
|
||||
|
||||
// Seascape
|
||||
assertGestureDragsHitTargetAllDirections(buttonView, false /* isRTL */, NAV_BAR_LEFT);
|
||||
|
||||
// Seascape RTL
|
||||
assertGestureDragsHitTargetAllDirections(buttonView, true /* isRTL */, NAV_BAR_LEFT);
|
||||
}
|
||||
|
||||
private void assertGestureDragsHitTargetAllDirections(View buttonView, boolean isRTL,
|
||||
int navPos) {
|
||||
mController.setBarState(isRTL, navPos);
|
||||
|
||||
// Swipe up
|
||||
assertGestureDragsHitTarget(buttonView, 10 /* x1 */, 200 /* y1 */, 0 /* x2 */, 0 /* y2 */,
|
||||
0 /* dx */, -1 /* dy */);
|
||||
// Swipe left
|
||||
assertGestureDragsHitTarget(buttonView, 200 /* x1 */, 10 /* y1 */, 0 /* x2 */, 0 /* y2 */,
|
||||
-1 /* dx */, 0 /* dy */);
|
||||
// Swipe right
|
||||
assertGestureDragsHitTarget(buttonView, 0 /* x1 */, 0 /* y1 */, 200 /* x2 */, 10 /* y2 */,
|
||||
1 /* dx */, 0 /* dy */);
|
||||
// Swipe down
|
||||
assertGestureDragsHitTarget(buttonView, 0 /* x1 */, 0 /* y1 */, 10 /* x2 */, 200 /* y2 */,
|
||||
0 /* dx */, 1 /* dy */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the gesture actually moves the hit target
|
||||
* @param buttonView button to check if moved, use Mockito.spy on a real object
|
||||
* @param x1 start x
|
||||
* @param x2 start y
|
||||
* @param y1 end x
|
||||
* @param y2 end y
|
||||
* @param dx diff in x, if not 0, its sign determines direction, value does not matter
|
||||
* @param dy diff in y, if not 0, its sign determines direction, value does not matter
|
||||
*/
|
||||
private void assertGestureDragsHitTarget(View buttonView, int x1, int y1, int x2, int y2,
|
||||
int dx, int dy) {
|
||||
ArgumentCaptor<Float> captor = ArgumentCaptor.forClass(Float.class);
|
||||
assertFalse(touch(MotionEvent.ACTION_DOWN, x1, y1));
|
||||
assertTrue(touch(MotionEvent.ACTION_MOVE, x2, y2));
|
||||
|
||||
// Verify positions of the button drag
|
||||
if (dx == 0) {
|
||||
verify(buttonView, never()).setTranslationX(anyFloat());
|
||||
} else {
|
||||
verify(buttonView).setTranslationX(captor.capture());
|
||||
if (dx < 0) {
|
||||
assertTrue("Button should have moved left", (float) captor.getValue() < 0);
|
||||
} else {
|
||||
assertTrue("Button should have moved right", (float) captor.getValue() > 0);
|
||||
}
|
||||
}
|
||||
if (dy == 0) {
|
||||
verify(buttonView, never()).setTranslationY(anyFloat());
|
||||
} else {
|
||||
verify(buttonView).setTranslationY(captor.capture());
|
||||
if (dy < 0) {
|
||||
assertTrue("Button should have moved up", (float) captor.getValue() < 0);
|
||||
} else {
|
||||
assertTrue("Button should have moved down", (float) captor.getValue() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Touch up
|
||||
assertFalse(touch(MotionEvent.ACTION_UP, x2, y2));
|
||||
verify(buttonView, times(1)).animate();
|
||||
|
||||
// Reset button state
|
||||
reset(buttonView);
|
||||
}
|
||||
|
||||
|
||||
private MotionEvent event(int action, float x, float y) {
|
||||
final MotionEvent event = mock(MotionEvent.class);
|
||||
doReturn(x).when(event).getX();
|
||||
doReturn(y).when(event).getY();
|
||||
doReturn(action & MotionEvent.ACTION_MASK).when(event).getActionMasked();
|
||||
doReturn(action).when(event).getAction();
|
||||
return event;
|
||||
}
|
||||
|
||||
private boolean touch(int action, float x, float y) {
|
||||
return touch(event(action, x, y));
|
||||
}
|
||||
|
||||
private boolean touch(MotionEvent event) {
|
||||
return mController.onInterceptTouchEvent(event);
|
||||
}
|
||||
|
||||
private NavigationGestureAction mockAction(boolean enabled) {
|
||||
final NavigationGestureAction action = mock(NavigationGestureAction.class);
|
||||
doReturn(enabled).when(action).isEnabled();
|
||||
doReturn(HIT_TARGET_NONE).when(action).requiresTouchDownHitTarget();
|
||||
doReturn(true).when(action).canPerformAction();
|
||||
return action;
|
||||
}
|
||||
|
||||
private void assertGestureTriggersAction(NavigationGestureAction action, int x1, int y1,
|
||||
int x2, int y2) {
|
||||
// Start the drag
|
||||
assertFalse(touch(MotionEvent.ACTION_DOWN, x1, y1));
|
||||
assertNull(mController.getCurrentAction());
|
||||
|
||||
// Swipe
|
||||
MotionEvent motionMoveEvent = event(MotionEvent.ACTION_MOVE, x2, y2);
|
||||
assertTrue(touch(motionMoveEvent));
|
||||
assertEquals(action, mController.getCurrentAction());
|
||||
verify(action, times(1)).startGesture(motionMoveEvent);
|
||||
|
||||
// Move again
|
||||
assertTrue(touch(MotionEvent.ACTION_MOVE, x2, y2));
|
||||
verify(action, times(1)).onGestureMove(x2, y2);
|
||||
|
||||
// Touch up
|
||||
assertFalse(touch(MotionEvent.ACTION_UP, x2, y2));
|
||||
assertNull(mController.getCurrentAction());
|
||||
verify(action, times(1)).endGesture();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user