Add ability for ResolverDrawer to be align to top via XML attribute.

Bug: 70036020
Test: ResolverActivityTest#setShowAtTopToTrue passes.
Change-Id: I2c51c9c731234f05a589c52d5a03b8b6605d4c9c
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 17c7ebd..7635a72 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -81,6 +81,7 @@
     private int mCollapsibleHeightReserved;
 
     private int mTopOffset;
+    private boolean mShowAtTop;
 
     private boolean mIsDragging;
     private boolean mOpenOnClick;
@@ -134,6 +135,7 @@
         mMaxCollapsedHeightSmall = a.getDimensionPixelSize(
                 R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall,
                 mMaxCollapsedHeight);
+        mShowAtTop = a.getBoolean(R.styleable.ResolverDrawerLayout_showAtTop, false);
         a.recycle();
 
         mScrollIndicatorDrawable = mContext.getDrawable(R.drawable.scroll_indicator_material);
@@ -162,6 +164,16 @@
         return mCollapseOffset > 0;
     }
 
+    public void setShowAtTop(boolean showOnTop) {
+        mShowAtTop = showOnTop;
+        invalidate();
+        requestLayout();
+    }
+
+    public boolean getShowAtTop() {
+        return mShowAtTop;
+    }
+
     public void setCollapsed(boolean collapsed) {
         if (!isLaidOut()) {
             mOpenOnLayout = collapsed;
@@ -206,6 +218,12 @@
             return false;
         }
 
+        if (getShowAtTop()) {
+            // Keep the drawer fully open.
+            mCollapseOffset = 0;
+            return false;
+        }
+
         if (isLaidOut()) {
             final boolean isCollapsedOld = mCollapseOffset != 0;
             if (remainClosed && (oldCollapsibleHeight < mCollapsibleHeight
@@ -372,14 +390,23 @@
                 mVelocityTracker.computeCurrentVelocity(1000);
                 final float yvel = mVelocityTracker.getYVelocity(mActivePointerId);
                 if (Math.abs(yvel) > mMinFlingVelocity) {
-                    if (isDismissable()
-                            && yvel > 0 && mCollapseOffset > mCollapsibleHeight) {
-                        smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, yvel);
-                        mDismissOnScrollerFinished = true;
+                    if (getShowAtTop()) {
+                        if (isDismissable() && yvel < 0) {
+                            abortAnimation();
+                            dismiss();
+                        } else {
+                            smoothScrollTo(yvel < 0 ? 0 : mCollapsibleHeight, yvel);
+                        }
                     } else {
-                        smoothScrollTo(yvel < 0 ? 0 : mCollapsibleHeight, yvel);
+                        if (isDismissable()
+                                && yvel > 0 && mCollapseOffset > mCollapsibleHeight) {
+                            smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, yvel);
+                            mDismissOnScrollerFinished = true;
+                        } else {
+                            smoothScrollTo(yvel < 0 ? 0 : mCollapsibleHeight, yvel);
+                        }
                     }
-                } else {
+                }else {
                     smoothScrollTo(
                             mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0);
                 }
@@ -421,6 +448,11 @@
         mVelocityTracker.clear();
     }
 
+    private void dismiss() {
+        mRunOnDismissedListener = new RunOnDismissedListener();
+        post(mRunOnDismissedListener);
+    }
+
     @Override
     public void computeScroll() {
         super.computeScroll();
@@ -430,8 +462,7 @@
             if (keepGoing) {
                 postInvalidateOnAnimation();
             } else if (mDismissOnScrollerFinished && mOnDismissedListener != null) {
-                mRunOnDismissedListener = new RunOnDismissedListener();
-                post(mRunOnDismissedListener);
+                dismiss();
             }
         }
     }
@@ -443,6 +474,10 @@
     }
 
     private float performDrag(float dy) {
+        if (getShowAtTop()) {
+            return 0;
+        }
+
         final float newPos = Math.max(0, Math.min(mCollapseOffset + dy,
                 mCollapsibleHeight + mUncollapsibleHeight));
         if (newPos != mCollapseOffset) {
@@ -656,7 +691,7 @@
 
     @Override
     public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
-        if (velocityY > mMinFlingVelocity && mCollapseOffset != 0) {
+        if (!getShowAtTop() && velocityY > mMinFlingVelocity && mCollapseOffset != 0) {
             smoothScrollTo(0, velocityY);
             return true;
         }
@@ -666,12 +701,21 @@
     @Override
     public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
         if (!consumed && Math.abs(velocityY) > mMinFlingVelocity) {
-            if (isDismissable()
-                    && velocityY < 0 && mCollapseOffset > mCollapsibleHeight) {
-                smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, velocityY);
-                mDismissOnScrollerFinished = true;
+            if (getShowAtTop()) {
+                if (isDismissable() && velocityY > 0) {
+                    abortAnimation();
+                    dismiss();
+                } else {
+                    smoothScrollTo(velocityY < 0 ? mCollapsibleHeight : 0, velocityY);
+                }
             } else {
-                smoothScrollTo(velocityY > 0 ? 0 : mCollapsibleHeight, velocityY);
+                if (isDismissable()
+                        && velocityY < 0 && mCollapseOffset > mCollapsibleHeight) {
+                    smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, velocityY);
+                    mDismissOnScrollerFinished = true;
+                } else {
+                    smoothScrollTo(velocityY > 0 ? 0 : mCollapsibleHeight, velocityY);
+                }
             }
             return true;
         }
@@ -794,7 +838,11 @@
 
         updateCollapseOffset(oldCollapsibleHeight, !isDragging());
 
-        mTopOffset = Math.max(0, heightSize - heightUsed) + (int) mCollapseOffset;
+        if (getShowAtTop()) {
+            mTopOffset = 0;
+        } else {
+            mTopOffset = Math.max(0, heightSize - heightUsed) + (int) mCollapseOffset;
+        }
 
         setMeasuredDimension(sourceWidth, heightSize);
     }
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9a0eafd..50e303e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8535,6 +8535,8 @@
         <attr name="maxWidth" />
         <attr name="maxCollapsedHeight" format="dimension" />
         <attr name="maxCollapsedHeightSmall" format="dimension" />
+        <!-- Whether the Drawer should be positioned at the top rather than at the bottom. -->
+        <attr name="showAtTop" format="boolean" />
     </declare-styleable>
 
     <declare-styleable name="MessagingLinearLayout">
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 55e804b..9fcb06e 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -18,6 +18,7 @@
 
 import com.android.internal.R;
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.widget.ResolverDrawerLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -33,6 +34,8 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.widget.RelativeLayout;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -100,6 +103,34 @@
     }
 
     @Test
+    public void setShowAtTopToTrue() throws Exception {
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+        waitForIdle();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        final View resolverList = activity.findViewById(R.id.resolver_list);
+        final RelativeLayout profileView =
+            (RelativeLayout) activity.findViewById(R.id.profile_button).getParent();
+        assertThat("Drawer should show at bottom by default",
+                profileView.getBottom() == resolverList.getTop() && profileView.getTop() > 0);
+
+        activity.runOnUiThread(() -> {
+            ResolverDrawerLayout layout = (ResolverDrawerLayout)
+                    activity.findViewById(
+                            R.id.contentPanel);
+            layout.setShowAtTop(true);
+        });
+        waitForIdle();
+        assertThat("Drawer should show at top with new attribute",
+            profileView.getBottom() == resolverList.getTop() && profileView.getTop() == 0);
+    }
+
+    @Test
     public void hasLastChosenActivity() throws Exception {
         Intent sendIntent = createSendImageIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -283,4 +314,4 @@
     private void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
-}
\ No newline at end of file
+}