Merge "Reveal recent calls shortcut only when at top of list" into lmp-preview-dev
diff --git a/src/com/android/dialer/list/ListsFragment.java b/src/com/android/dialer/list/ListsFragment.java
index 3de714d..ab790ed 100644
--- a/src/com/android/dialer/list/ListsFragment.java
+++ b/src/com/android/dialer/list/ListsFragment.java
@@ -73,6 +73,7 @@
 
     public interface HostInterface {
         public void showCallHistory();
+        public int getActionBarHeight();
     }
 
     private ActionBar mActionBar;
@@ -348,6 +349,8 @@
         paneLayout.setCapturableView(mViewPagerTabs);
         paneLayout.openPane();
         paneLayout.setPanelSlideListener(mPanelSlideListener);
+        paneLayout.setIntermediatePinnedOffset(
+                ((HostInterface) getActivity()).getActionBarHeight());
 
         LayoutTransition transition = paneLayout.getLayoutTransition();
         // Turns on animations for all types of layout changes so that they occur for
diff --git a/src/com/android/dialer/widget/OverlappingPaneLayout.java b/src/com/android/dialer/widget/OverlappingPaneLayout.java
index e17194e..e28bcc5 100644
--- a/src/com/android/dialer/widget/OverlappingPaneLayout.java
+++ b/src/com/android/dialer/widget/OverlappingPaneLayout.java
@@ -44,6 +44,7 @@
  */
 public class OverlappingPaneLayout extends ViewGroup {
     private static final String TAG = "SlidingPaneLayout";
+    private static final boolean DEBUG = false;
 
     /**
      * Default size of the overhang for a pane in the open state.
@@ -92,6 +93,12 @@
     private float mSlideOffset;
 
     /**
+     * How far the panel is offset from its closed position, in pixels.
+     * range [0, {@link #mSlideRange}] where 0 is completely closed.
+     */
+    private int mSlideOffsetPx;
+
+    /**
      * How far in pixels the slideable panel may move.
      */
     private int mSlideRange;
@@ -107,6 +114,20 @@
      */
     private boolean mIsInNestedScroll;
 
+    /**
+     * Indicates that the layout is currently in the process of a nested pre-scroll operation where
+     * the child is being dragged downwards. If so, we should open the pane up to the maximum
+     * offset defined in {@link #mIntermediateOffset}, and no further.
+     */
+    boolean mInNestedPreScrollDownwards = false;
+
+    /**
+     * Stores an offset used to represent a point somewhere in between the panel's fully closed
+     * state and fully opened state where the panel can be temporarily pinned or opened up to
+     * during scrolling.
+     */
+    private int mIntermediateOffset = 0;
+
     private float mInitialMotionX;
     private float mInitialMotionY;
 
@@ -173,6 +194,16 @@
     }
 
     /**
+     * Set an offset, somewhere in between the panel's fully closed state and fully opened state,
+     * where the panel can be temporarily pinned or opened up to.
+     *
+     * @param offset Offset in pixels
+     */
+    public void setIntermediatePinnedOffset(int offset) {
+        mIntermediateOffset = offset;
+    }
+
+    /**
      * Set the view that can be used to start dragging the sliding pane.
      */
     public void setCapturableView(View capturableView) {
@@ -525,7 +556,7 @@
                 final int lpMargin = lp.topMargin;
                 final int pos = (int) (range * mSlideOffset);
                 yStart += pos + lpMargin;
-                mSlideOffset = (float) pos / mSlideRange;
+                updateSlideOffset(pos);
             } else {
                 yStart = nextYStart;
             }
@@ -664,6 +695,11 @@
         return false;
     }
 
+    private void updateSlideOffset(int offsetPx) {
+        mSlideOffsetPx = offsetPx;
+        mSlideOffset = (float) mSlideOffsetPx / mSlideRange;
+    }
+
     /**
      * Open the sliding pane if it is currently slideable. If first layout
      * has already completed this will animate.
@@ -685,13 +721,13 @@
     }
 
     /**
-     * Check if the layout is completely open. It can be open either because the slider
+     * Check if the layout is open. It can be open either because the slider
      * itself is open revealing the left pane, or if all content fits without sliding.
      *
-     * @return true if sliding panels are completely open
+     * @return true if sliding panels are open
      */
     public boolean isOpen() {
-        return !mCanSlide || mSlideOffset == 1;
+        return !mCanSlide || mSlideOffset > 0;
     }
 
     /**
@@ -715,7 +751,7 @@
         final int lpMargin = lp.topMargin;
         final int topBound = getPaddingTop() + lpMargin;
 
-        mSlideOffset = (float) (newTop - topBound) / mSlideRange;
+        updateSlideOffset(newTop - topBound);
 
         dispatchOnPanelSlide(mSlideableView);
     }
@@ -855,17 +891,44 @@
             mIsInNestedScroll = true;
             mDragHelper.startNestedScroll(mSlideableView);
         }
+        if (DEBUG) {
+            Log.d(TAG, "onStartNestedScroll: " + startNestedScroll);
+        }
         return startNestedScroll;
     }
 
     @Override
     public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+        if (dy == 0) {
+            // Nothing to do
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "onNestedPreScroll: " + dy);
+        }
+        mInNestedPreScrollDownwards = (dy > 0 && mSlideOffsetPx <= mIntermediateOffset);
         mDragHelper.processNestedScroll(mSlideableView, 0, dy, consumed);
     }
 
     @Override
+    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
+            int dyUnconsumed) {
+        if (DEBUG) {
+            Log.d(TAG, "onNestedScroll: " + dyUnconsumed);
+        }
+        mInNestedPreScrollDownwards = false;
+        // We need to flip dyUnconsumed here, because its magnitude is reversed. b/14585990
+        mDragHelper.processNestedScroll(mSlideableView, 0, -dyUnconsumed, null);
+    }
+
+    @Override
     public void onStopNestedScroll(View child) {
-        mDragHelper.stopNestedScroll(mSlideableView);
+        if (DEBUG) {
+            Log.d(TAG, "onStopNestedScroll");
+        }
+        if (mIsInNestedScroll) {
+            mDragHelper.stopNestedScroll(mSlideableView);
+        }
         mIsInNestedScroll = false;
     }
 
@@ -911,7 +974,25 @@
             final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams();
 
             int top = getPaddingTop() + lp.topMargin;
-            if (yvel > 0 || (yvel == 0 && mSlideOffset > 0.5f)) {
+
+            if (mInNestedPreScrollDownwards) {
+                // Snap to the closest pinnable position based on the current slide offset
+                // (in pixels)   [0  -  mIntermediateoffset  - mSlideRange]
+                if (yvel > 0) {
+                    top += mSlideRange;
+                } else if (0 <= mSlideOffsetPx && mSlideOffsetPx <= mIntermediateOffset / 2) {
+                    // Offset is between 0 and mIntermediateOffset, but closer to 0
+                    // Leave top unchanged
+                } else if (mIntermediateOffset / 2 <= mSlideOffsetPx
+                        && mSlideOffsetPx <= (mIntermediateOffset + mSlideRange) / 2) {
+                    // Offset is closest to mIntermediateOffset
+                    top += mIntermediateOffset;
+                } else {
+                    // Offset is between mIntermediateOffset and mSlideRange, but closer to
+                    // mSlideRange
+                    top += mSlideRange;
+                }
+            } else if (yvel > 0 || (yvel == 0 && mSlideOffset > 0.5f)) {
                 top += mSlideRange;
             }
 
@@ -936,7 +1017,8 @@
 
             final int newTop;
             int topBound = getPaddingTop() + lp.topMargin;
-            int bottomBound = topBound + mSlideRange;
+            int bottomBound = topBound
+                    + (mInNestedPreScrollDownwards ? mIntermediateOffset : mSlideRange);
             newTop = Math.min(Math.max(top, topBound), bottomBound);
 
             return newTop;