Merge "Generalize notifications logic into a subclass of OverlayViewController" into rvc-dev am: d307dceba5
Change-Id: Ie77b352fa603b86df2f898accd5f4f810ed8bbc5
This commit is contained in:
@@ -20,8 +20,6 @@
|
|||||||
android:id="@+id/notification_view"
|
android:id="@+id/notification_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:visibility="invisible"
|
|
||||||
android:layout_marginBottom="@dimen/navigation_bar_height"
|
|
||||||
android:background="@color/notification_shade_background_color">
|
android:background="@color/notification_shade_background_color">
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -18,4 +18,5 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/notification_container"
|
android:id="@+id/notification_container"
|
||||||
android:layout_width="match_parent"
|
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_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<!-- TODO(b/151617493): replace marginBottom with insets. -->
|
||||||
<ViewStub android:id="@+id/notification_panel_stub"
|
<ViewStub android:id="@+id/notification_panel_stub"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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"
|
<ViewStub android:id="@+id/fullscreen_user_switcher_stub"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
|
|||||||
*/
|
*/
|
||||||
public interface CarDeviceProvisionedController extends 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);
|
boolean isUserSetupInProgress(int user);
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,6 @@
|
|||||||
|
|
||||||
package com.android.systemui.car.notification;
|
package com.android.systemui.car.notification;
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.car.Car;
|
import android.car.Car;
|
||||||
import android.car.drivingstate.CarUxRestrictionsManager;
|
import android.car.drivingstate.CarUxRestrictionsManager;
|
||||||
@@ -33,7 +30,6 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
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.CommandQueue;
|
||||||
import com.android.systemui.statusbar.FlingAnimationUtils;
|
import com.android.systemui.statusbar.FlingAnimationUtils;
|
||||||
import com.android.systemui.statusbar.StatusBarState;
|
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 com.android.systemui.window.OverlayViewGlobalStateController;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -62,39 +58,22 @@ import javax.inject.Singleton;
|
|||||||
|
|
||||||
/** View controller for the notification panel. */
|
/** View controller for the notification panel. */
|
||||||
@Singleton
|
@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 boolean DEBUG = true;
|
||||||
private static final String TAG = "NotificationPanelViewController";
|
private static final String TAG = "NotificationPanelViewController";
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final Resources mResources;
|
private final Resources mResources;
|
||||||
private final CarServiceProvider mCarServiceProvider;
|
private final CarServiceProvider mCarServiceProvider;
|
||||||
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
|
|
||||||
private final IStatusBarService mBarService;
|
private final IStatusBarService mBarService;
|
||||||
private final CommandQueue mCommandQueue;
|
private final CommandQueue mCommandQueue;
|
||||||
private final NotificationDataManager mNotificationDataManager;
|
private final NotificationDataManager mNotificationDataManager;
|
||||||
private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
|
private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
|
||||||
private final CarNotificationListener mCarNotificationListener;
|
private final CarNotificationListener mCarNotificationListener;
|
||||||
private final NotificationClickHandlerFactory mNotificationClickHandlerFactory;
|
private final NotificationClickHandlerFactory mNotificationClickHandlerFactory;
|
||||||
private final FlingAnimationUtils mFlingAnimationUtils;
|
|
||||||
private final StatusBarStateController mStatusBarStateController;
|
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 mInitialBackgroundAlpha;
|
||||||
private float mBackgroundAlphaDiff;
|
private float mBackgroundAlphaDiff;
|
||||||
|
|
||||||
@@ -108,13 +87,7 @@ public class NotificationPanelViewController extends OverlayViewController {
|
|||||||
private float mFirstTouchDownOnGlassPane;
|
private float mFirstTouchDownOnGlassPane;
|
||||||
private boolean mNotificationListAtBottomAtTimeOfTouch;
|
private boolean mNotificationListAtBottomAtTimeOfTouch;
|
||||||
private boolean mIsSwipingVerticallyToClose;
|
private boolean mIsSwipingVerticallyToClose;
|
||||||
private int mPercentageFromBottom;
|
|
||||||
private boolean mIsNotificationAnimating;
|
|
||||||
private boolean mIsNotificationCardSwiping;
|
private boolean mIsNotificationCardSwiping;
|
||||||
private boolean mPanelExpanded = false;
|
|
||||||
|
|
||||||
private View.OnTouchListener mTopNavBarNotificationTouchListener;
|
|
||||||
private View.OnTouchListener mNavBarNotificationTouchListener;
|
|
||||||
|
|
||||||
private OnUnseenCountUpdateListener mUnseenCountUpdateListener;
|
private OnUnseenCountUpdateListener mUnseenCountUpdateListener;
|
||||||
|
|
||||||
@@ -123,6 +96,7 @@ public class NotificationPanelViewController extends OverlayViewController {
|
|||||||
Context context,
|
Context context,
|
||||||
@Main Resources resources,
|
@Main Resources resources,
|
||||||
OverlayViewGlobalStateController overlayViewGlobalStateController,
|
OverlayViewGlobalStateController overlayViewGlobalStateController,
|
||||||
|
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
|
||||||
|
|
||||||
/* Other things */
|
/* Other things */
|
||||||
CarServiceProvider carServiceProvider,
|
CarServiceProvider carServiceProvider,
|
||||||
@@ -135,26 +109,21 @@ public class NotificationPanelViewController extends OverlayViewController {
|
|||||||
CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
|
CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
|
||||||
CarNotificationListener carNotificationListener,
|
CarNotificationListener carNotificationListener,
|
||||||
NotificationClickHandlerFactory notificationClickHandlerFactory,
|
NotificationClickHandlerFactory notificationClickHandlerFactory,
|
||||||
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
|
|
||||||
|
|
||||||
/* Things that need to be replaced */
|
/* Things that need to be replaced */
|
||||||
StatusBarStateController statusBarStateController
|
StatusBarStateController statusBarStateController
|
||||||
) {
|
) {
|
||||||
super(R.id.notification_panel_stub, overlayViewGlobalStateController);
|
super(context, resources, R.id.notification_panel_stub, overlayViewGlobalStateController,
|
||||||
|
flingAnimationUtilsBuilder, carDeviceProvisionedController);
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mResources = resources;
|
mResources = resources;
|
||||||
mCarServiceProvider = carServiceProvider;
|
mCarServiceProvider = carServiceProvider;
|
||||||
mCarDeviceProvisionedController = carDeviceProvisionedController;
|
|
||||||
mBarService = barService;
|
mBarService = barService;
|
||||||
mCommandQueue = commandQueue;
|
mCommandQueue = commandQueue;
|
||||||
mNotificationDataManager = notificationDataManager;
|
mNotificationDataManager = notificationDataManager;
|
||||||
mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper;
|
mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper;
|
||||||
mCarNotificationListener = carNotificationListener;
|
mCarNotificationListener = carNotificationListener;
|
||||||
mNotificationClickHandlerFactory = notificationClickHandlerFactory;
|
mNotificationClickHandlerFactory = notificationClickHandlerFactory;
|
||||||
mFlingAnimationUtils = flingAnimationUtilsBuilder
|
|
||||||
.setMaxLengthSeconds(FLING_ANIMATION_MAX_TIME)
|
|
||||||
.setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
|
|
||||||
.build();
|
|
||||||
mStatusBarStateController = statusBarStateController;
|
mStatusBarStateController = statusBarStateController;
|
||||||
|
|
||||||
// Notification background setup.
|
// Notification background setup.
|
||||||
@@ -175,60 +144,6 @@ public class NotificationPanelViewController extends OverlayViewController {
|
|||||||
+ " percentage");
|
+ " percentage");
|
||||||
}
|
}
|
||||||
mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha;
|
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
|
@Override
|
||||||
@@ -252,14 +167,13 @@ public class NotificationPanelViewController extends OverlayViewController {
|
|||||||
private void onNotificationViewInflated() {
|
private void onNotificationViewInflated() {
|
||||||
// Find views.
|
// Find views.
|
||||||
mNotificationView = getLayout().findViewById(R.id.notification_view);
|
mNotificationView = getLayout().findViewById(R.id.notification_view);
|
||||||
View glassPane = mNotificationView.findViewById(R.id.glass_pane);
|
setupHandleBar();
|
||||||
mHandleBar = mNotificationView.findViewById(R.id.handle_bar);
|
setupNotificationPanel();
|
||||||
mNotificationList = mNotificationView.findViewById(R.id.notifications);
|
|
||||||
|
|
||||||
mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> {
|
mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> {
|
||||||
if (launchResult == ActivityManager.START_TASK_TO_FRONT
|
if (launchResult == ActivityManager.START_TASK_TO_FRONT
|
||||||
|| launchResult == ActivityManager.START_SUCCESS) {
|
|| launchResult == ActivityManager.START_SUCCESS) {
|
||||||
animateCollapsePanels();
|
animateCollapsePanel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -269,39 +183,52 @@ public class NotificationPanelViewController extends OverlayViewController {
|
|||||||
mNotificationDataManager.getUnseenNotificationCount());
|
mNotificationDataManager.getUnseenNotificationCount());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
|
mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
|
||||||
mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory);
|
mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory);
|
||||||
mNotificationView.setNotificationDataManager(mNotificationDataManager);
|
mNotificationView.setNotificationDataManager(mNotificationDataManager);
|
||||||
|
|
||||||
mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
mCarServiceProvider.addListener(car -> {
|
||||||
@Override
|
CarUxRestrictionsManager carUxRestrictionsManager =
|
||||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
(CarUxRestrictionsManager)
|
||||||
super.onScrolled(recyclerView, dx, dy);
|
car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
|
||||||
if (!mNotificationList.canScrollVertically(1)) {
|
mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
|
||||||
mNotificationListAtBottom = true;
|
carUxRestrictionsManager);
|
||||||
return;
|
|
||||||
}
|
|
||||||
mNotificationListAtBottom = false;
|
|
||||||
mIsSwipingVerticallyToClose = false;
|
|
||||||
mNotificationListAtBottomAtTimeOfTouch = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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,
|
GestureDetector closeGestureDetector = new GestureDetector(mContext,
|
||||||
new CloseNotificationGestureListener() {
|
new CloseGestureListener() {
|
||||||
@Override
|
@Override
|
||||||
protected void close() {
|
protected void close() {
|
||||||
if (mPanelExpanded) {
|
if (isPanelExpanded()) {
|
||||||
animateCollapsePanels();
|
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.
|
// 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
|
// This allows us to initialize gesture listeners and detect when to close the notifications
|
||||||
glassPane.setOnTouchListener((v, event) -> {
|
glassPane.setOnTouchListener((v, event) -> {
|
||||||
@@ -320,6 +247,21 @@ public class NotificationPanelViewController extends OverlayViewController {
|
|||||||
return false;
|
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) -> {
|
mNotificationList.setOnTouchListener((v, event) -> {
|
||||||
mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
|
mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
|
||||||
> SWIPE_MAX_OFF_PATH;
|
> SWIPE_MAX_OFF_PATH;
|
||||||
@@ -341,19 +283,19 @@ public class NotificationPanelViewController extends OverlayViewController {
|
|||||||
|
|
||||||
boolean handled = closeGestureDetector.onTouchEvent(event);
|
boolean handled = closeGestureDetector.onTouchEvent(event);
|
||||||
boolean isTracking = mIsTracking;
|
boolean isTracking = mIsTracking;
|
||||||
Rect rect = mNotificationView.getClipBounds();
|
Rect rect = getLayout().getClipBounds();
|
||||||
float clippedHeight = 0;
|
float clippedHeight = 0;
|
||||||
if (rect != null) {
|
if (rect != null) {
|
||||||
clippedHeight = rect.bottom;
|
clippedHeight = rect.bottom;
|
||||||
}
|
}
|
||||||
if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
|
if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
|
||||||
&& mIsSwipingVerticallyToClose) {
|
&& mIsSwipingVerticallyToClose) {
|
||||||
if (mSettleClosePercentage < mPercentageFromBottom && isTracking) {
|
if (getSettleClosePercentage() < getPercentageFromBottom() && isTracking) {
|
||||||
animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
|
animatePanel(DEFAULT_FLING_VELOCITY, false);
|
||||||
} else if (clippedHeight != mNotificationView.getHeight() && isTracking) {
|
} else if (clippedHeight != getLayout().getHeight() && isTracking) {
|
||||||
// this can be caused when user is at the end of the list and trying to
|
// 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.
|
// 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;
|
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. */
|
/** Called when the car power state is changed to ON. */
|
||||||
@@ -397,139 +317,40 @@ public class NotificationPanelViewController extends OverlayViewController {
|
|||||||
mNotificationDataManager.clearAll();
|
mNotificationDataManager.clearAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
View.OnTouchListener getTopNavBarNotificationTouchListener() {
|
|
||||||
return mTopNavBarNotificationTouchListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
View.OnTouchListener getNavBarNotificationTouchListener() {
|
|
||||||
return mNavBarNotificationTouchListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
@Override
|
||||||
public void onGlobalLayout() {
|
protected boolean shouldAnimateCollapsePanel() {
|
||||||
ViewTreeObserver obs = mNotificationView.getViewTreeObserver();
|
return true;
|
||||||
obs.removeOnGlobalLayoutListener(this);
|
|
||||||
float to = mNotificationView.getHeight();
|
|
||||||
animate(/* from= */ 0, to, velocity, isClosing);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void animateCollapsePanels() {
|
@Override
|
||||||
if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) {
|
protected void onAnimateCollapsePanel() {
|
||||||
return;
|
// No op.
|
||||||
}
|
|
||||||
getOverlayViewGlobalStateController().setWindowFocusable(false);
|
|
||||||
animateNotificationPanel(mClosingVelocity, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void animateExpandNotificationsPanel() {
|
@Override
|
||||||
if (!mCommandQueue.panelsEnabled()
|
protected boolean shouldAnimateExpandPanel() {
|
||||||
|| !mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
|
return mCommandQueue.panelsEnabled();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// scroll to top
|
|
||||||
|
@Override
|
||||||
|
protected void onAnimateExpandPanel() {
|
||||||
mNotificationList.scrollToPosition(0);
|
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
|
@Override
|
||||||
public void onAnimationEnd(Animator animation) {
|
protected void onCollapseAnimationEnd() {
|
||||||
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);
|
mNotificationViewController.onVisibilityChanged(false);
|
||||||
// let the status bar know that the panel is closed
|
}
|
||||||
setPanelExpanded(false);
|
|
||||||
} else {
|
@Override
|
||||||
|
protected void onExpandAnimationEnd() {
|
||||||
mNotificationViewController.onVisibilityChanged(true);
|
mNotificationViewController.onVisibilityChanged(true);
|
||||||
// let the status bar know that the panel is open
|
|
||||||
mNotificationView.setVisibleNotificationsAsSeen();
|
mNotificationView.setVisibleNotificationsAsSeen();
|
||||||
setPanelExpanded(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity));
|
|
||||||
animator.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Set the panel view to be visible.
|
protected void onPanelExpanded(boolean expand) {
|
||||||
*/
|
super.onPanelExpanded(expand);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the panel state to expanded. This will expand or collapse the overlay window if
|
|
||||||
* necessary.
|
|
||||||
*/
|
|
||||||
public void setPanelExpanded(boolean expand) {
|
|
||||||
mPanelExpanded = expand;
|
|
||||||
|
|
||||||
if (expand && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
|
if (expand && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -550,19 +371,19 @@ public class NotificationPanelViewController extends OverlayViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setNotificationViewClipBounds(int height) {
|
@Override
|
||||||
if (height > mNotificationView.getHeight()) {
|
protected void onOpenScrollStart() {
|
||||||
height = mNotificationView.getHeight();
|
mNotificationList.scrollToPosition(0);
|
||||||
}
|
}
|
||||||
Rect clipBounds = new Rect();
|
|
||||||
clipBounds.set(0, 0, mNotificationView.getWidth(), height);
|
@Override
|
||||||
// Sets the clip region on the notification list view.
|
protected void onScroll(int height) {
|
||||||
mNotificationView.setClipBounds(clipBounds);
|
|
||||||
if (mHandleBar != null) {
|
if (mHandleBar != null) {
|
||||||
ViewGroup.MarginLayoutParams lp =
|
ViewGroup.MarginLayoutParams lp =
|
||||||
(ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams();
|
(ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams();
|
||||||
mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
|
mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mNotificationView.getHeight() > 0) {
|
if (mNotificationView.getHeight() > 0) {
|
||||||
Drawable background = mNotificationView.getBackground().mutate();
|
Drawable background = mNotificationView.getBackground().mutate();
|
||||||
background.setAlpha((int) (getBackgroundAlpha(height) * 255));
|
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
|
* 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
|
* 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);
|
+ ((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. */
|
/** Sets the unseen count listener. */
|
||||||
public void setOnUnseenCountUpdateListener(OnUnseenCountUpdateListener listener) {
|
public void setOnUnseenCountUpdateListener(OnUnseenCountUpdateListener listener) {
|
||||||
mUnseenCountUpdateListener = listener;
|
mUnseenCountUpdateListener = listener;
|
||||||
@@ -618,155 +422,10 @@ public class NotificationPanelViewController extends OverlayViewController {
|
|||||||
void onUnseenCountUpdate(int unseenNotificationCount);
|
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.
|
* To be installed on the handle bar.
|
||||||
*/
|
*/
|
||||||
private class HandleBarCloseNotificationGestureListener extends
|
private class HandleBarCloseGestureListener extends
|
||||||
GestureDetector.SimpleOnGestureListener {
|
GestureDetector.SimpleOnGestureListener {
|
||||||
|
|
||||||
@Override
|
@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.
|
// 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
|
// This will help the notification shade to clip smoothly as the event2 value changes
|
||||||
// as event1 value will be fixed.
|
// as event1 value will be fixed.
|
||||||
int clipHeight =
|
int clipHeight = getLayout().getHeight() - (int) (event1.getRawY() - event2.getRawY());
|
||||||
mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY());
|
setViewClipBounds(clipHeight);
|
||||||
setNotificationViewClipBounds(clipHeight);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,13 +59,13 @@ public class NotificationPanelViewMediator implements OverlayViewMediator,
|
|||||||
@Override
|
@Override
|
||||||
public void registerListeners() {
|
public void registerListeners() {
|
||||||
mCarNavigationBarController.registerTopBarTouchListener(
|
mCarNavigationBarController.registerTopBarTouchListener(
|
||||||
mNotificationPanelViewController.getTopNavBarNotificationTouchListener());
|
mNotificationPanelViewController.getDragOpenTouchListener());
|
||||||
mCarNavigationBarController.registerBottomBarTouchListener(
|
mCarNavigationBarController.registerBottomBarTouchListener(
|
||||||
mNotificationPanelViewController.getNavBarNotificationTouchListener());
|
mNotificationPanelViewController.getDragCloseTouchListener());
|
||||||
mCarNavigationBarController.registerLeftBarTouchListener(
|
mCarNavigationBarController.registerLeftBarTouchListener(
|
||||||
mNotificationPanelViewController.getNavBarNotificationTouchListener());
|
mNotificationPanelViewController.getDragCloseTouchListener());
|
||||||
mCarNavigationBarController.registerRightBarTouchListener(
|
mCarNavigationBarController.registerRightBarTouchListener(
|
||||||
mNotificationPanelViewController.getNavBarNotificationTouchListener());
|
mNotificationPanelViewController.getDragCloseTouchListener());
|
||||||
|
|
||||||
mCarNavigationBarController.registerNotificationController(
|
mCarNavigationBarController.registerNotificationController(
|
||||||
new CarNavigationBarController.NotificationsShadeController() {
|
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 static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
|
||||||
import android.testing.AndroidTestingRunner;
|
import android.testing.AndroidTestingRunner;
|
||||||
import android.testing.TestableLooper;
|
import android.testing.TestableLooper;
|
||||||
import android.testing.TestableResources;
|
import android.testing.TestableResources;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.SysuiTestCase;
|
import com.android.systemui.SysuiTestCase;
|
||||||
import com.android.systemui.car.CarDeviceProvisionedController;
|
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
|
@TestableLooper.RunWithLooper
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public class OverlayViewControllerTest extends SysuiTestCase {
|
public class OverlayViewControllerTest extends SysuiTestCase {
|
||||||
private MockOverlayViewController mOverlayViewController;
|
private TestOverlayViewController mOverlayViewController;
|
||||||
private ViewGroup mBaseLayout;
|
private ViewGroup mBaseLayout;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
@@ -56,7 +56,7 @@ public class OverlayViewControllerTest extends SysuiTestCase {
|
|||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(/* testClass= */ this);
|
MockitoAnnotations.initMocks(/* testClass= */ this);
|
||||||
|
|
||||||
mOverlayViewController = new MockOverlayViewController(R.id.overlay_view_controller_stub,
|
mOverlayViewController = new TestOverlayViewController(R.id.overlay_view_controller_stub,
|
||||||
mOverlayViewGlobalStateController);
|
mOverlayViewGlobalStateController);
|
||||||
|
|
||||||
mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate(
|
mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate(
|
||||||
@@ -130,12 +130,12 @@ public class OverlayViewControllerTest extends SysuiTestCase {
|
|||||||
assertThat(mOverlayViewController.mHideInternalCalled).isFalse();
|
assertThat(mOverlayViewController.mHideInternalCalled).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MockOverlayViewController extends OverlayViewController {
|
private static class TestOverlayViewController extends OverlayViewController {
|
||||||
boolean mOnFinishInflateCalled = false;
|
boolean mOnFinishInflateCalled = false;
|
||||||
boolean mShowInternalCalled = false;
|
boolean mShowInternalCalled = false;
|
||||||
boolean mHideInternalCalled = false;
|
boolean mHideInternalCalled = false;
|
||||||
|
|
||||||
MockOverlayViewController(int stubId,
|
TestOverlayViewController(int stubId,
|
||||||
OverlayViewGlobalStateController overlayViewGlobalStateController) {
|
OverlayViewGlobalStateController overlayViewGlobalStateController) {
|
||||||
super(stubId, overlayViewGlobalStateController);
|
super(stubId, overlayViewGlobalStateController);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user