summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author JianYang Liu <jianyliu@google.com> 2020-04-22 13:57:12 -0700
committer Jian-Yang Liu <jianyliu@google.com> 2020-04-24 23:12:51 +0000
commit2d922fb64b7bcd7553cc3f2ddef7eaacaaa0d86d (patch)
treecec90d86ed1c7742374151d698f65ac7d449e21d
parent9b8965044cdaa4d84ff0a3126ca6276f6a186940 (diff)
Updated OverlayPanelViewController to allow for showing overlay panel
from the bottom navigation bar. Added Top and Bottom NotificationPanelViewMediators that can be used to change the direction that the notification panel should go in. Bug: 145827692 Test: Manual Change-Id: I813415711f826a70cdbf4bd16e9b4f425e81e0de
-rw-r--r--packages/CarSystemUI/res/values/config.xml12
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java59
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java69
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java20
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java59
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java187
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java18
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java96
8 files changed, 428 insertions, 92 deletions
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index eb1d9d0dd602..728d57d54edb 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 000000000000..6d140cae5442
--- /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 a17a0e9d2920..cb9539ad5b1d 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 @@ public class NotificationPanelViewController extends OverlayPanelViewController
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 @@ public class NotificationPanelViewController extends OverlayPanelViewController
// 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 @@ public class NotificationPanelViewController extends OverlayPanelViewController
@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 @@ public class NotificationPanelViewController extends OverlayPanelViewController
}
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 @@ public class NotificationPanelViewController extends OverlayPanelViewController
}
}
- // 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 @@ public class NotificationPanelViewController extends OverlayPanelViewController
}
@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 @@ public class NotificationPanelViewController extends OverlayPanelViewController
* 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 @@ public class NotificationPanelViewController extends OverlayPanelViewController
@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 24a84d940577..8f52638afdf1 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 @@ package com.android.systemui.car.notification;
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 @@ public class NotificationPanelViewMediator implements OverlayViewMediator,
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
private final ConfigurationController mConfigurationController;
- @Inject
public NotificationPanelViewMediator(
CarNavigationBarController carNavigationBarController,
NotificationPanelViewController notificationPanelViewController,
@@ -56,9 +53,10 @@ public class NotificationPanelViewMediator implements OverlayViewMediator,
}
@Override
+ @CallSuper
public void registerListeners() {
mCarNavigationBarController.registerTopBarTouchListener(
- mNotificationPanelViewController.getDragOpenTouchListener());
+ mNotificationPanelViewController.getDragCloseTouchListener());
mCarNavigationBarController.registerBottomBarTouchListener(
mNotificationPanelViewController.getDragCloseTouchListener());
mCarNavigationBarController.registerLeftBarTouchListener(
@@ -128,4 +126,12 @@ public class NotificationPanelViewMediator implements OverlayViewMediator,
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 000000000000..09a462185dac
--- /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 90892d5c53e4..0fe985684543 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 @@ package com.android.systemui.car.window;
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.car.CarDeviceProvisionedController;
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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
}
});
- // 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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
if (!isInflated()) {
return true;
}
- boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
+ boolean consumed = closeGestureDetector.onTouchEvent(event);
if (consumed) {
return true;
}
@@ -141,6 +168,17 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
};
}
+ /** 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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
* 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 abstract class OverlayPanelViewController extends OverlayViewController {
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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
* 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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
}
/** 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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
// 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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
@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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
@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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
// 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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
// 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 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
@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 c46b287ade5a..e1918ceeaea4 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 @@ import dagger.multibindings.IntoMap;
@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 70f1d25fe2a4..8d705a8cca1f 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 @@ package com.android.systemui.car.window;
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 com.android.systemui.tests.R;
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 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
}
@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 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
}
@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 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
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;