Merge "Adding the touch gestures for the pull down notification shade." into qt-dev

This commit is contained in:
TreeHugger Robot
2019-04-22 03:33:14 +00:00
committed by Android (Google) Code Review

View File

@@ -16,11 +16,15 @@
package com.android.systemui.statusbar.car; package com.android.systemui.statusbar.car;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.ActivityTaskManager; import android.app.ActivityTaskManager;
import android.car.drivingstate.CarDrivingStateEvent; import android.car.drivingstate.CarDrivingStateEvent;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.util.Log; import android.util.Log;
import android.view.GestureDetector; 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.qs.car.CarQSFragment;
import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.car.hvac.HvacController; import com.android.systemui.statusbar.car.hvac.HvacController;
import com.android.systemui.statusbar.car.hvac.TemperatureView; import com.android.systemui.statusbar.car.hvac.TemperatureView;
@@ -74,6 +79,15 @@ import java.util.Map;
public class CarStatusBar extends StatusBar implements public class CarStatusBar extends StatusBar implements
CarBatteryController.BatteryViewHandler { CarBatteryController.BatteryViewHandler {
private static final String TAG = "CarStatusBar"; 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; private TaskStackListenerImpl mTaskStackListener;
@@ -100,6 +114,7 @@ public class CarStatusBar extends StatusBar implements
private boolean mDeviceIsProvisioned = true; private boolean mDeviceIsProvisioned = true;
private HvacController mHvacController; private HvacController mHvacController;
private DrivingStateHelper mDrivingStateHelper; private DrivingStateHelper mDrivingStateHelper;
private static FlingAnimationUtils sFlingAnimationUtils;
private SwitchToGuestTimer mSwitchToGuestTimer; private SwitchToGuestTimer mSwitchToGuestTimer;
// The container for the notifications. // The container for the notifications.
@@ -113,6 +128,27 @@ public class CarStatusBar extends StatusBar implements
// it's open. // it's open.
private View.OnTouchListener mNavBarNotificationTouchListener; 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 @Override
public void start() { public void start() {
// get the provisioned state before calling the parent class since it's that flow that // 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); mActivityManagerWrapper.registerTaskStackListener(mTaskStackListener);
mNotificationPanel.setScrollingEnabled(true); 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(); createBatteryController();
mCarBatteryController.startListening(); mCarBatteryController.startListening();
@@ -313,14 +355,46 @@ public class CarStatusBar extends StatusBar implements
} }
}); });
mNavBarNotificationTouchListener = 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. // 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. // This will set the status bar so it they can make call backs.
CarNavigationBarView topBar = mStatusBarWindow.findViewById(R.id.car_top_bar); CarNavigationBarView topBar = mStatusBarWindow.findViewById(R.id.car_top_bar);
topBar.setStatusBar(this); topBar.setStatusBar(this);
topBar.setStatusBarWindowTouchListener((v1, event1) -> topBar.setStatusBarWindowTouchListener((v1, event1) -> {
openGestureDetector.onTouchEvent(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( NotificationClickHandlerFactory clickHandlerFactory = new NotificationClickHandlerFactory(
mBarService, mBarService,
@@ -347,7 +421,10 @@ public class CarStatusBar extends StatusBar implements
mNotificationListAtBottomAtTimeOfTouch = false; mNotificationListAtBottomAtTimeOfTouch = false;
} }
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mFirstTouchDownOnGlassPane = event.getRawX();
mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom; 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 // Pass the down event to gesture detector so that it knows where the touch event
// started. // started.
closeGestureDetector.onTouchEvent(event); closeGestureDetector.onTouchEvent(event);
@@ -364,23 +441,56 @@ public class CarStatusBar extends StatusBar implements
return; return;
} }
mNotificationListAtBottom = false; mNotificationListAtBottom = false;
mIsSwipingVerticallyToClose = false;
mNotificationListAtBottomAtTimeOfTouch = false; mNotificationListAtBottomAtTimeOfTouch = false;
} }
}); });
mNotificationList.setOnTouchListener(new View.OnTouchListener() { mNotificationList.setOnTouchListener(new View.OnTouchListener() {
@Override @Override
public boolean onTouch(View v, MotionEvent event) { 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; boolean handled = false;
if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) { if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) {
handled = closeGestureDetector.onTouchEvent(event); 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 // Updating the mNotificationListAtBottomAtTimeOfTouch state has to be done after
// the event has been passed to the closeGestureDetector above, such that the // the event has been passed to the closeGestureDetector above, such that the
// closeGestureDetector sees the up event before the state has changed. // closeGestureDetector sees the up event before the state has changed.
if (event.getActionMasked() == MotionEvent.ACTION_UP) { if (event.getActionMasked() == MotionEvent.ACTION_UP) {
mNotificationListAtBottomAtTimeOfTouch = false; mNotificationListAtBottomAtTimeOfTouch = false;
} }
return handled; return handled || isTracking;
} }
}); });
@@ -401,7 +511,9 @@ public class CarStatusBar extends StatusBar implements
mNotificationList.scrollToPosition(0); mNotificationList.scrollToPosition(0);
mStatusBarWindowController.setPanelVisible(true); mStatusBarWindowController.setPanelVisible(true);
mNotificationView.setVisibility(View.VISIBLE); mNotificationView.setVisibility(View.VISIBLE);
// let the status bar know that the panel is open
animateNotificationPanel(mOpeningVelocity, false);
setPanelExpanded(true); setPanelExpanded(true);
} }
@@ -415,12 +527,66 @@ public class CarStatusBar extends StatusBar implements
mStatusBarWindowController.setStatusBarFocusable(false); mStatusBarWindowController.setStatusBarFocusable(false);
mStatusBarWindow.cancelExpandHelper(); mStatusBarWindow.cancelExpandHelper();
mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor); mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor);
mStatusBarWindowController.setPanelVisible(false);
mNotificationView.setVisibility(View.INVISIBLE); animateNotificationPanel(mClosingVelocity, true);
// let the status bar know that the panel is cloased
if (!mIsTracking) {
mStatusBarWindowController.setPanelVisible(false);
mNotificationView.setVisibility(View.INVISIBLE);
}
setPanelExpanded(false); 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 @Override
protected QS createDefaultQSFragment() { protected QS createDefaultQSFragment() {
@@ -576,7 +742,7 @@ public class CarStatusBar extends StatusBar implements
@Override @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 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. //to get mStackScroller's position correctly.
synchronized (mQueueLock) { synchronized (mQueueLock) {
pw.println(" mStackScroller: " + viewInfo(mStackScroller)); 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. */ /** Returns true if the current user makes it through the setup wizard, false otherwise. */
public boolean getIsUserSetup(){ private boolean getIsUserSetup() {
return mUserSetup; 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_UP_MIN_DISTANCE = 75;
private static final int SWIPE_DOWN_MIN_DISTANCE = 25; private static final int SWIPE_DOWN_MIN_DISTANCE = 25;
private static final int SWIPE_MAX_OFF_PATH = 75; private static final int SWIPE_MAX_OFF_PATH = 75;
private static final int SWIPE_THRESHOLD_VELOCITY = 200; private static final int SWIPE_THRESHOLD_VELOCITY = 200;
// Only responsible for open hooks. Since once the panel opens it covers all elements // Only responsible for open hooks. Since once the panel opens it covers all elements
// there is no need to merge with close. // there is no need to merge with close.
private abstract class OpenNotificationGestureListener extends private abstract class OpenNotificationGestureListener extends
GestureDetector.SimpleOnGestureListener { 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 @Override
public boolean onFling(MotionEvent event1, MotionEvent event2, public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) { float velocityX, float velocityY) {
if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
|| Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { mOpeningVelocity = velocityY;
// 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) {
openNotification(); openNotification();
return true; return true;
} }
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
return false; return false;
} }
protected abstract void openNotification(); protected abstract void openNotification();
} }
@@ -799,36 +996,85 @@ public class CarStatusBar extends StatusBar implements
private abstract class CloseNotificationGestureListener extends private abstract class CloseNotificationGestureListener extends
GestureDetector.SimpleOnGestureListener { 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 @Override
public boolean onFling(MotionEvent event1, MotionEvent event2, public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) { float velocityX, float velocityY) {
if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
|| Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
// swipe was not vertical or was not fast enough // swipe was not vertical or was not fast enough
return false; return false;
} }
boolean isUp = velocityY < 0; boolean isUp = velocityY < 0;
float distanceDelta = Math.abs(event1.getY() - event2.getY()); if (isUp) {
if (isUp && distanceDelta > SWIPE_UP_MIN_DISTANCE) {
close(); close();
return true; return true;
} else {
// we should close the shade
animateNotificationPanel(velocityY, false);
} }
return false; return false;
} }
protected abstract void close(); protected abstract void close();
} }
// to be installed on the nav bars // To be installed on the nav bars.
private abstract class NavBarCloseNotificationGestureListener extends private abstract class NavBarCloseNotificationGestureListener extends
CloseNotificationGestureListener { CloseNotificationGestureListener {
@Override @Override
public boolean onSingleTapUp(MotionEvent e) { public boolean onSingleTapUp(MotionEvent e) {
mClosingVelocity = DEFAULT_FLING_VELOCITY;
close(); close();
return super.onSingleTapUp(e); 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 @Override
public void onLongPress(MotionEvent e) { public void onLongPress(MotionEvent e) {
mClosingVelocity = DEFAULT_FLING_VELOCITY;
close(); close();
super.onLongPress(e); super.onLongPress(e);
} }