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;