Merge "Updated OverlayPanelViewController to allow for showing overlay panel from the bottom navigation bar." into rvc-dev
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index e826003..050db32 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -72,11 +72,21 @@
<!-- Car System UI's OverlayViewsMediator-->
<string-array name="config_carSystemUIOverlayViewsMediators" translatable="false">
- <item>com.android.systemui.car.notification.NotificationPanelViewMediator</item>
+ <item>@string/config_notificationPanelViewMediator</item>
<item>com.android.systemui.car.keyguard.CarKeyguardViewMediator</item>
<item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item>
</string-array>
+ <!--
+ Car SystemUI's notification mediator. Replace with other notification mediators to have
+ the notification panel show from another system bar. The system bar should be enabled to
+ use the mediator with that system bar.
+ Example: config_enableBottomNavigationBar=true
+ config_notificationPanelViewMediator=
+ com.android.systemui.car.notification.BottomNotificationPanelViewMediator -->
+ <string name="config_notificationPanelViewMediator" translatable="false">
+ com.android.systemui.car.notification.TopNotificationPanelViewMediator</string>
+
<!-- List of package names that are allowed sources of app installation. -->
<string-array name="config_allowedAppInstallSources" translatable="false">
<item>com.android.vending</item>
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java
new file mode 100644
index 0000000..6d140ca
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java
@@ -0,0 +1,59 @@
+/*
+ * 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.car.notification;
+
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.navigationbar.CarNavigationBarController;
+import com.android.systemui.car.window.OverlayPanelViewController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Implementation of NotificationPanelViewMediator that sets the notification panel to be opened
+ * from the top navigation bar.
+ */
+@Singleton
+public class BottomNotificationPanelViewMediator extends NotificationPanelViewMediator {
+
+ @Inject
+ public BottomNotificationPanelViewMediator(
+ CarNavigationBarController carNavigationBarController,
+ NotificationPanelViewController notificationPanelViewController,
+
+ PowerManagerHelper powerManagerHelper,
+
+ CarDeviceProvisionedController carDeviceProvisionedController,
+ ConfigurationController configurationController
+ ) {
+ super(carNavigationBarController,
+ notificationPanelViewController,
+ powerManagerHelper,
+ carDeviceProvisionedController,
+ configurationController);
+ notificationPanelViewController.setOverlayDirection(
+ OverlayPanelViewController.OVERLAY_FROM_BOTTOM_BAR);
+ }
+
+ @Override
+ public void registerListeners() {
+ super.registerListeners();
+ getCarNavigationBarController().registerBottomBarTouchListener(
+ getNotificationPanelViewController().getDragOpenTouchListener());
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index a17a0e9..cb9539a 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -83,9 +83,9 @@
private NotificationViewController mNotificationViewController;
private boolean mIsTracking;
- private boolean mNotificationListAtBottom;
+ private boolean mNotificationListAtEnd;
private float mFirstTouchDownOnGlassPane;
- private boolean mNotificationListAtBottomAtTimeOfTouch;
+ private boolean mNotificationListAtEndAtTimeOfTouch;
private boolean mIsSwipingVerticallyToClose;
private boolean mIsNotificationCardSwiping;
@@ -233,11 +233,11 @@
// This allows us to initialize gesture listeners and detect when to close the notifications
glassPane.setOnTouchListener((v, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- mNotificationListAtBottomAtTimeOfTouch = false;
+ mNotificationListAtEndAtTimeOfTouch = false;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mFirstTouchDownOnGlassPane = event.getRawX();
- mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom;
+ mNotificationListAtEndAtTimeOfTouch = mNotificationListAtEnd;
// Reset the tracker when there is a touch down on the glass pane.
mIsTracking = false;
// Pass the down event to gesture detector so that it knows where the touch event
@@ -251,34 +251,34 @@
@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;
+ // Check if we can scroll vertically in the animation direction.
+ if (!mNotificationList.canScrollVertically(mAnimateDirection)) {
+ mNotificationListAtEnd = true;
return;
}
- mNotificationListAtBottom = false;
+ mNotificationListAtEnd = false;
mIsSwipingVerticallyToClose = false;
- mNotificationListAtBottomAtTimeOfTouch = false;
+ mNotificationListAtEndAtTimeOfTouch = false;
}
});
mNotificationList.setOnTouchListener((v, event) -> {
mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
> SWIPE_MAX_OFF_PATH;
- if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) {
+ if (mNotificationListAtEndAtTimeOfTouch && mNotificationListAtEnd) {
// We need to save the state here as if notification card is swiping we will
- // change the mNotificationListAtBottomAtTimeOfTouch. This is to protect
+ // change the mNotificationListAtEndAtTimeOfTouch. This is to protect
// closing the notification shade while the notification card is being swiped.
mIsSwipingVerticallyToClose = true;
}
// If the card is swiping we should not allow the notification shade to close.
- // Hence setting mNotificationListAtBottomAtTimeOfTouch to false will stop that
+ // Hence setting mNotificationListAtEndAtTimeOfTouch to false will stop that
// for us. We are also checking for mIsTracking because while swiping the
// notification shade to close if the user goes a bit horizontal while swiping
// upwards then also this should close.
if (mIsNotificationCardSwiping && !mIsTracking) {
- mNotificationListAtBottomAtTimeOfTouch = false;
+ mNotificationListAtEndAtTimeOfTouch = false;
}
boolean handled = closeGestureDetector.onTouchEvent(event);
@@ -290,7 +290,7 @@
}
if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
&& mIsSwipingVerticallyToClose) {
- if (getSettleClosePercentage() < getPercentageFromBottom() && isTracking) {
+ if (getSettleClosePercentage() < getPercentageFromEndingEdge() && 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
@@ -299,11 +299,11 @@
}
}
- // Updating the mNotificationListAtBottomAtTimeOfTouch state has to be done after
+ // Updating the mNotificationListAtEndAtTimeOfTouch state has to be done after
// the event has been passed to the closeGestureDetector above, such that the
// closeGestureDetector sees the up event before the state has changed.
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- mNotificationListAtBottomAtTimeOfTouch = false;
+ mNotificationListAtEndAtTimeOfTouch = false;
}
return handled || isTracking;
});
@@ -377,25 +377,31 @@
}
@Override
- protected void onScroll(int height) {
+ protected void onScroll(int y) {
if (mHandleBar != null) {
ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams();
- mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
+ // Adjust handlebar to new pointer position, and a little more depending on the
+ // animate direction so the bar can be seen fully.
+ if (mAnimateDirection > 0) {
+ mHandleBar.setTranslationY(y - mHandleBar.getHeight() - lp.bottomMargin);
+ } else {
+ mHandleBar.setTranslationY(y + mHandleBar.getHeight() + lp.topMargin);
+ }
}
if (mNotificationView.getHeight() > 0) {
Drawable background = mNotificationView.getBackground().mutate();
- background.setAlpha((int) (getBackgroundAlpha(height) * 255));
+ background.setAlpha((int) (getBackgroundAlpha(y) * 255));
mNotificationView.setBackground(background);
}
}
@Override
protected boolean shouldAllowClosingScroll() {
- // Unless the notification list is at the bottom, the panel shouldn't be allowed to
+ // Unless the notification list is at the end, the panel shouldn't be allowed to
// collapse on scroll.
- return mNotificationListAtBottomAtTimeOfTouch;
+ return mNotificationListAtEndAtTimeOfTouch;
}
/**
@@ -403,9 +409,11 @@
* shade is visible to the user. When the notification shade is completely open then
* alpha value will be 1.
*/
- private float getBackgroundAlpha(int height) {
- return mInitialBackgroundAlpha
- + ((float) height / mNotificationView.getHeight() * mBackgroundAlphaDiff);
+ private float getBackgroundAlpha(int y) {
+ float fractionCovered =
+ ((float) (mAnimateDirection > 0 ? y : mNotificationView.getHeight() - y))
+ / mNotificationView.getHeight();
+ return mInitialBackgroundAlpha + fractionCovered * mBackgroundAlphaDiff;
}
/** Sets the unseen count listener. */
@@ -431,13 +439,18 @@
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
float distanceY) {
- calculatePercentageFromBottom(event2.getRawY());
- // To prevent the jump in the clip bounds while closing the notification shade using
+ calculatePercentageFromEndingEdge(event2.getRawY());
+ // To prevent the jump in the clip bounds while closing the notification panel using
// 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 = getLayout().getHeight() - (int) (event1.getRawY() - event2.getRawY());
- setViewClipBounds(clipHeight);
+ float diff = mAnimateDirection * (event1.getRawY() - event2.getRawY());
+ float y = mAnimateDirection > 0
+ ? getLayout().getHeight() - diff
+ : diff;
+ // Ensure the position is within the overlay panel.
+ y = Math.max(0, Math.min(y, getLayout().getHeight()));
+ setViewClipBounds((int) y);
return true;
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
index 24a84d9..8f52638 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
@@ -19,17 +19,15 @@
import android.car.hardware.power.CarPowerManager;
import android.content.res.Configuration;
+import androidx.annotation.CallSuper;
+
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.navigationbar.CarNavigationBarController;
import com.android.systemui.car.window.OverlayViewMediator;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
/** The view mediator which attaches the view controller to other elements of the system ui. */
-@Singleton
-public class NotificationPanelViewMediator implements OverlayViewMediator,
+public abstract class NotificationPanelViewMediator implements OverlayViewMediator,
ConfigurationController.ConfigurationListener {
private final CarNavigationBarController mCarNavigationBarController;
@@ -38,7 +36,6 @@
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
private final ConfigurationController mConfigurationController;
- @Inject
public NotificationPanelViewMediator(
CarNavigationBarController carNavigationBarController,
NotificationPanelViewController notificationPanelViewController,
@@ -56,9 +53,10 @@
}
@Override
+ @CallSuper
public void registerListeners() {
mCarNavigationBarController.registerTopBarTouchListener(
- mNotificationPanelViewController.getDragOpenTouchListener());
+ mNotificationPanelViewController.getDragCloseTouchListener());
mCarNavigationBarController.registerBottomBarTouchListener(
mNotificationPanelViewController.getDragCloseTouchListener());
mCarNavigationBarController.registerLeftBarTouchListener(
@@ -128,4 +126,12 @@
mNotificationPanelViewController.reinflate();
registerListeners();
}
+
+ protected final CarNavigationBarController getCarNavigationBarController() {
+ return mCarNavigationBarController;
+ }
+
+ protected final NotificationPanelViewController getNotificationPanelViewController() {
+ return mNotificationPanelViewController;
+ }
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java
new file mode 100644
index 0000000..09a4621
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java
@@ -0,0 +1,59 @@
+/*
+ * 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.car.notification;
+
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.navigationbar.CarNavigationBarController;
+import com.android.systemui.car.window.OverlayPanelViewController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Implementation of NotificationPanelViewMediator that sets the notification panel to be opened
+ * from the top navigation bar.
+ */
+@Singleton
+public class TopNotificationPanelViewMediator extends NotificationPanelViewMediator {
+
+ @Inject
+ public TopNotificationPanelViewMediator(
+ CarNavigationBarController carNavigationBarController,
+ NotificationPanelViewController notificationPanelViewController,
+
+ PowerManagerHelper powerManagerHelper,
+
+ CarDeviceProvisionedController carDeviceProvisionedController,
+ ConfigurationController configurationController
+ ) {
+ super(carNavigationBarController,
+ notificationPanelViewController,
+ powerManagerHelper,
+ carDeviceProvisionedController,
+ configurationController);
+ notificationPanelViewController.setOverlayDirection(
+ OverlayPanelViewController.OVERLAY_FROM_TOP_BAR);
+ }
+
+ @Override
+ public void registerListeners() {
+ super.registerListeners();
+ getCarNavigationBarController().registerBottomBarTouchListener(
+ getNotificationPanelViewController().getDragOpenTouchListener());
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
index 90892d5..0fe9856 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
@@ -19,6 +19,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -35,13 +36,36 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.FlingAnimationUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* The {@link OverlayPanelViewController} provides additional dragging animation capabilities to
* {@link OverlayViewController}.
*/
public abstract class OverlayPanelViewController extends OverlayViewController {
- private static final boolean DEBUG = true;
+ /** @hide */
+ @IntDef(flag = true, prefix = { "OVERLAY_" }, value = {
+ OVERLAY_FROM_TOP_BAR,
+ OVERLAY_FROM_BOTTOM_BAR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OverlayDirection {}
+
+ /**
+ * Indicates that the overlay panel should be opened from the top bar and expanded by dragging
+ * towards the bottom bar.
+ */
+ public static final int OVERLAY_FROM_TOP_BAR = 0;
+
+ /**
+ * Indicates that the overlay panel should be opened from the bottom bar and expanded by
+ * dragging towards the top bar.
+ */
+ public static final int OVERLAY_FROM_BOTTOM_BAR = 1;
+
+ private static final boolean DEBUG = false;
private static final String TAG = "OverlayPanelViewController";
// used to calculate how fast to open or close the window
@@ -54,14 +78,18 @@
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 static final int POSITIVE_DIRECTION = 1;
+ private static final int NEGATIVE_DIRECTION = -1;
private final FlingAnimationUtils mFlingAnimationUtils;
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
private final View.OnTouchListener mDragOpenTouchListener;
private final View.OnTouchListener mDragCloseTouchListener;
+ protected int mAnimateDirection = POSITIVE_DIRECTION;
+
private final int mSettleClosePercentage;
- private int mPercentageFromBottom;
+ private int mPercentageFromEndingEdge;
private boolean mPanelVisible;
private boolean mPanelExpanded;
@@ -91,8 +119,7 @@
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.
+ // Attached to a navigation bar to open the overlay panel
GestureDetector openGestureDetector = new GestureDetector(context,
new OpenGestureListener() {
@Override
@@ -101,8 +128,8 @@
}
});
- // Attached to the NavBars to close the notification shade
- GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(context,
+ // Attached to the other navigation bars to close the overlay panel
+ GestureDetector closeGestureDetector = new GestureDetector(context,
new SystemBarCloseGestureListener() {
@Override
protected void close() {
@@ -132,7 +159,7 @@
if (!isInflated()) {
return true;
}
- boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
+ boolean consumed = closeGestureDetector.onTouchEvent(event);
if (consumed) {
return true;
}
@@ -141,6 +168,17 @@
};
}
+ /** Sets the overlay panel animation direction along the x or y axis. */
+ public void setOverlayDirection(@OverlayDirection int direction) {
+ if (direction == OVERLAY_FROM_TOP_BAR) {
+ mAnimateDirection = POSITIVE_DIRECTION;
+ } else if (direction == OVERLAY_FROM_BOTTOM_BAR) {
+ mAnimateDirection = NEGATIVE_DIRECTION;
+ } else {
+ throw new IllegalArgumentException("Direction not supported");
+ }
+ }
+
/** Toggles the visibility of the panel. */
public void toggle() {
if (!isInflated()) {
@@ -207,7 +245,7 @@
protected void maybeCompleteAnimation(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_UP
&& isPanelVisible()) {
- if (mSettleClosePercentage < mPercentageFromBottom) {
+ if (mSettleClosePercentage < mPercentageFromEndingEdge) {
animatePanel(DEFAULT_FLING_VELOCITY, false);
} else {
animatePanel(DEFAULT_FLING_VELOCITY, true);
@@ -221,16 +259,15 @@
* 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();
- }
+ float to = getEndPosition(isClosing);
Rect rect = getLayout().getClipBounds();
- if (rect != null && rect.bottom != to) {
- float from = rect.bottom;
- animate(from, to, velocity, isClosing);
- return;
+ if (rect != null) {
+ float from = getCurrentStartPosition(rect);
+ if (from != to) {
+ animate(from, to, velocity, isClosing);
+ return;
+ }
}
// We will only be here if the shade is being opened programmatically or via button when
@@ -242,12 +279,32 @@
public void onGlobalLayout() {
ViewTreeObserver obs = getLayout().getViewTreeObserver();
obs.removeOnGlobalLayoutListener(this);
- float to = getLayout().getHeight();
- animate(/* from= */ 0, to, velocity, isClosing);
+ animate(
+ getDefaultStartPosition(),
+ getEndPosition(/* isClosing= */ false),
+ velocity,
+ isClosing
+ );
}
});
}
+ /* Returns the start position if the user has not started swiping. */
+ private int getDefaultStartPosition() {
+ return mAnimateDirection > 0 ? 0 : getLayout().getHeight();
+ }
+
+ /** Returns the start position if we are in the middle of swiping. */
+ private int getCurrentStartPosition(Rect clipBounds) {
+ return mAnimateDirection > 0 ? clipBounds.bottom : clipBounds.top;
+ }
+
+ private int getEndPosition(boolean isClosing) {
+ return (mAnimateDirection > 0 && !isClosing) || (mAnimateDirection == -1 && isClosing)
+ ? getLayout().getHeight()
+ : 0;
+ }
+
private void animate(float from, float to, float velocity, boolean isClosing) {
if (mIsAnimating) {
return;
@@ -356,25 +413,44 @@
* Misc
* ***************************************************************************************** */
- protected void calculatePercentageFromBottom(float height) {
+ /**
+ * Given the position of the pointer dragging the panel, return the percentage of its closeness
+ * to the ending edge.
+ */
+ protected void calculatePercentageFromEndingEdge(float y) {
if (getLayout().getHeight() > 0) {
- mPercentageFromBottom = (int) Math.abs(
- height / getLayout().getHeight() * 100);
+ float height = getVisiblePanelHeight(y);
+ mPercentageFromEndingEdge = (int) Math.abs(height / getLayout().getHeight() * 100);
}
}
- protected void setViewClipBounds(int height) {
- if (height > getLayout().getHeight()) {
- height = getLayout().getHeight();
- }
+ private float getVisiblePanelHeight(float y) {
+ return mAnimateDirection > 0 ? y : getLayout().getHeight() - y;
+ }
+
+ /** Sets the boundaries of the overlay panel that can be seen based on pointer position. */
+ protected void setViewClipBounds(int y) {
+ // Bound the pointer position to be within the overlay panel.
+ y = Math.max(0, Math.min(y, getLayout().getHeight()));
Rect clipBounds = new Rect();
- clipBounds.set(0, 0, getLayout().getWidth(), height);
+ int top, bottom;
+ if (mAnimateDirection > 0) {
+ top = 0;
+ bottom = y;
+ } else {
+ top = y;
+ bottom = getLayout().getHeight();
+ }
+ clipBounds.set(0, top, getLayout().getWidth(), bottom);
getLayout().setClipBounds(clipBounds);
- onScroll(height);
+ onScroll(y);
}
- /** Called while scrolling. */
- protected abstract void onScroll(int height);
+ /**
+ * Called while scrolling, this passes the position of the clip boundary that is currently
+ * changing.
+ */
+ protected abstract void onScroll(int y);
/* ***************************************************************************************** *
* Getters
@@ -406,8 +482,8 @@
}
/** Returns the percentage of the panel that is open from the bottom. */
- protected final int getPercentageFromBottom() {
- return mPercentageFromBottom;
+ protected final int getPercentageFromEndingEdge() {
+ return mPercentageFromEndingEdge;
}
/** Returns the percentage at which we've determined whether to open or close the panel. */
@@ -443,7 +519,7 @@
// Initially the scroll starts with height being zero. This checks protects from divide
// by zero error.
- calculatePercentageFromBottom(event2.getRawY());
+ calculatePercentageFromEndingEdge(event2.getRawY());
mIsTracking = true;
return true;
@@ -453,7 +529,7 @@
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
- if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
+ if (mAnimateDirection * velocityY > SWIPE_THRESHOLD_VELOCITY) {
mOpeningVelocity = velocityY;
open();
return true;
@@ -483,19 +559,14 @@
@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();
- }
+ float y = getYPositionOfPanelEndingEdge(event1, event2);
if (getLayout().getHeight() > 0) {
- mPercentageFromBottom = (int) Math.abs(
- actualNotificationHeight / getLayout().getHeight() * 100);
- boolean isUp = distanceY > 0;
+ mPercentageFromEndingEdge = (int) Math.abs(
+ y / getLayout().getHeight() * 100);
+ boolean isInClosingDirection = mAnimateDirection * 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
@@ -503,23 +574,37 @@
// 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);
+ if (Math.abs(getLayout().getHeight() - y)
+ > SWIPE_DOWN_MIN_DISTANCE && isInClosingDirection) {
+ setViewClipBounds((int) y);
mIsTracking = true;
- } else if (!isUp) {
- setViewClipBounds((int) actualNotificationHeight);
+ } else if (!isInClosingDirection) {
+ setViewClipBounds((int) y);
}
}
// if we return true the items in RV won't be scrollable.
return false;
}
+ /**
+ * To prevent the jump in the clip bounds while closing the panel we should calculate the y
+ * position using the diff of event1 and event2. This will help the panel clip smoothly as
+ * the event2 value changes while event1 value will be fixed.
+ * @param event1 MotionEvent that contains the position of where the event2 started.
+ * @param event2 MotionEvent that contains the position of where the user has scrolled to
+ * on the screen.
+ */
+ private float getYPositionOfPanelEndingEdge(MotionEvent event1, MotionEvent event2) {
+ float diff = mAnimateDirection * (event1.getRawY() - event2.getRawY());
+ float y = mAnimateDirection > 0 ? getLayout().getHeight() - diff : diff;
+ y = Math.max(0, Math.min(y, getLayout().getHeight()));
+ return y;
+ }
@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.
+ // should not fling if the touch does not start when view is at the end of the list.
if (!shouldAllowClosingScroll()) {
return false;
}
@@ -528,8 +613,8 @@
// swipe was not vertical or was not fast enough
return false;
}
- boolean isUp = velocityY < 0;
- if (isUp) {
+ boolean isInClosingDirection = mAnimateDirection * velocityY < 0;
+ if (isInClosingDirection) {
close();
return true;
} else {
@@ -555,7 +640,7 @@
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
float distanceY) {
- calculatePercentageFromBottom(event2.getRawY());
+ calculatePercentageFromEndingEdge(event2.getRawY());
setViewClipBounds((int) event2.getRawY());
return true;
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java
index c46b287..e1918ce 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java
@@ -17,7 +17,8 @@
package com.android.systemui.car.window;
import com.android.systemui.car.keyguard.CarKeyguardViewMediator;
-import com.android.systemui.car.notification.NotificationPanelViewMediator;
+import com.android.systemui.car.notification.BottomNotificationPanelViewMediator;
+import com.android.systemui.car.notification.TopNotificationPanelViewMediator;
import com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator;
import dagger.Binds;
@@ -31,12 +32,19 @@
@Module
public abstract class OverlayWindowModule {
- /** Injects NotificationPanelViewMediator. */
+ /** Injects TopNotificationPanelViewMediator. */
@Binds
@IntoMap
- @ClassKey(NotificationPanelViewMediator.class)
- public abstract OverlayViewMediator bindNotificationPanelViewMediator(
- NotificationPanelViewMediator notificationPanelViewMediator);
+ @ClassKey(TopNotificationPanelViewMediator.class)
+ public abstract OverlayViewMediator bindTopNotificationPanelViewMediator(
+ TopNotificationPanelViewMediator topNotificationPanelViewMediator);
+
+ /** Injects BottomNotificationPanelViewMediator. */
+ @Binds
+ @IntoMap
+ @ClassKey(BottomNotificationPanelViewMediator.class)
+ public abstract OverlayViewMediator bindBottomNotificationPanelViewMediator(
+ BottomNotificationPanelViewMediator bottomNotificationPanelViewMediator);
/** Inject into CarKeyguardViewMediator. */
@Binds
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
index 70f1d25..8d705a8 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
@@ -18,13 +18,16 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
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.animation.Animator;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
@@ -42,6 +45,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -173,6 +177,51 @@
}
@Test
+ public void animateCollapsePanel_withOverlayFromTopBar_collapsesTowardsTopBar() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ // Mock a panel that has layout size 50 and where the panel is opened.
+ int size = 50;
+ mockPanelWithSize(size);
+ mOverlayPanelViewController.getLayout().setClipBounds(
+ new Rect(0, 0, size, size));
+ mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
+ mOverlayPanelViewController.setPanelExpanded(true);
+ mOverlayPanelViewController.setPanelVisible(true);
+ mOverlayPanelViewController.setOverlayDirection(
+ OverlayPanelViewController.OVERLAY_FROM_TOP_BAR);
+
+ mOverlayPanelViewController.animateCollapsePanel();
+
+ ArgumentCaptor<Float> endValueCaptor = ArgumentCaptor.forClass(Float.class);
+ verify(mFlingAnimationUtils).apply(
+ any(Animator.class), anyFloat(), endValueCaptor.capture(), anyFloat());
+ assertThat(endValueCaptor.getValue().intValue()).isEqualTo(0);
+ }
+
+ @Test
+ public void animateCollapsePanel_withOverlayFromBottomBar_collapsesTowardsBottomBar() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ // Mock a panel that has layout size 50 and where the panel is opened.
+ int size = 50;
+ mockPanelWithSize(size);
+ mOverlayPanelViewController.getLayout().setClipBounds(
+ new Rect(0, 0, size, size));
+ mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
+ mOverlayPanelViewController.setPanelExpanded(true);
+ mOverlayPanelViewController.setPanelVisible(true);
+ mOverlayPanelViewController.setOverlayDirection(
+ OverlayPanelViewController.OVERLAY_FROM_BOTTOM_BAR);
+
+ mOverlayPanelViewController.animateCollapsePanel();
+
+ ArgumentCaptor<Float> endValueCaptor = ArgumentCaptor.forClass(Float.class);
+ verify(mFlingAnimationUtils).apply(
+ any(Animator.class), anyFloat(), endValueCaptor.capture(), anyFloat());
+ assertThat(endValueCaptor.getValue().intValue()).isEqualTo(
+ mOverlayPanelViewController.getLayout().getHeight());
+ }
+
+ @Test
public void animateCollapsePanel_removesWindowFocus() {
mOverlayPanelViewController.inflate(mBaseLayout);
mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
@@ -219,6 +268,49 @@
}
@Test
+ public void animateExpandPanel_withOverlayFromTopBar_expandsToBottom() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ // Mock a panel that has layout size 50 and where the panel is not opened.
+ int size = 50;
+ mockPanelWithSize(size);
+ mOverlayPanelViewController.getLayout().setClipBounds(
+ new Rect(0, 0, size, 0));
+ mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
+ when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
+ mOverlayPanelViewController.setOverlayDirection(
+ OverlayPanelViewController.OVERLAY_FROM_TOP_BAR);
+
+ mOverlayPanelViewController.animateExpandPanel();
+
+ ArgumentCaptor<Float> endValueCaptor = ArgumentCaptor.forClass(Float.class);
+ verify(mFlingAnimationUtils).apply(
+ any(Animator.class), anyFloat(), endValueCaptor.capture(), anyFloat());
+ assertThat(endValueCaptor.getValue().intValue()).isEqualTo(
+ mOverlayPanelViewController.getLayout().getHeight());
+ }
+
+ @Test
+ public void animateExpandPanel_withOverlayFromBottomBar_expandsToTop() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ // Mock a panel that has layout size 50 and where the panel is not opened.
+ int size = 50;
+ mockPanelWithSize(size);
+ mOverlayPanelViewController.getLayout().setClipBounds(
+ new Rect(0, size, size, size));
+ mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
+ when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
+ mOverlayPanelViewController.setOverlayDirection(
+ OverlayPanelViewController.OVERLAY_FROM_BOTTOM_BAR);
+
+ mOverlayPanelViewController.animateExpandPanel();
+
+ ArgumentCaptor<Float> endValueCaptor = ArgumentCaptor.forClass(Float.class);
+ verify(mFlingAnimationUtils).apply(
+ any(Animator.class), anyFloat(), endValueCaptor.capture(), anyFloat());
+ assertThat(endValueCaptor.getValue().intValue()).isEqualTo(0);
+ }
+
+ @Test
public void animateExpandPanel_setsPanelVisible() {
mOverlayPanelViewController.inflate(mBaseLayout);
mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
@@ -330,6 +422,10 @@
verify(mOverlayViewGlobalStateController).inflateView(mOverlayPanelViewController);
}
+ private void mockPanelWithSize(int size) {
+ mOverlayPanelViewController.getLayout().setLeftTopRightBottom(0, 0, size, size);
+ }
+
private static class TestOverlayPanelViewController extends OverlayPanelViewController {
private boolean mShouldAnimateCollapsePanel;