Merge "Generalize notifications logic into a subclass of OverlayViewController" into rvc-dev
This commit is contained in:
@@ -20,8 +20,6 @@
|
||||
android:id="@+id/notification_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible"
|
||||
android:layout_marginBottom="@dimen/navigation_bar_height"
|
||||
android:background="@color/notification_shade_background_color">
|
||||
|
||||
<View
|
||||
|
||||
@@ -18,4 +18,5 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/notification_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
@@ -22,10 +22,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- TODO(b/151617493): replace marginBottom with insets. -->
|
||||
<ViewStub android:id="@+id/notification_panel_stub"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout="@layout/notification_panel_container"/>
|
||||
android:layout="@layout/notification_panel_container"
|
||||
android:layout_marginBottom="@dimen/navigation_bar_height"/>
|
||||
|
||||
<ViewStub android:id="@+id/fullscreen_user_switcher_stub"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -24,7 +24,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
|
||||
*/
|
||||
public interface CarDeviceProvisionedController extends DeviceProvisionedController {
|
||||
/**
|
||||
* Returns {@code true} then SUW is in progress for the given user.
|
||||
* Returns {@code true} when SUW is in progress for the given user.
|
||||
*/
|
||||
boolean isUserSetupInProgress(int user);
|
||||
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
package com.android.systemui.car.notification;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.ActivityManager;
|
||||
import android.car.Car;
|
||||
import android.car.drivingstate.CarUxRestrictionsManager;
|
||||
@@ -33,7 +30,6 @@ import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -54,7 +50,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.CommandQueue;
|
||||
import com.android.systemui.statusbar.FlingAnimationUtils;
|
||||
import com.android.systemui.statusbar.StatusBarState;
|
||||
import com.android.systemui.window.OverlayViewController;
|
||||
import com.android.systemui.window.OverlayPanelViewController;
|
||||
import com.android.systemui.window.OverlayViewGlobalStateController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -62,39 +58,22 @@ import javax.inject.Singleton;
|
||||
|
||||
/** View controller for the notification panel. */
|
||||
@Singleton
|
||||
public class NotificationPanelViewController extends OverlayViewController {
|
||||
public class NotificationPanelViewController extends OverlayPanelViewController {
|
||||
|
||||
// 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 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;
|
||||
private static final boolean DEBUG = true;
|
||||
private static final String TAG = "NotificationPanelViewController";
|
||||
|
||||
private final Context mContext;
|
||||
private final Resources mResources;
|
||||
private final CarServiceProvider mCarServiceProvider;
|
||||
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
|
||||
private final IStatusBarService mBarService;
|
||||
private final CommandQueue mCommandQueue;
|
||||
private final NotificationDataManager mNotificationDataManager;
|
||||
private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
|
||||
private final CarNotificationListener mCarNotificationListener;
|
||||
private final NotificationClickHandlerFactory mNotificationClickHandlerFactory;
|
||||
private final FlingAnimationUtils mFlingAnimationUtils;
|
||||
private final StatusBarStateController mStatusBarStateController;
|
||||
|
||||
private final int mSettleClosePercentage;
|
||||
|
||||
private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
|
||||
private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
|
||||
|
||||
private float mInitialBackgroundAlpha;
|
||||
private float mBackgroundAlphaDiff;
|
||||
|
||||
@@ -108,13 +87,7 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
private float mFirstTouchDownOnGlassPane;
|
||||
private boolean mNotificationListAtBottomAtTimeOfTouch;
|
||||
private boolean mIsSwipingVerticallyToClose;
|
||||
private int mPercentageFromBottom;
|
||||
private boolean mIsNotificationAnimating;
|
||||
private boolean mIsNotificationCardSwiping;
|
||||
private boolean mPanelExpanded = false;
|
||||
|
||||
private View.OnTouchListener mTopNavBarNotificationTouchListener;
|
||||
private View.OnTouchListener mNavBarNotificationTouchListener;
|
||||
|
||||
private OnUnseenCountUpdateListener mUnseenCountUpdateListener;
|
||||
|
||||
@@ -123,6 +96,7 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
Context context,
|
||||
@Main Resources resources,
|
||||
OverlayViewGlobalStateController overlayViewGlobalStateController,
|
||||
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
|
||||
|
||||
/* Other things */
|
||||
CarServiceProvider carServiceProvider,
|
||||
@@ -135,26 +109,21 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
|
||||
CarNotificationListener carNotificationListener,
|
||||
NotificationClickHandlerFactory notificationClickHandlerFactory,
|
||||
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
|
||||
|
||||
/* Things that need to be replaced */
|
||||
StatusBarStateController statusBarStateController
|
||||
) {
|
||||
super(R.id.notification_panel_stub, overlayViewGlobalStateController);
|
||||
super(context, resources, R.id.notification_panel_stub, overlayViewGlobalStateController,
|
||||
flingAnimationUtilsBuilder, carDeviceProvisionedController);
|
||||
mContext = context;
|
||||
mResources = resources;
|
||||
mCarServiceProvider = carServiceProvider;
|
||||
mCarDeviceProvisionedController = carDeviceProvisionedController;
|
||||
mBarService = barService;
|
||||
mCommandQueue = commandQueue;
|
||||
mNotificationDataManager = notificationDataManager;
|
||||
mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper;
|
||||
mCarNotificationListener = carNotificationListener;
|
||||
mNotificationClickHandlerFactory = notificationClickHandlerFactory;
|
||||
mFlingAnimationUtils = flingAnimationUtilsBuilder
|
||||
.setMaxLengthSeconds(FLING_ANIMATION_MAX_TIME)
|
||||
.setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
|
||||
.build();
|
||||
mStatusBarStateController = statusBarStateController;
|
||||
|
||||
// Notification background setup.
|
||||
@@ -175,60 +144,6 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
+ " percentage");
|
||||
}
|
||||
mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha;
|
||||
|
||||
// Notification Panel param setup
|
||||
mSettleClosePercentage = mResources.getInteger(
|
||||
R.integer.notification_settle_close_percentage);
|
||||
|
||||
// Attached to the top navigation bar (i.e. status bar) to detect pull down of the
|
||||
// notification shade.
|
||||
GestureDetector openGestureDetector = new GestureDetector(mContext,
|
||||
new OpenNotificationGestureListener() {
|
||||
@Override
|
||||
protected void openNotification() {
|
||||
animateExpandNotificationsPanel();
|
||||
}
|
||||
});
|
||||
|
||||
// Attached to the NavBars to close the notification shade
|
||||
GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(mContext,
|
||||
new NavBarCloseNotificationGestureListener() {
|
||||
@Override
|
||||
protected void close() {
|
||||
if (mPanelExpanded) {
|
||||
animateCollapsePanels();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mTopNavBarNotificationTouchListener = (v, event) -> {
|
||||
if (!isInflated()) {
|
||||
getOverlayViewGlobalStateController().inflateView(this);
|
||||
}
|
||||
if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean consumed = openGestureDetector.onTouchEvent(event);
|
||||
if (consumed) {
|
||||
return true;
|
||||
}
|
||||
maybeCompleteAnimation(event);
|
||||
return true;
|
||||
};
|
||||
|
||||
mNavBarNotificationTouchListener =
|
||||
(v, event) -> {
|
||||
if (!isInflated()) {
|
||||
return true;
|
||||
}
|
||||
boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
|
||||
if (consumed) {
|
||||
return true;
|
||||
}
|
||||
maybeCompleteAnimation(event);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -252,14 +167,13 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
private void onNotificationViewInflated() {
|
||||
// Find views.
|
||||
mNotificationView = getLayout().findViewById(R.id.notification_view);
|
||||
View glassPane = mNotificationView.findViewById(R.id.glass_pane);
|
||||
mHandleBar = mNotificationView.findViewById(R.id.handle_bar);
|
||||
mNotificationList = mNotificationView.findViewById(R.id.notifications);
|
||||
setupHandleBar();
|
||||
setupNotificationPanel();
|
||||
|
||||
mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> {
|
||||
if (launchResult == ActivityManager.START_TASK_TO_FRONT
|
||||
|| launchResult == ActivityManager.START_SUCCESS) {
|
||||
animateCollapsePanels();
|
||||
animateCollapsePanel();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -269,39 +183,52 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
mNotificationDataManager.getUnseenNotificationCount());
|
||||
}
|
||||
});
|
||||
|
||||
mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
|
||||
mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory);
|
||||
mNotificationView.setNotificationDataManager(mNotificationDataManager);
|
||||
|
||||
mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
if (!mNotificationList.canScrollVertically(1)) {
|
||||
mNotificationListAtBottom = true;
|
||||
return;
|
||||
}
|
||||
mNotificationListAtBottom = false;
|
||||
mIsSwipingVerticallyToClose = false;
|
||||
mNotificationListAtBottomAtTimeOfTouch = false;
|
||||
}
|
||||
});
|
||||
mCarServiceProvider.addListener(car -> {
|
||||
CarUxRestrictionsManager carUxRestrictionsManager =
|
||||
(CarUxRestrictionsManager)
|
||||
car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
|
||||
mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
|
||||
carUxRestrictionsManager);
|
||||
|
||||
// Attached to the notification ui to detect close request of the notification shade.
|
||||
mNotificationViewController = new NotificationViewController(
|
||||
mNotificationView,
|
||||
PreprocessingManager.getInstance(mContext),
|
||||
mCarNotificationListener,
|
||||
mCarUxRestrictionManagerWrapper,
|
||||
mNotificationDataManager);
|
||||
mNotificationViewController.enable();
|
||||
});
|
||||
}
|
||||
|
||||
private void setupHandleBar() {
|
||||
mHandleBar = mNotificationView.findViewById(R.id.handle_bar);
|
||||
GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
|
||||
new HandleBarCloseGestureListener());
|
||||
mHandleBar.setOnTouchListener((v, event) -> {
|
||||
handleBarCloseNotificationGestureDetector.onTouchEvent(event);
|
||||
maybeCompleteAnimation(event);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void setupNotificationPanel() {
|
||||
View glassPane = mNotificationView.findViewById(R.id.glass_pane);
|
||||
mNotificationList = mNotificationView.findViewById(R.id.notifications);
|
||||
GestureDetector closeGestureDetector = new GestureDetector(mContext,
|
||||
new CloseNotificationGestureListener() {
|
||||
new CloseGestureListener() {
|
||||
@Override
|
||||
protected void close() {
|
||||
if (mPanelExpanded) {
|
||||
animateCollapsePanels();
|
||||
if (isPanelExpanded()) {
|
||||
animateCollapsePanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Attached to the Handle bar to close the notification shade
|
||||
GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
|
||||
new HandleBarCloseNotificationGestureListener());
|
||||
|
||||
// The glass pane is used to view touch events before passed to the notification list.
|
||||
// This allows us to initialize gesture listeners and detect when to close the notifications
|
||||
glassPane.setOnTouchListener((v, event) -> {
|
||||
@@ -320,6 +247,21 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
return false;
|
||||
});
|
||||
|
||||
mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
// Check if we can scroll vertically downwards.
|
||||
if (!mNotificationList.canScrollVertically(/* direction= */ 1)) {
|
||||
mNotificationListAtBottom = true;
|
||||
return;
|
||||
}
|
||||
mNotificationListAtBottom = false;
|
||||
mIsSwipingVerticallyToClose = false;
|
||||
mNotificationListAtBottomAtTimeOfTouch = false;
|
||||
}
|
||||
});
|
||||
|
||||
mNotificationList.setOnTouchListener((v, event) -> {
|
||||
mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
|
||||
> SWIPE_MAX_OFF_PATH;
|
||||
@@ -341,19 +283,19 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
|
||||
boolean handled = closeGestureDetector.onTouchEvent(event);
|
||||
boolean isTracking = mIsTracking;
|
||||
Rect rect = mNotificationView.getClipBounds();
|
||||
Rect rect = getLayout().getClipBounds();
|
||||
float clippedHeight = 0;
|
||||
if (rect != null) {
|
||||
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) {
|
||||
if (getSettleClosePercentage() < getPercentageFromBottom() && isTracking) {
|
||||
animatePanel(DEFAULT_FLING_VELOCITY, false);
|
||||
} else if (clippedHeight != getLayout().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);
|
||||
animatePanel(DEFAULT_FLING_VELOCITY, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,28 +307,6 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
}
|
||||
return handled || isTracking;
|
||||
});
|
||||
|
||||
mCarServiceProvider.addListener(car -> {
|
||||
CarUxRestrictionsManager carUxRestrictionsManager =
|
||||
(CarUxRestrictionsManager)
|
||||
car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
|
||||
mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
|
||||
carUxRestrictionsManager);
|
||||
|
||||
mNotificationViewController = new NotificationViewController(
|
||||
mNotificationView,
|
||||
PreprocessingManager.getInstance(mContext),
|
||||
mCarNotificationListener,
|
||||
mCarUxRestrictionManagerWrapper,
|
||||
mNotificationDataManager);
|
||||
mNotificationViewController.enable();
|
||||
});
|
||||
|
||||
mHandleBar.setOnTouchListener((v, event) -> {
|
||||
handleBarCloseNotificationGestureDetector.onTouchEvent(event);
|
||||
maybeCompleteAnimation(event);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/** Called when the car power state is changed to ON. */
|
||||
@@ -397,139 +317,40 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
mNotificationDataManager.clearAll();
|
||||
}
|
||||
|
||||
View.OnTouchListener getTopNavBarNotificationTouchListener() {
|
||||
return mTopNavBarNotificationTouchListener;
|
||||
@Override
|
||||
protected boolean shouldAnimateCollapsePanel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
View.OnTouchListener getNavBarNotificationTouchListener() {
|
||||
return mNavBarNotificationTouchListener;
|
||||
@Override
|
||||
protected void onAnimateCollapsePanel() {
|
||||
// No op.
|
||||
}
|
||||
|
||||
private void maybeCompleteAnimation(MotionEvent event) {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_UP
|
||||
&& mNotificationView.getVisibility() == View.VISIBLE) {
|
||||
if (mSettleClosePercentage < mPercentageFromBottom) {
|
||||
animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
|
||||
} else {
|
||||
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected boolean shouldAnimateExpandPanel() {
|
||||
return mCommandQueue.panelsEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the notification shade from one position to other. This is used to either open or
|
||||
* close the notification shade completely with a velocity. If 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) {
|
||||
float to = 0;
|
||||
if (!isClosing) {
|
||||
to = mNotificationView.getHeight();
|
||||
}
|
||||
|
||||
Rect rect = mNotificationView.getClipBounds();
|
||||
if (rect != null && rect.bottom != to) {
|
||||
float from = rect.bottom;
|
||||
animate(from, to, velocity, isClosing);
|
||||
return;
|
||||
}
|
||||
|
||||
// We will only be here if the shade is being opened programmatically or via button when
|
||||
// height of the layout was not calculated.
|
||||
ViewTreeObserver notificationTreeObserver = mNotificationView.getViewTreeObserver();
|
||||
notificationTreeObserver.addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
ViewTreeObserver obs = mNotificationView.getViewTreeObserver();
|
||||
obs.removeOnGlobalLayoutListener(this);
|
||||
float to = mNotificationView.getHeight();
|
||||
animate(/* from= */ 0, to, velocity, isClosing);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void animateCollapsePanels() {
|
||||
if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) {
|
||||
return;
|
||||
}
|
||||
getOverlayViewGlobalStateController().setWindowFocusable(false);
|
||||
animateNotificationPanel(mClosingVelocity, true);
|
||||
}
|
||||
|
||||
private void animateExpandNotificationsPanel() {
|
||||
if (!mCommandQueue.panelsEnabled()
|
||||
|| !mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
|
||||
return;
|
||||
}
|
||||
// scroll to top
|
||||
@Override
|
||||
protected void onAnimateExpandPanel() {
|
||||
mNotificationList.scrollToPosition(0);
|
||||
setPanelVisible(true);
|
||||
mNotificationView.setVisibility(View.VISIBLE);
|
||||
animateNotificationPanel(mOpeningVelocity, false);
|
||||
|
||||
setPanelExpanded(true);
|
||||
}
|
||||
|
||||
private void animate(float from, float to, float velocity, boolean isClosing) {
|
||||
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) {
|
||||
setPanelVisible(false);
|
||||
mNotificationView.setVisibility(View.INVISIBLE);
|
||||
mNotificationView.setClipBounds(null);
|
||||
mNotificationViewController.onVisibilityChanged(false);
|
||||
// let the status bar know that the panel is closed
|
||||
setPanelExpanded(false);
|
||||
} else {
|
||||
mNotificationViewController.onVisibilityChanged(true);
|
||||
// let the status bar know that the panel is open
|
||||
mNotificationView.setVisibleNotificationsAsSeen();
|
||||
setPanelExpanded(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
mFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity));
|
||||
animator.start();
|
||||
@Override
|
||||
protected void onCollapseAnimationEnd() {
|
||||
mNotificationViewController.onVisibilityChanged(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the panel view to be visible.
|
||||
*/
|
||||
public void setPanelVisible(boolean visible) {
|
||||
if (visible && !getOverlayViewGlobalStateController().isWindowVisible()) {
|
||||
getOverlayViewGlobalStateController().setWindowVisible(true);
|
||||
}
|
||||
if (!visible && getOverlayViewGlobalStateController().isWindowVisible()) {
|
||||
getOverlayViewGlobalStateController().setWindowVisible(false);
|
||||
}
|
||||
getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
|
||||
getOverlayViewGlobalStateController().setWindowFocusable(visible);
|
||||
@Override
|
||||
protected void onExpandAnimationEnd() {
|
||||
mNotificationViewController.onVisibilityChanged(true);
|
||||
mNotificationView.setVisibleNotificationsAsSeen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the panel state to expanded. This will expand or collapse the overlay window if
|
||||
* necessary.
|
||||
*/
|
||||
public void setPanelExpanded(boolean expand) {
|
||||
mPanelExpanded = expand;
|
||||
@Override
|
||||
protected void onPanelExpanded(boolean expand) {
|
||||
super.onPanelExpanded(expand);
|
||||
|
||||
if (expand && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
|
||||
if (DEBUG) {
|
||||
@@ -550,19 +371,19 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private void setNotificationViewClipBounds(int height) {
|
||||
if (height > mNotificationView.getHeight()) {
|
||||
height = mNotificationView.getHeight();
|
||||
}
|
||||
Rect clipBounds = new Rect();
|
||||
clipBounds.set(0, 0, mNotificationView.getWidth(), height);
|
||||
// Sets the clip region on the notification list view.
|
||||
mNotificationView.setClipBounds(clipBounds);
|
||||
@Override
|
||||
protected void onOpenScrollStart() {
|
||||
mNotificationList.scrollToPosition(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScroll(int height) {
|
||||
if (mHandleBar != null) {
|
||||
ViewGroup.MarginLayoutParams lp =
|
||||
(ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams();
|
||||
mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
|
||||
}
|
||||
|
||||
if (mNotificationView.getHeight() > 0) {
|
||||
Drawable background = mNotificationView.getBackground().mutate();
|
||||
background.setAlpha((int) (getBackgroundAlpha(height) * 255));
|
||||
@@ -570,6 +391,13 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldAllowClosingScroll() {
|
||||
// Unless the notification list is at the bottom, the panel shouldn't be allowed to
|
||||
// collapse on scroll.
|
||||
return mNotificationListAtBottomAtTimeOfTouch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the alpha value for the background based on how much of the notification
|
||||
* shade is visible to the user. When the notification shade is completely open then
|
||||
@@ -580,30 +408,6 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
+ ((float) height / mNotificationView.getHeight() * mBackgroundAlphaDiff);
|
||||
}
|
||||
|
||||
private void calculatePercentageFromBottom(float height) {
|
||||
if (mNotificationView.getHeight() > 0) {
|
||||
mPercentageFromBottom = (int) Math.abs(
|
||||
height / mNotificationView.getHeight() * 100);
|
||||
}
|
||||
}
|
||||
|
||||
/** Toggles the visibility of the notification panel. */
|
||||
public void toggle() {
|
||||
if (!isInflated()) {
|
||||
getOverlayViewGlobalStateController().inflateView(this);
|
||||
}
|
||||
if (mPanelExpanded) {
|
||||
animateCollapsePanels();
|
||||
} else {
|
||||
animateExpandNotificationsPanel();
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns {@code true} if the notification panel is expanded. */
|
||||
public boolean isPanelExpanded() {
|
||||
return mPanelExpanded;
|
||||
}
|
||||
|
||||
/** Sets the unseen count listener. */
|
||||
public void setOnUnseenCountUpdateListener(OnUnseenCountUpdateListener listener) {
|
||||
mUnseenCountUpdateListener = listener;
|
||||
@@ -618,155 +422,10 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
void onUnseenCountUpdate(int unseenNotificationCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
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 (velocityY > SWIPE_THRESHOLD_VELOCITY) {
|
||||
mOpeningVelocity = velocityY;
|
||||
openNotification();
|
||||
return true;
|
||||
}
|
||||
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract void openNotification();
|
||||
}
|
||||
|
||||
/**
|
||||
* To be installed on the open panel notification panel
|
||||
*/
|
||||
private abstract class CloseNotificationGestureListener extends
|
||||
GestureDetector.SimpleOnGestureListener {
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent motionEvent) {
|
||||
if (mPanelExpanded) {
|
||||
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
|
||||
float distanceY) {
|
||||
// should not clip while scroll to the bottom of the list.
|
||||
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 items in RV won't be scrollable.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent event1, MotionEvent event2,
|
||||
float velocityX, float velocityY) {
|
||||
// should not fling if the touch does not start when view is at the bottom of the list.
|
||||
if (!mNotificationListAtBottomAtTimeOfTouch) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
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.
|
||||
*/
|
||||
private abstract class NavBarCloseNotificationGestureListener extends
|
||||
CloseNotificationGestureListener {
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
mClosingVelocity = DEFAULT_FLING_VELOCITY;
|
||||
if (mPanelExpanded) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To be installed on the handle bar.
|
||||
*/
|
||||
private class HandleBarCloseNotificationGestureListener extends
|
||||
private class HandleBarCloseGestureListener extends
|
||||
GestureDetector.SimpleOnGestureListener {
|
||||
|
||||
@Override
|
||||
@@ -777,9 +436,8 @@ public class NotificationPanelViewController extends OverlayViewController {
|
||||
// the handle bar we should calculate the height using the diff of event1 and event2.
|
||||
// This will help the notification shade to clip smoothly as the event2 value changes
|
||||
// as event1 value will be fixed.
|
||||
int clipHeight =
|
||||
mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY());
|
||||
setNotificationViewClipBounds(clipHeight);
|
||||
int clipHeight = getLayout().getHeight() - (int) (event1.getRawY() - event2.getRawY());
|
||||
setViewClipBounds(clipHeight);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,13 +59,13 @@ public class NotificationPanelViewMediator implements OverlayViewMediator,
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
mCarNavigationBarController.registerTopBarTouchListener(
|
||||
mNotificationPanelViewController.getTopNavBarNotificationTouchListener());
|
||||
mNotificationPanelViewController.getDragOpenTouchListener());
|
||||
mCarNavigationBarController.registerBottomBarTouchListener(
|
||||
mNotificationPanelViewController.getNavBarNotificationTouchListener());
|
||||
mNotificationPanelViewController.getDragCloseTouchListener());
|
||||
mCarNavigationBarController.registerLeftBarTouchListener(
|
||||
mNotificationPanelViewController.getNavBarNotificationTouchListener());
|
||||
mNotificationPanelViewController.getDragCloseTouchListener());
|
||||
mCarNavigationBarController.registerRightBarTouchListener(
|
||||
mNotificationPanelViewController.getNavBarNotificationTouchListener());
|
||||
mNotificationPanelViewController.getDragCloseTouchListener());
|
||||
|
||||
mCarNavigationBarController.registerNotificationController(
|
||||
new CarNavigationBarController.NotificationsShadeController() {
|
||||
|
||||
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.window;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.car.CarDeviceProvisionedController;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.statusbar.FlingAnimationUtils;
|
||||
|
||||
/**
|
||||
* The {@link OverlayPanelViewController} provides additional dragging animation capabilities to
|
||||
* {@link OverlayViewController}.
|
||||
*/
|
||||
public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
|
||||
private static final boolean DEBUG = true;
|
||||
private static final String TAG = "OverlayPanelViewController";
|
||||
|
||||
// used to calculate how fast to open or close the window
|
||||
protected static final float DEFAULT_FLING_VELOCITY = 0;
|
||||
// max time a fling animation takes
|
||||
protected static final float FLING_ANIMATION_MAX_TIME = 0.5f;
|
||||
// acceleration rate for the fling animation
|
||||
protected static final float FLING_SPEED_UP_FACTOR = 0.6f;
|
||||
|
||||
protected static final int SWIPE_DOWN_MIN_DISTANCE = 25;
|
||||
protected static final int SWIPE_MAX_OFF_PATH = 75;
|
||||
protected static final int SWIPE_THRESHOLD_VELOCITY = 200;
|
||||
|
||||
private final FlingAnimationUtils mFlingAnimationUtils;
|
||||
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
|
||||
private final View.OnTouchListener mDragOpenTouchListener;
|
||||
private final View.OnTouchListener mDragCloseTouchListener;
|
||||
|
||||
private final int mSettleClosePercentage;
|
||||
private int mPercentageFromBottom;
|
||||
|
||||
private boolean mPanelVisible;
|
||||
private boolean mPanelExpanded;
|
||||
|
||||
private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
|
||||
private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
|
||||
|
||||
private boolean mIsAnimating;
|
||||
private boolean mIsTracking;
|
||||
|
||||
public OverlayPanelViewController(
|
||||
Context context,
|
||||
@Main Resources resources,
|
||||
int stubId,
|
||||
OverlayViewGlobalStateController overlayViewGlobalStateController,
|
||||
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
|
||||
CarDeviceProvisionedController carDeviceProvisionedController
|
||||
) {
|
||||
super(stubId, overlayViewGlobalStateController);
|
||||
|
||||
mFlingAnimationUtils = flingAnimationUtilsBuilder
|
||||
.setMaxLengthSeconds(FLING_ANIMATION_MAX_TIME)
|
||||
.setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
|
||||
.build();
|
||||
mCarDeviceProvisionedController = carDeviceProvisionedController;
|
||||
|
||||
mSettleClosePercentage = resources.getInteger(
|
||||
R.integer.notification_settle_close_percentage);
|
||||
|
||||
// Attached to the top navigation bar (i.e. status bar) to detect pull down of the
|
||||
// notification shade.
|
||||
GestureDetector openGestureDetector = new GestureDetector(context,
|
||||
new OpenGestureListener() {
|
||||
@Override
|
||||
protected void open() {
|
||||
animateExpandPanel();
|
||||
}
|
||||
});
|
||||
|
||||
// Attached to the NavBars to close the notification shade
|
||||
GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(context,
|
||||
new SystemBarCloseGestureListener() {
|
||||
@Override
|
||||
protected void close() {
|
||||
if (isPanelExpanded()) {
|
||||
animateCollapsePanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mDragOpenTouchListener = (v, event) -> {
|
||||
if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
|
||||
return true;
|
||||
}
|
||||
if (!isInflated()) {
|
||||
getOverlayViewGlobalStateController().inflateView(this);
|
||||
}
|
||||
|
||||
boolean consumed = openGestureDetector.onTouchEvent(event);
|
||||
if (consumed) {
|
||||
return true;
|
||||
}
|
||||
maybeCompleteAnimation(event);
|
||||
return true;
|
||||
};
|
||||
|
||||
mDragCloseTouchListener = (v, event) -> {
|
||||
if (!isInflated()) {
|
||||
return true;
|
||||
}
|
||||
boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
|
||||
if (consumed) {
|
||||
return true;
|
||||
}
|
||||
maybeCompleteAnimation(event);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** Toggles the visibility of the panel. */
|
||||
public void toggle() {
|
||||
if (!isInflated()) {
|
||||
getOverlayViewGlobalStateController().inflateView(this);
|
||||
}
|
||||
if (isPanelExpanded()) {
|
||||
animateCollapsePanel();
|
||||
} else {
|
||||
animateExpandPanel();
|
||||
}
|
||||
}
|
||||
|
||||
/* ***************************************************************************************** *
|
||||
* Panel Animation
|
||||
* ***************************************************************************************** */
|
||||
|
||||
/** Animates the closing of the panel. */
|
||||
protected void animateCollapsePanel() {
|
||||
if (!shouldAnimateCollapsePanel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPanelExpanded() || !isPanelVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onAnimateCollapsePanel();
|
||||
getOverlayViewGlobalStateController().setWindowFocusable(false);
|
||||
animatePanel(mClosingVelocity, /* isClosing= */ true);
|
||||
}
|
||||
|
||||
/** Determines whether {@link #animateCollapsePanel()} should collapse the panel. */
|
||||
protected abstract boolean shouldAnimateCollapsePanel();
|
||||
|
||||
/** Called when the panel is beginning to collapse. */
|
||||
protected abstract void onAnimateCollapsePanel();
|
||||
|
||||
/** Animates the expansion of the panel. */
|
||||
protected void animateExpandPanel() {
|
||||
if (!shouldAnimateExpandPanel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onAnimateExpandPanel();
|
||||
setPanelVisible(true);
|
||||
animatePanel(mOpeningVelocity, /* isClosing= */ false);
|
||||
|
||||
setPanelExpanded(true);
|
||||
}
|
||||
|
||||
/** Determines whether {@link #animateExpandPanel()}} should expand the panel. */
|
||||
protected abstract boolean shouldAnimateExpandPanel();
|
||||
|
||||
/** Called when the panel is beginning to expand. */
|
||||
protected abstract void onAnimateExpandPanel();
|
||||
|
||||
/**
|
||||
* Depending on certain conditions, determines whether to fully expand or collapse the panel.
|
||||
*/
|
||||
protected void maybeCompleteAnimation(MotionEvent event) {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_UP
|
||||
&& isPanelVisible()) {
|
||||
if (mSettleClosePercentage < mPercentageFromBottom) {
|
||||
animatePanel(DEFAULT_FLING_VELOCITY, false);
|
||||
} else {
|
||||
animatePanel(DEFAULT_FLING_VELOCITY, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the panel from one position to other. This is used to either open or
|
||||
* close the panel completely with a velocity. If the animation is to close the
|
||||
* panel this method also makes the view invisible after animation ends.
|
||||
*/
|
||||
protected void animatePanel(float velocity, boolean isClosing) {
|
||||
float to = 0;
|
||||
if (!isClosing) {
|
||||
to = getLayout().getHeight();
|
||||
}
|
||||
|
||||
Rect rect = getLayout().getClipBounds();
|
||||
if (rect != null && rect.bottom != to) {
|
||||
float from = rect.bottom;
|
||||
animate(from, to, velocity, isClosing);
|
||||
return;
|
||||
}
|
||||
|
||||
// We will only be here if the shade is being opened programmatically or via button when
|
||||
// height of the layout was not calculated.
|
||||
ViewTreeObserver notificationTreeObserver = getLayout().getViewTreeObserver();
|
||||
notificationTreeObserver.addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
ViewTreeObserver obs = getLayout().getViewTreeObserver();
|
||||
obs.removeOnGlobalLayoutListener(this);
|
||||
float to = getLayout().getHeight();
|
||||
animate(/* from= */ 0, to, velocity, isClosing);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void animate(float from, float to, float velocity, boolean isClosing) {
|
||||
if (mIsAnimating) {
|
||||
return;
|
||||
}
|
||||
mIsAnimating = true;
|
||||
mIsTracking = true;
|
||||
ValueAnimator animator = ValueAnimator.ofFloat(from, to);
|
||||
animator.addUpdateListener(
|
||||
animation -> {
|
||||
float animatedValue = (Float) animation.getAnimatedValue();
|
||||
setViewClipBounds((int) animatedValue);
|
||||
});
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
mIsAnimating = false;
|
||||
mIsTracking = false;
|
||||
mOpeningVelocity = DEFAULT_FLING_VELOCITY;
|
||||
mClosingVelocity = DEFAULT_FLING_VELOCITY;
|
||||
if (isClosing) {
|
||||
setPanelVisible(false);
|
||||
getLayout().setClipBounds(null);
|
||||
onCollapseAnimationEnd();
|
||||
setPanelExpanded(false);
|
||||
} else {
|
||||
onExpandAnimationEnd();
|
||||
setPanelExpanded(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
getFlingAnimationUtils().apply(animator, from, to, Math.abs(velocity));
|
||||
animator.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in {@link Animator.AnimatorListener#onAnimationEnd(Animator)} when the panel is
|
||||
* closing.
|
||||
*/
|
||||
protected abstract void onCollapseAnimationEnd();
|
||||
|
||||
/**
|
||||
* Called in {@link Animator.AnimatorListener#onAnimationEnd(Animator)} when the panel is
|
||||
* opening.
|
||||
*/
|
||||
protected abstract void onExpandAnimationEnd();
|
||||
|
||||
/* ***************************************************************************************** *
|
||||
* Panel Visibility
|
||||
* ***************************************************************************************** */
|
||||
|
||||
/** Set the panel view to be visible. */
|
||||
protected final void setPanelVisible(boolean visible) {
|
||||
mPanelVisible = visible;
|
||||
onPanelVisible(visible);
|
||||
}
|
||||
|
||||
/** Returns {@code true} if panel is visible. */
|
||||
public final boolean isPanelVisible() {
|
||||
return mPanelVisible;
|
||||
}
|
||||
|
||||
/** Business logic run when panel visibility is set. */
|
||||
@CallSuper
|
||||
protected void onPanelVisible(boolean visible) {
|
||||
if (DEBUG) {
|
||||
Log.e(TAG, "onPanelVisible: " + visible);
|
||||
}
|
||||
|
||||
if (visible && !getOverlayViewGlobalStateController().isWindowVisible()) {
|
||||
getOverlayViewGlobalStateController().setWindowVisible(true);
|
||||
}
|
||||
if (!visible && getOverlayViewGlobalStateController().isWindowVisible()) {
|
||||
getOverlayViewGlobalStateController().setWindowVisible(false);
|
||||
}
|
||||
getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
|
||||
getOverlayViewGlobalStateController().setWindowFocusable(visible);
|
||||
}
|
||||
|
||||
/* ***************************************************************************************** *
|
||||
* Panel Expansion
|
||||
* ***************************************************************************************** */
|
||||
|
||||
/**
|
||||
* Set the panel state to expanded. This will expand or collapse the overlay window if
|
||||
* necessary.
|
||||
*/
|
||||
protected final void setPanelExpanded(boolean expand) {
|
||||
mPanelExpanded = expand;
|
||||
onPanelExpanded(expand);
|
||||
}
|
||||
|
||||
/** Returns {@code true} if panel is expanded. */
|
||||
public final boolean isPanelExpanded() {
|
||||
return mPanelExpanded;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
protected void onPanelExpanded(boolean expand) {
|
||||
if (DEBUG) {
|
||||
Log.e(TAG, "onPanelExpanded: " + expand);
|
||||
}
|
||||
}
|
||||
|
||||
/* ***************************************************************************************** *
|
||||
* Misc
|
||||
* ***************************************************************************************** */
|
||||
|
||||
protected void calculatePercentageFromBottom(float height) {
|
||||
if (getLayout().getHeight() > 0) {
|
||||
mPercentageFromBottom = (int) Math.abs(
|
||||
height / getLayout().getHeight() * 100);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setViewClipBounds(int height) {
|
||||
if (height > getLayout().getHeight()) {
|
||||
height = getLayout().getHeight();
|
||||
}
|
||||
Rect clipBounds = new Rect();
|
||||
clipBounds.set(0, 0, getLayout().getWidth(), height);
|
||||
getLayout().setClipBounds(clipBounds);
|
||||
onScroll(height);
|
||||
}
|
||||
|
||||
/** Called while scrolling. */
|
||||
protected abstract void onScroll(int height);
|
||||
|
||||
/* ***************************************************************************************** *
|
||||
* Getters
|
||||
* ***************************************************************************************** */
|
||||
|
||||
/** Returns the open touch listener. */
|
||||
public final View.OnTouchListener getDragOpenTouchListener() {
|
||||
return mDragOpenTouchListener;
|
||||
}
|
||||
|
||||
/** Returns the close touch listener. */
|
||||
public final View.OnTouchListener getDragCloseTouchListener() {
|
||||
return mDragCloseTouchListener;
|
||||
}
|
||||
|
||||
/** Gets the fling animation utils used for animating this panel. */
|
||||
protected final FlingAnimationUtils getFlingAnimationUtils() {
|
||||
return mFlingAnimationUtils;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if the panel is currently tracking. */
|
||||
protected final boolean isTracking() {
|
||||
return mIsTracking;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if the panel is currently animating. */
|
||||
protected final boolean isAnimating() {
|
||||
return mIsAnimating;
|
||||
}
|
||||
|
||||
/** Returns the percentage of the panel that is open from the bottom. */
|
||||
protected final int getPercentageFromBottom() {
|
||||
return mPercentageFromBottom;
|
||||
}
|
||||
|
||||
/** Returns the percentage at which we've determined whether to open or close the panel. */
|
||||
protected final int getSettleClosePercentage() {
|
||||
return mSettleClosePercentage;
|
||||
}
|
||||
|
||||
/* ***************************************************************************************** *
|
||||
* Gesture Listeners
|
||||
* ***************************************************************************************** */
|
||||
|
||||
/** Called when the user is beginning to scroll down the panel. */
|
||||
protected abstract void onOpenScrollStart();
|
||||
|
||||
/**
|
||||
* Only responsible for open hooks. Since once the panel opens it covers all elements
|
||||
* there is no need to merge with close.
|
||||
*/
|
||||
protected abstract class OpenGestureListener extends
|
||||
GestureDetector.SimpleOnGestureListener {
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
|
||||
float distanceY) {
|
||||
|
||||
if (!isPanelVisible()) {
|
||||
onOpenScrollStart();
|
||||
}
|
||||
setPanelVisible(true);
|
||||
|
||||
// clips the view for the notification shade when the user scrolls to open.
|
||||
setViewClipBounds((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 (velocityY > SWIPE_THRESHOLD_VELOCITY) {
|
||||
mOpeningVelocity = velocityY;
|
||||
open();
|
||||
return true;
|
||||
}
|
||||
animatePanel(DEFAULT_FLING_VELOCITY, true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract void open();
|
||||
}
|
||||
|
||||
/** Determines whether the scroll event should allow closing of the panel. */
|
||||
protected abstract boolean shouldAllowClosingScroll();
|
||||
|
||||
protected abstract class CloseGestureListener extends
|
||||
GestureDetector.SimpleOnGestureListener {
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent motionEvent) {
|
||||
if (isPanelExpanded()) {
|
||||
animatePanel(DEFAULT_FLING_VELOCITY, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
|
||||
float distanceY) {
|
||||
// should not clip while scroll to the bottom of the list.
|
||||
if (!shouldAllowClosingScroll()) {
|
||||
return false;
|
||||
}
|
||||
float actualNotificationHeight =
|
||||
getLayout().getHeight() - (event1.getRawY() - event2.getRawY());
|
||||
if (actualNotificationHeight > getLayout().getHeight()) {
|
||||
actualNotificationHeight = getLayout().getHeight();
|
||||
}
|
||||
if (getLayout().getHeight() > 0) {
|
||||
mPercentageFromBottom = (int) Math.abs(
|
||||
actualNotificationHeight / getLayout().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(getLayout().getHeight() - actualNotificationHeight)
|
||||
> SWIPE_DOWN_MIN_DISTANCE && isUp) {
|
||||
setViewClipBounds((int) actualNotificationHeight);
|
||||
mIsTracking = true;
|
||||
} else if (!isUp) {
|
||||
setViewClipBounds((int) actualNotificationHeight);
|
||||
}
|
||||
}
|
||||
// if we return true the items in RV won't be scrollable.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent event1, MotionEvent event2,
|
||||
float velocityX, float velocityY) {
|
||||
// should not fling if the touch does not start when view is at the bottom of the list.
|
||||
if (!shouldAllowClosingScroll()) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
if (isUp) {
|
||||
close();
|
||||
return true;
|
||||
} else {
|
||||
// we should close the shade
|
||||
animatePanel(velocityY, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract void close();
|
||||
}
|
||||
|
||||
protected abstract class SystemBarCloseGestureListener extends CloseGestureListener {
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
mClosingVelocity = DEFAULT_FLING_VELOCITY;
|
||||
if (isPanelExpanded()) {
|
||||
close();
|
||||
}
|
||||
return super.onSingleTapUp(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
|
||||
float distanceY) {
|
||||
calculatePercentageFromBottom(event2.getRawY());
|
||||
setViewClipBounds((int) event2.getRawY());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,13 +20,14 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.testing.TestableResources;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.car.CarDeviceProvisionedController;
|
||||
|
||||
@@ -0,0 +1,431 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.window;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyFloat;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.car.CarDeviceProvisionedController;
|
||||
import com.android.systemui.statusbar.FlingAnimationUtils;
|
||||
import com.android.systemui.tests.R;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
@SmallTest
|
||||
public class OverlayPanelViewControllerTest extends SysuiTestCase {
|
||||
private TestOverlayPanelViewController mOverlayPanelViewController;
|
||||
private ViewGroup mBaseLayout;
|
||||
|
||||
@Mock
|
||||
private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
|
||||
@Mock
|
||||
private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
|
||||
@Mock
|
||||
private FlingAnimationUtils mFlingAnimationUtils;
|
||||
@Mock
|
||||
private CarDeviceProvisionedController mCarDeviceProvisionedController;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate(
|
||||
R.layout.overlay_view_controller_test, /* root= */ null);
|
||||
|
||||
when(mFlingAnimationUtilsBuilder.setMaxLengthSeconds(anyFloat())).thenReturn(
|
||||
mFlingAnimationUtilsBuilder);
|
||||
when(mFlingAnimationUtilsBuilder.setSpeedUpFactor(anyFloat())).thenReturn(
|
||||
mFlingAnimationUtilsBuilder);
|
||||
when(mFlingAnimationUtilsBuilder.build()).thenReturn(mFlingAnimationUtils);
|
||||
mOverlayPanelViewController = new TestOverlayPanelViewController(
|
||||
getContext(),
|
||||
getContext().getOrCreateTestableResources().getResources(),
|
||||
R.id.overlay_view_controller_stub,
|
||||
mOverlayViewGlobalStateController,
|
||||
mFlingAnimationUtilsBuilder,
|
||||
mCarDeviceProvisionedController);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toggle_notInflated_inflates() {
|
||||
assertThat(mOverlayPanelViewController.isInflated()).isFalse();
|
||||
|
||||
mOverlayPanelViewController.toggle();
|
||||
|
||||
verify(mOverlayViewGlobalStateController).inflateView(mOverlayPanelViewController);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toggle_inflated_doesNotInflate() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
assertThat(mOverlayPanelViewController.isInflated()).isTrue();
|
||||
|
||||
mOverlayPanelViewController.toggle();
|
||||
|
||||
verify(mOverlayViewGlobalStateController, never()).inflateView(mOverlayPanelViewController);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toggle_notExpanded_panelExpands() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setPanelExpanded(false);
|
||||
|
||||
mOverlayPanelViewController.toggle();
|
||||
|
||||
assertThat(mOverlayPanelViewController.mAnimateExpandPanelCalled).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toggle_expanded_panelCollapses() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setPanelExpanded(true);
|
||||
|
||||
mOverlayPanelViewController.toggle();
|
||||
|
||||
assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateCollapsePanel_shouldNotAnimateCollapsePanel_doesNotCollapse() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setShouldAnimateCollapsePanel(false);
|
||||
|
||||
mOverlayPanelViewController.animateCollapsePanel();
|
||||
|
||||
assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue();
|
||||
assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateCollapsePanel_isNotExpanded_doesNotCollapse() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
|
||||
mOverlayPanelViewController.setPanelExpanded(false);
|
||||
|
||||
mOverlayPanelViewController.animateCollapsePanel();
|
||||
|
||||
assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue();
|
||||
assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateCollapsePanel_isNotVisible_doesNotCollapse() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
|
||||
mOverlayPanelViewController.setPanelExpanded(true);
|
||||
mOverlayPanelViewController.setPanelVisible(false);
|
||||
|
||||
mOverlayPanelViewController.animateCollapsePanel();
|
||||
|
||||
assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue();
|
||||
assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateCollapsePanel_collapses() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
|
||||
mOverlayPanelViewController.setPanelExpanded(true);
|
||||
mOverlayPanelViewController.setPanelVisible(true);
|
||||
|
||||
mOverlayPanelViewController.animateCollapsePanel();
|
||||
|
||||
assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateCollapsePanel_removesWindowFocus() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
|
||||
mOverlayPanelViewController.setPanelExpanded(true);
|
||||
mOverlayPanelViewController.setPanelVisible(true);
|
||||
|
||||
mOverlayPanelViewController.animateCollapsePanel();
|
||||
|
||||
verify(mOverlayViewGlobalStateController).setWindowFocusable(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateExpandPanel_shouldNotAnimateExpandPanel_doesNotExpand() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setShouldAnimateExpandPanel(false);
|
||||
|
||||
mOverlayPanelViewController.animateExpandPanel();
|
||||
|
||||
assertThat(mOverlayPanelViewController.mAnimateExpandPanelCalled).isTrue();
|
||||
assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateExpandPanel_userNotSetup_doesNotExpand() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
|
||||
when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(false);
|
||||
|
||||
mOverlayPanelViewController.animateExpandPanel();
|
||||
|
||||
assertThat(mOverlayPanelViewController.mAnimateExpandPanelCalled).isTrue();
|
||||
assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateExpandPanel_expands() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
|
||||
when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
|
||||
|
||||
mOverlayPanelViewController.animateExpandPanel();
|
||||
|
||||
assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateExpandPanel_setsPanelVisible() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
|
||||
when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
|
||||
|
||||
mOverlayPanelViewController.animateExpandPanel();
|
||||
|
||||
assertThat(mOverlayPanelViewController.isPanelVisible()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateExpandPanel_setsPanelExpanded() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
|
||||
when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
|
||||
|
||||
mOverlayPanelViewController.animateExpandPanel();
|
||||
|
||||
assertThat(mOverlayPanelViewController.isPanelExpanded()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPanelVisible_setTrue_windowNotVisible_setsWindowVisible() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(false);
|
||||
|
||||
mOverlayPanelViewController.setPanelVisible(true);
|
||||
|
||||
verify(mOverlayViewGlobalStateController).setWindowVisible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPanelVisible_setTrue_windowVisible_doesNotSetWindowVisible() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(true);
|
||||
|
||||
mOverlayPanelViewController.setPanelVisible(true);
|
||||
|
||||
verify(mOverlayViewGlobalStateController, never()).setWindowVisible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPanelVisible_setTrue_setLayoutVisible() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.getLayout().setVisibility(View.INVISIBLE);
|
||||
|
||||
mOverlayPanelViewController.setPanelVisible(true);
|
||||
|
||||
assertThat(mOverlayPanelViewController.getLayout().getVisibility()).isEqualTo(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPanelVisible_setTrue_setWindowFocusable() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.setPanelVisible(true);
|
||||
|
||||
verify(mOverlayViewGlobalStateController).setWindowFocusable(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPanelVisible_setFalse_windowVisible_setsWindowNotVisible() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(true);
|
||||
|
||||
mOverlayPanelViewController.setPanelVisible(false);
|
||||
|
||||
verify(mOverlayViewGlobalStateController).setWindowVisible(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPanelVisible_setFalse_windowNotVisible_doesNotSetWindowNotVisible() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(false);
|
||||
|
||||
mOverlayPanelViewController.setPanelVisible(false);
|
||||
|
||||
verify(mOverlayViewGlobalStateController, never()).setWindowVisible(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPanelVisible_setFalse_setLayoutInvisible() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
mOverlayPanelViewController.getLayout().setVisibility(View.VISIBLE);
|
||||
|
||||
mOverlayPanelViewController.setPanelVisible(false);
|
||||
|
||||
assertThat(mOverlayPanelViewController.getLayout().getVisibility()).isEqualTo(
|
||||
View.INVISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPanelVisible_setFalse_setWindowNotFocusable() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
|
||||
mOverlayPanelViewController.setPanelVisible(false);
|
||||
|
||||
verify(mOverlayViewGlobalStateController).setWindowFocusable(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dragOpenTouchListener_isNotInflated_inflatesView() {
|
||||
when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
|
||||
assertThat(mOverlayPanelViewController.isInflated()).isFalse();
|
||||
|
||||
mOverlayPanelViewController.getDragOpenTouchListener().onTouch(/* v= */ null,
|
||||
MotionEvent.obtain(/* downTime= */ 200, /* eventTime= */ 300,
|
||||
MotionEvent.ACTION_MOVE, /* x= */ 0, /* y= */ 0, /* metaState= */ 0));
|
||||
|
||||
verify(mOverlayViewGlobalStateController).inflateView(mOverlayPanelViewController);
|
||||
}
|
||||
|
||||
private static class TestOverlayPanelViewController extends OverlayPanelViewController {
|
||||
|
||||
private boolean mShouldAnimateCollapsePanel;
|
||||
private boolean mShouldAnimateExpandPanel;
|
||||
private boolean mShouldAllowClosingScroll;
|
||||
|
||||
boolean mOnAnimateCollapsePanelCalled;
|
||||
boolean mAnimateCollapsePanelCalled;
|
||||
boolean mOnAnimateExpandPanelCalled;
|
||||
boolean mAnimateExpandPanelCalled;
|
||||
boolean mOnCollapseAnimationEndCalled;
|
||||
boolean mOnExpandAnimationEndCalled;
|
||||
boolean mOnOpenScrollStartEnd;
|
||||
List<Integer> mOnScrollHeights;
|
||||
|
||||
TestOverlayPanelViewController(
|
||||
Context context,
|
||||
Resources resources,
|
||||
int stubId,
|
||||
OverlayViewGlobalStateController overlayViewGlobalStateController,
|
||||
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
|
||||
CarDeviceProvisionedController carDeviceProvisionedController) {
|
||||
super(context, resources, stubId, overlayViewGlobalStateController,
|
||||
flingAnimationUtilsBuilder,
|
||||
carDeviceProvisionedController);
|
||||
|
||||
mOnScrollHeights = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void setShouldAnimateCollapsePanel(boolean shouldAnimate) {
|
||||
mShouldAnimateCollapsePanel = shouldAnimate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldAnimateCollapsePanel() {
|
||||
return mShouldAnimateCollapsePanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void animateCollapsePanel() {
|
||||
super.animateCollapsePanel();
|
||||
mAnimateCollapsePanelCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimateCollapsePanel() {
|
||||
mOnAnimateCollapsePanelCalled = true;
|
||||
}
|
||||
|
||||
public void setShouldAnimateExpandPanel(boolean shouldAnimate) {
|
||||
mShouldAnimateExpandPanel = shouldAnimate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldAnimateExpandPanel() {
|
||||
return mShouldAnimateExpandPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void animateExpandPanel() {
|
||||
super.animateExpandPanel();
|
||||
mAnimateExpandPanelCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimateExpandPanel() {
|
||||
mOnAnimateExpandPanelCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCollapseAnimationEnd() {
|
||||
mOnCollapseAnimationEndCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onExpandAnimationEnd() {
|
||||
mOnExpandAnimationEndCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScroll(int height) {
|
||||
mOnScrollHeights.add(height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOpenScrollStart() {
|
||||
mOnOpenScrollStartEnd = true;
|
||||
}
|
||||
|
||||
public void setShouldAllowClosingScroll(boolean shouldAllow) {
|
||||
mShouldAllowClosingScroll = shouldAllow;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldAllowClosingScroll() {
|
||||
return mShouldAllowClosingScroll;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ import org.mockito.MockitoAnnotations;
|
||||
@TestableLooper.RunWithLooper
|
||||
@SmallTest
|
||||
public class OverlayViewControllerTest extends SysuiTestCase {
|
||||
private MockOverlayViewController mOverlayViewController;
|
||||
private TestOverlayViewController mOverlayViewController;
|
||||
private ViewGroup mBaseLayout;
|
||||
|
||||
@Mock
|
||||
@@ -56,7 +56,7 @@ public class OverlayViewControllerTest extends SysuiTestCase {
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(/* testClass= */ this);
|
||||
|
||||
mOverlayViewController = new MockOverlayViewController(R.id.overlay_view_controller_stub,
|
||||
mOverlayViewController = new TestOverlayViewController(R.id.overlay_view_controller_stub,
|
||||
mOverlayViewGlobalStateController);
|
||||
|
||||
mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate(
|
||||
@@ -130,12 +130,12 @@ public class OverlayViewControllerTest extends SysuiTestCase {
|
||||
assertThat(mOverlayViewController.mHideInternalCalled).isFalse();
|
||||
}
|
||||
|
||||
private static class MockOverlayViewController extends OverlayViewController {
|
||||
private static class TestOverlayViewController extends OverlayViewController {
|
||||
boolean mOnFinishInflateCalled = false;
|
||||
boolean mShowInternalCalled = false;
|
||||
boolean mHideInternalCalled = false;
|
||||
|
||||
MockOverlayViewController(int stubId,
|
||||
TestOverlayViewController(int stubId,
|
||||
OverlayViewGlobalStateController overlayViewGlobalStateController) {
|
||||
super(stubId, overlayViewGlobalStateController);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user