Merge "Adding the touch gestures for the pull down notification shade." into qt-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
784999f21f
@@ -16,11 +16,15 @@
|
||||
|
||||
package com.android.systemui.statusbar.car;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.car.drivingstate.CarDrivingStateEvent;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
@@ -55,6 +59,7 @@ import com.android.systemui.plugins.qs.QS;
|
||||
import com.android.systemui.qs.car.CarQSFragment;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import com.android.systemui.statusbar.FlingAnimationUtils;
|
||||
import com.android.systemui.statusbar.StatusBarState;
|
||||
import com.android.systemui.statusbar.car.hvac.HvacController;
|
||||
import com.android.systemui.statusbar.car.hvac.TemperatureView;
|
||||
@@ -74,6 +79,15 @@ import java.util.Map;
|
||||
public class CarStatusBar extends StatusBar implements
|
||||
CarBatteryController.BatteryViewHandler {
|
||||
private static final String TAG = "CarStatusBar";
|
||||
// used to calculate how fast to open or close the window
|
||||
private static final float DEFAULT_FLING_VELOCITY = 0;
|
||||
// max time a fling animation takes
|
||||
private static final float FLING_ANIMATION_MAX_TIME = 0.5f;
|
||||
// acceleration rate for the fling animation
|
||||
private static final float FLING_SPEED_UP_FACTOR = 0.6f;
|
||||
|
||||
private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
|
||||
private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
|
||||
|
||||
private TaskStackListenerImpl mTaskStackListener;
|
||||
|
||||
@@ -100,6 +114,7 @@ public class CarStatusBar extends StatusBar implements
|
||||
private boolean mDeviceIsProvisioned = true;
|
||||
private HvacController mHvacController;
|
||||
private DrivingStateHelper mDrivingStateHelper;
|
||||
private static FlingAnimationUtils sFlingAnimationUtils;
|
||||
private SwitchToGuestTimer mSwitchToGuestTimer;
|
||||
|
||||
// The container for the notifications.
|
||||
@@ -113,6 +128,27 @@ public class CarStatusBar extends StatusBar implements
|
||||
// it's open.
|
||||
private View.OnTouchListener mNavBarNotificationTouchListener;
|
||||
|
||||
// Percentage from top of the screen after which the notification shade will open. This value
|
||||
// will be used while opening the notification shade.
|
||||
private int mSettleOpenPercentage;
|
||||
// Percentage from top of the screen below which the notification shade will close. This
|
||||
// value will be used while closing the notification shade.
|
||||
private int mSettleClosePercentage;
|
||||
// Percentage of notification shade open from top of the screen.
|
||||
private int mPercentageFromBottom;
|
||||
// If notification shade is animation to close or to open.
|
||||
private boolean mIsNotificationAnimating;
|
||||
|
||||
// Tracks when the notification shade is being scrolled. This refers to the glass pane being
|
||||
// scrolled not the recycler view.
|
||||
private boolean mIsTracking;
|
||||
private float mFirstTouchDownOnGlassPane;
|
||||
|
||||
// If the notification card inside the recycler view is being swiped.
|
||||
private boolean mIsNotificationCardSwiping;
|
||||
// If notification shade is being swiped vertically to close.
|
||||
private boolean mIsSwipingVerticallyToClose;
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
// get the provisioned state before calling the parent class since it's that flow that
|
||||
@@ -125,6 +161,12 @@ public class CarStatusBar extends StatusBar implements
|
||||
mActivityManagerWrapper.registerTaskStackListener(mTaskStackListener);
|
||||
|
||||
mNotificationPanel.setScrollingEnabled(true);
|
||||
mSettleOpenPercentage = mContext.getResources().getInteger(
|
||||
R.integer.notification_settle_open_percentage);
|
||||
mSettleClosePercentage = mContext.getResources().getInteger(
|
||||
R.integer.notification_settle_close_percentage);
|
||||
sFlingAnimationUtils = new FlingAnimationUtils(mContext,
|
||||
FLING_ANIMATION_MAX_TIME, FLING_SPEED_UP_FACTOR);
|
||||
|
||||
createBatteryController();
|
||||
mCarBatteryController.startListening();
|
||||
@@ -313,14 +355,46 @@ public class CarStatusBar extends StatusBar implements
|
||||
}
|
||||
});
|
||||
mNavBarNotificationTouchListener =
|
||||
(v, event) -> navBarCloseNotificationGestureDetector.onTouchEvent(event);
|
||||
(v, event) -> {
|
||||
boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
|
||||
if (consumed) {
|
||||
return true;
|
||||
}
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_UP
|
||||
&& mNotificationView.getVisibility() == View.VISIBLE) {
|
||||
if (mSettleClosePercentage < mPercentageFromBottom) {
|
||||
animateNotificationPanel(
|
||||
DEFAULT_FLING_VELOCITY, false);
|
||||
} else {
|
||||
animateNotificationPanel(DEFAULT_FLING_VELOCITY,
|
||||
true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// The following are the ui elements that the user would call the status bar.
|
||||
// This will set the status bar so it they can make call backs.
|
||||
CarNavigationBarView topBar = mStatusBarWindow.findViewById(R.id.car_top_bar);
|
||||
topBar.setStatusBar(this);
|
||||
topBar.setStatusBarWindowTouchListener((v1, event1) ->
|
||||
openGestureDetector.onTouchEvent(event1));
|
||||
topBar.setStatusBarWindowTouchListener((v1, event1) -> {
|
||||
|
||||
boolean consumed = openGestureDetector.onTouchEvent(event1);
|
||||
if (consumed) {
|
||||
return true;
|
||||
}
|
||||
if (event1.getActionMasked() == MotionEvent.ACTION_UP
|
||||
&& mNotificationView.getVisibility() == View.VISIBLE) {
|
||||
if (mSettleOpenPercentage > mPercentageFromBottom) {
|
||||
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
|
||||
} else {
|
||||
animateNotificationPanel(
|
||||
DEFAULT_FLING_VELOCITY, false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
NotificationClickHandlerFactory clickHandlerFactory = new NotificationClickHandlerFactory(
|
||||
mBarService,
|
||||
@@ -347,7 +421,10 @@ public class CarStatusBar extends StatusBar implements
|
||||
mNotificationListAtBottomAtTimeOfTouch = false;
|
||||
}
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
mFirstTouchDownOnGlassPane = event.getRawX();
|
||||
mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom;
|
||||
// Reset the tracker when there is a touch down on the glass pane.
|
||||
mIsTracking = false;
|
||||
// Pass the down event to gesture detector so that it knows where the touch event
|
||||
// started.
|
||||
closeGestureDetector.onTouchEvent(event);
|
||||
@@ -364,23 +441,56 @@ public class CarStatusBar extends StatusBar implements
|
||||
return;
|
||||
}
|
||||
mNotificationListAtBottom = false;
|
||||
mIsSwipingVerticallyToClose = false;
|
||||
mNotificationListAtBottomAtTimeOfTouch = false;
|
||||
}
|
||||
});
|
||||
mNotificationList.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
|
||||
> SWIPE_MAX_OFF_PATH;
|
||||
if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) {
|
||||
// We need to save the state here as if notification card is swiping we will
|
||||
// change the mNotificationListAtBottomAtTimeOfTouch. This is to protect
|
||||
// closing the notification shade while the notification card is being swiped.
|
||||
mIsSwipingVerticallyToClose = true;
|
||||
}
|
||||
|
||||
// If the card is swiping we should not allow the notification shade to close.
|
||||
// Hence setting mNotificationListAtBottomAtTimeOfTouch to false will stop that
|
||||
// for us. We are also checking for mIsTracking because while swiping the
|
||||
// notification shade to close if the user goes a bit horizontal while swiping
|
||||
// upwards then also this should close.
|
||||
if (mIsNotificationCardSwiping && !mIsTracking) {
|
||||
mNotificationListAtBottomAtTimeOfTouch = false;
|
||||
}
|
||||
|
||||
boolean handled = false;
|
||||
if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) {
|
||||
handled = closeGestureDetector.onTouchEvent(event);
|
||||
}
|
||||
boolean isTracking = mIsTracking;
|
||||
Rect rect = mNotificationView.getClipBounds();
|
||||
float clippedHeight = rect.bottom;
|
||||
if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
|
||||
&& mIsSwipingVerticallyToClose) {
|
||||
if (mSettleClosePercentage < mPercentageFromBottom && isTracking) {
|
||||
animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
|
||||
} else if (clippedHeight != mNotificationView.getHeight() && isTracking) {
|
||||
// this can be caused when user is at the end of the list and trying to
|
||||
// fling to top of the list by scrolling down.
|
||||
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Updating the mNotificationListAtBottomAtTimeOfTouch state has to be done after
|
||||
// the event has been passed to the closeGestureDetector above, such that the
|
||||
// closeGestureDetector sees the up event before the state has changed.
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
|
||||
mNotificationListAtBottomAtTimeOfTouch = false;
|
||||
}
|
||||
return handled;
|
||||
return handled || isTracking;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -401,7 +511,9 @@ public class CarStatusBar extends StatusBar implements
|
||||
mNotificationList.scrollToPosition(0);
|
||||
mStatusBarWindowController.setPanelVisible(true);
|
||||
mNotificationView.setVisibility(View.VISIBLE);
|
||||
// let the status bar know that the panel is open
|
||||
|
||||
animateNotificationPanel(mOpeningVelocity, false);
|
||||
|
||||
setPanelExpanded(true);
|
||||
}
|
||||
|
||||
@@ -415,12 +527,66 @@ public class CarStatusBar extends StatusBar implements
|
||||
mStatusBarWindowController.setStatusBarFocusable(false);
|
||||
mStatusBarWindow.cancelExpandHelper();
|
||||
mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor);
|
||||
mStatusBarWindowController.setPanelVisible(false);
|
||||
mNotificationView.setVisibility(View.INVISIBLE);
|
||||
// let the status bar know that the panel is cloased
|
||||
|
||||
animateNotificationPanel(mClosingVelocity, true);
|
||||
|
||||
if (!mIsTracking) {
|
||||
mStatusBarWindowController.setPanelVisible(false);
|
||||
mNotificationView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
setPanelExpanded(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the notification shade from one position to other. This is used to either open or
|
||||
* close the notification shade completely with a velocity. Id the animation is to close the
|
||||
* notification shade this method also makes the view invisible after animation ends.
|
||||
*/
|
||||
private void animateNotificationPanel(float velocity, boolean isClosing) {
|
||||
Rect rect = mNotificationView.getClipBounds();
|
||||
if (rect == null) {
|
||||
return;
|
||||
}
|
||||
float from = rect.bottom;
|
||||
float to = 0;
|
||||
if (!isClosing) {
|
||||
to = mNotificationView.getHeight();
|
||||
}
|
||||
if (mIsNotificationAnimating) {
|
||||
return;
|
||||
}
|
||||
mIsNotificationAnimating = true;
|
||||
mIsTracking = true;
|
||||
ValueAnimator animator = ValueAnimator.ofFloat(from, to);
|
||||
animator.addUpdateListener(
|
||||
animation -> {
|
||||
float animatedValue = (Float) animation.getAnimatedValue();
|
||||
setNotificationViewClipBounds((int) animatedValue);
|
||||
});
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
mIsNotificationAnimating = false;
|
||||
mIsTracking = false;
|
||||
mOpeningVelocity = DEFAULT_FLING_VELOCITY;
|
||||
mClosingVelocity = DEFAULT_FLING_VELOCITY;
|
||||
if (isClosing) {
|
||||
mStatusBarWindowController.setPanelVisible(false);
|
||||
mNotificationView.setVisibility(View.INVISIBLE);
|
||||
mNotificationView.setClipBounds(null);
|
||||
// let the status bar know that the panel is closed
|
||||
setPanelExpanded(false);
|
||||
} else {
|
||||
// let the status bar know that the panel is open
|
||||
setPanelExpanded(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
sFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity));
|
||||
animator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QS createDefaultQSFragment() {
|
||||
@@ -576,7 +742,7 @@ public class CarStatusBar extends StatusBar implements
|
||||
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
//When executing dump() funciton simultaneously, we need to serialize them
|
||||
//When executing dump() function simultaneously, we need to serialize them
|
||||
//to get mStackScroller's position correctly.
|
||||
synchronized (mQueueLock) {
|
||||
pw.println(" mStackScroller: " + viewInfo(mStackScroller));
|
||||
@@ -760,38 +926,69 @@ public class CarStatusBar extends StatusBar implements
|
||||
}
|
||||
|
||||
/** Returns true if the current user makes it through the setup wizard, false otherwise. */
|
||||
public boolean getIsUserSetup(){
|
||||
private boolean getIsUserSetup() {
|
||||
return mUserSetup;
|
||||
}
|
||||
|
||||
private void setNotificationViewClipBounds(int height) {
|
||||
Rect clipBounds = new Rect();
|
||||
clipBounds.set(0, 0, mNotificationView.getWidth(), height);
|
||||
mNotificationView.setClipBounds(clipBounds);
|
||||
}
|
||||
|
||||
private void calculatePercentageFromBottom(float height) {
|
||||
if (mNotificationView.getHeight() > 0) {
|
||||
mPercentageFromBottom = (int) Math.abs(
|
||||
height / mNotificationView.getHeight() * 100);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add settle down/up logic
|
||||
private static final int SWIPE_UP_MIN_DISTANCE = 75;
|
||||
private static final int SWIPE_DOWN_MIN_DISTANCE = 25;
|
||||
private static final int SWIPE_MAX_OFF_PATH = 75;
|
||||
private static final int SWIPE_THRESHOLD_VELOCITY = 200;
|
||||
|
||||
// Only responsible for open hooks. Since once the panel opens it covers all elements
|
||||
// there is no need to merge with close.
|
||||
private abstract class OpenNotificationGestureListener extends
|
||||
GestureDetector.SimpleOnGestureListener {
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
|
||||
float distanceY) {
|
||||
|
||||
if (mNotificationView.getVisibility() == View.INVISIBLE) {
|
||||
// when the on-scroll is called for the first time to open.
|
||||
mNotificationList.scrollToPosition(0);
|
||||
}
|
||||
mStatusBarWindowController.setPanelVisible(true);
|
||||
mNotificationView.setVisibility(View.VISIBLE);
|
||||
|
||||
// clips the view for the notification shade when the user scrolls to open.
|
||||
setNotificationViewClipBounds((int) event2.getRawY());
|
||||
|
||||
// Initially the scroll starts with height being zero. This checks protects from divide
|
||||
// by zero error.
|
||||
calculatePercentageFromBottom(event2.getRawY());
|
||||
|
||||
mIsTracking = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent event1, MotionEvent event2,
|
||||
float velocityX, float velocityY) {
|
||||
if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
|
||||
|| Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
|
||||
// swipe was not vertical or was not fast enough
|
||||
return false;
|
||||
}
|
||||
boolean isDown = velocityY > 0;
|
||||
float distanceDelta = Math.abs(event1.getY() - event2.getY());
|
||||
if (isDown && distanceDelta > SWIPE_DOWN_MIN_DISTANCE) {
|
||||
if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
|
||||
mOpeningVelocity = velocityY;
|
||||
openNotification();
|
||||
return true;
|
||||
}
|
||||
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract void openNotification();
|
||||
}
|
||||
|
||||
@@ -799,36 +996,85 @@ public class CarStatusBar extends StatusBar implements
|
||||
private abstract class CloseNotificationGestureListener extends
|
||||
GestureDetector.SimpleOnGestureListener {
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
|
||||
float distanceY) {
|
||||
if (!mNotificationListAtBottomAtTimeOfTouch) {
|
||||
return false;
|
||||
}
|
||||
float actualNotificationHeight =
|
||||
mNotificationView.getHeight() - (event1.getRawY() - event2.getRawY());
|
||||
if (actualNotificationHeight > mNotificationView.getHeight()) {
|
||||
actualNotificationHeight = mNotificationView.getHeight();
|
||||
}
|
||||
if (mNotificationView.getHeight() > 0) {
|
||||
mPercentageFromBottom = (int) Math.abs(
|
||||
actualNotificationHeight / mNotificationView.getHeight() * 100);
|
||||
boolean isUp = distanceY > 0;
|
||||
|
||||
// This check is to figure out if onScroll was called while swiping the card at
|
||||
// bottom of the list. At that time we should not allow notification shade to
|
||||
// close. We are also checking for the upwards swipe gesture here because it is
|
||||
// possible if a user is closing the notification shade and while swiping starts
|
||||
// to open again but does not fling. At that time we should allow the
|
||||
// notification shade to close fully or else it would stuck in between.
|
||||
if (Math.abs(mNotificationView.getHeight() - actualNotificationHeight)
|
||||
> SWIPE_DOWN_MIN_DISTANCE && isUp) {
|
||||
setNotificationViewClipBounds((int) actualNotificationHeight);
|
||||
mIsTracking = true;
|
||||
} else if (!isUp) {
|
||||
setNotificationViewClipBounds((int) actualNotificationHeight);
|
||||
}
|
||||
}
|
||||
// if we return true the the items in RV won't be scrollable.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent event1, MotionEvent event2,
|
||||
float velocityX, float velocityY) {
|
||||
|
||||
if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
|
||||
|| Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
|
||||
// swipe was not vertical or was not fast enough
|
||||
return false;
|
||||
}
|
||||
boolean isUp = velocityY < 0;
|
||||
float distanceDelta = Math.abs(event1.getY() - event2.getY());
|
||||
if (isUp && distanceDelta > SWIPE_UP_MIN_DISTANCE) {
|
||||
if (isUp) {
|
||||
close();
|
||||
return true;
|
||||
} else {
|
||||
// we should close the shade
|
||||
animateNotificationPanel(velocityY, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract void close();
|
||||
}
|
||||
|
||||
// to be installed on the nav bars
|
||||
// To be installed on the nav bars.
|
||||
private abstract class NavBarCloseNotificationGestureListener extends
|
||||
CloseNotificationGestureListener {
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
mClosingVelocity = DEFAULT_FLING_VELOCITY;
|
||||
close();
|
||||
return super.onSingleTapUp(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
|
||||
float distanceY) {
|
||||
calculatePercentageFromBottom(event2.getRawY());
|
||||
setNotificationViewClipBounds((int) event2.getRawY());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
mClosingVelocity = DEFAULT_FLING_VELOCITY;
|
||||
close();
|
||||
super.onLongPress(e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user