Handle cases for going to SHADE_LOCKED

- When the user drags down in the empty (not on a notification), we
  scale up the clock in the interaction.
- If there are no notifications at all, we stay in KEYGUARD and let
  the clock spring back.
- Rubberbanding is different for the case when there are no
  notifications or only low-priority notifications, to indicate what
  the user can expect when they lift his finger.

Bug: 16216907
Change-Id: I69d6552c96ef9e3d0d0b526bbd232d68bef47960
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index 517a4e8..e9989ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.view.MotionEvent;
 import android.view.View;
@@ -46,22 +47,23 @@
     private float mInitialTouchY;
     private boolean mDraggingDown;
     private float mTouchSlop;
-    private OnDragDownListener mOnDragDownListener;
+    private DragDownCallback mDragDownCallback;
     private View mHost;
     private final int[] mTemp2 = new int[2];
     private boolean mDraggedFarEnough;
     private ExpandableView mStartingChild;
     private Interpolator mInterpolator;
+    private float mLastHeight;
 
     public DragDownHelper(Context context, View host, ExpandHelper.Callback callback,
-            OnDragDownListener onDragDownListener) {
+            DragDownCallback dragDownCallback) {
         mMinDragDistance = context.getResources().getDimensionPixelSize(
                 R.dimen.keyguard_drag_down_min_distance);
         mInterpolator =
                 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mCallback = callback;
-        mOnDragDownListener = onDragDownListener;
+        mDragDownCallback = dragDownCallback;
         mHost = host;
     }
 
@@ -86,7 +88,7 @@
                     captureStartingChild(mInitialTouchX, mInitialTouchY);
                     mInitialTouchY = y;
                     mInitialTouchX = x;
-                    mOnDragDownListener.onTouchSlopExceeded();
+                    mDragDownCallback.onTouchSlopExceeded();
                     return true;
                 }
                 break;
@@ -104,29 +106,32 @@
 
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_MOVE:
-                final float h = y - mInitialTouchY;
+                mLastHeight = y - mInitialTouchY;
                 captureStartingChild(mInitialTouchX, mInitialTouchY);
                 if (mStartingChild != null) {
-                    handleExpansion(h, mStartingChild);
+                    handleExpansion(mLastHeight, mStartingChild);
+                } else {
+                    mDragDownCallback.setEmptyDragAmount(mLastHeight);
                 }
-                if (h > mMinDragDistance) {
+                if (mLastHeight > mMinDragDistance) {
                     if (!mDraggedFarEnough) {
                         mDraggedFarEnough = true;
-                        mOnDragDownListener.onThresholdReached();
+                        mDragDownCallback.onThresholdReached();
                     }
                 } else {
                     if (mDraggedFarEnough) {
                         mDraggedFarEnough = false;
-                        mOnDragDownListener.onDragDownReset();
+                        mDragDownCallback.onDragDownReset();
                     }
                 }
                 return true;
             case MotionEvent.ACTION_UP:
-                if (mDraggedFarEnough) {
+                if (mDraggedFarEnough && mDragDownCallback.onDraggedDown(mStartingChild)) {
                     if (mStartingChild != null) {
                         mCallback.setUserLockedChild(mStartingChild, false);
+                    } else {
+                        mDragDownCallback.setEmptyDragAmount(0f);
                     }
-                    mOnDragDownListener.onDraggedDown(mStartingChild);
                     mDraggingDown = false;
                 } else {
                     stopDragging();
@@ -183,12 +188,27 @@
         anim.start();
     }
 
+    private void cancelExpansion() {
+        ValueAnimator anim = ValueAnimator.ofFloat(mLastHeight, 0);
+        anim.setInterpolator(mInterpolator);
+        anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS);
+        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                mDragDownCallback.setEmptyDragAmount((Float) animation.getAnimatedValue());
+            }
+        });
+        anim.start();
+    }
+
     private void stopDragging() {
         if (mStartingChild != null) {
             cancelExpansion(mStartingChild);
+        } else {
+            cancelExpansion();
         }
         mDraggingDown = false;
-        mOnDragDownListener.onDragDownReset();
+        mDragDownCallback.onDragDownReset();
     }
 
     private ExpandableView findView(float x, float y) {
@@ -198,10 +218,15 @@
         return mCallback.getChildAtRawPosition(x, y);
     }
 
-    public interface OnDragDownListener {
-        void onDraggedDown(View startingChild);
+    public interface DragDownCallback {
+
+        /**
+         * @return true if the interaction is accepted, false if it should be cancelled
+         */
+        boolean onDraggedDown(View startingChild);
         void onDragDownReset();
         void onThresholdReached();
         void onTouchSlopExceeded();
+        void setEmptyDragAmount(float amount);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 319096d..a15d35e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -36,7 +36,6 @@
     private static final float CLOCK_SCALE_FADE_END = 0.75f;
     private static final float CLOCK_SCALE_FADE_END_NO_NOTIFS = 0.5f;
 
-
     private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MIN = 1.4f;
     private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX = 3.2f;
 
@@ -50,6 +49,8 @@
     private int mNotificationCount;
     private int mHeight;
     private int mKeyguardStatusHeight;
+    private float mEmptyDragAmount;
+    private float mDensity;
 
     /**
      * The number (fractional) of notifications the "more" card counts when calculating how many
@@ -81,16 +82,18 @@
         mMoreCardNotificationAmount =
                 (float) res.getDimensionPixelSize(R.dimen.notification_summary_height) /
                         res.getDimensionPixelSize(R.dimen.notification_min_height);
+        mDensity = res.getDisplayMetrics().density;
     }
 
     public void setup(int maxKeyguardNotifications, int maxPanelHeight, float expandedHeight,
-            int notificationCount, int height, int keyguardStatusHeight) {
+            int notificationCount, int height, int keyguardStatusHeight, float emptyDragAmount) {
         mMaxKeyguardNotifications = maxKeyguardNotifications;
         mMaxPanelHeight = maxPanelHeight;
         mExpandedHeight = expandedHeight;
         mNotificationCount = notificationCount;
         mHeight = height;
         mKeyguardStatusHeight = keyguardStatusHeight;
+        mEmptyDragAmount = emptyDragAmount;
     }
 
     public void run(Result result) {
@@ -116,6 +119,7 @@
         float progress = distanceToScaleEnd / (startPadding - scaleEnd);
         progress = Math.max(0.0f, Math.min(progress, 1.0f));
         progress = mAccelerateInterpolator.getInterpolation(progress);
+        progress *= Math.pow(1 + mEmptyDragAmount / mDensity / 300, 0.3f);
         return progress;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 291e692..909972a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -45,8 +45,6 @@
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
-import java.util.ArrayList;
-
 public class NotificationPanelView extends PanelView implements
         ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
         View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
@@ -112,6 +110,7 @@
     private boolean mUnlockIconActive;
     private int mNotificationsHeaderCollideDistance;
     private int mUnlockMoveDistance;
+    private float mEmptyDragAmount;
 
     private Interpolator mFastOutSlowInInterpolator;
     private Interpolator mFastOutLinearInterpolator;
@@ -236,7 +235,8 @@
                     getExpandedHeight(),
                     mNotificationStackScroller.getNotGoneChildCount(),
                     getHeight(),
-                    mKeyguardStatusView.getHeight());
+                    mKeyguardStatusView.getHeight(),
+                    mEmptyDragAmount);
             mClockPositionAlgorithm.run(mClockPositionResult);
             if (animate || mClockAnimator != null) {
                 startClockAnimation(mClockPositionResult.clockY);
@@ -1321,4 +1321,15 @@
     public void setLaunchTransitionEndRunnable(Runnable r) {
         mLaunchAnimationEndRunnable = r;
     }
+
+    public void setEmptyDragAmount(float amount) {
+        float factor = 1f;
+        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
+            factor = 0.6f;
+        } else if (!mStatusBar.hasNotifications()) {
+            factor = 0.4f;
+        }
+        mEmptyDragAmount = amount * factor;
+        positionClockAndNotifications();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index a0b4b4c..2123bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -159,7 +159,7 @@
 import java.util.List;
 
 public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
-        DragDownHelper.OnDragDownListener, ActivityStarter {
+        DragDownHelper.DragDownCallback, ActivityStarter {
     static final String TAG = "PhoneStatusBar";
     public static final boolean DEBUG = BaseStatusBar.DEBUG;
     public static final boolean SPEW = false;
@@ -453,6 +453,8 @@
     private Runnable mLaunchTransitionEndRunnable;
     private boolean mLaunchTransitionFadingAway;
 
+    private boolean mHasNotifications;
+
     private static final int VISIBLE_LOCATIONS = ViewState.LOCATION_FIRST_CARD
             | ViewState.LOCATION_TOP_STACK_PEEKING
             | ViewState.LOCATION_MAIN_AREA
@@ -1313,8 +1315,12 @@
             updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
 
             if (CLOSE_PANEL_WHEN_EMPTIED && mNotificationData.size() == 0
-                    && !mNotificationPanel.isTracking() && mState != StatusBarState.KEYGUARD) {
-                animateCollapsePanels();
+                    && !mNotificationPanel.isTracking()) {
+                if (mState == StatusBarState.SHADE) {
+                    animateCollapsePanels();
+                } else if (mState == StatusBarState.SHADE_LOCKED) {
+                    goToKeyguard();
+                }
             }
         }
         setAreThereNotifications();
@@ -1610,6 +1616,9 @@
         findAndUpdateMediaNotifications();
 
         updateCarrierLabelVisibility(false);
+
+        // TODO: Multiuser handling!
+        mHasNotifications = any;
     }
 
     public void findAndUpdateMediaNotifications() {
@@ -3600,8 +3609,17 @@
     // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
 
     @Override
-    public void onDraggedDown(View startingChild) {
-        goToLockedShade(startingChild);
+    public boolean onDraggedDown(View startingChild) {
+        if (mHasNotifications) {
+
+            // We have notifications, go to locked shade.
+            goToLockedShade(startingChild);
+            return true;
+        } else {
+
+            // No notifications - abort gesture.
+            return false;
+        }
     }
 
     @Override
@@ -3609,6 +3627,7 @@
         mStackScroller.setDimmed(true /* dimmed */, true /* animated */);
     }
 
+    @Override
     public void onThresholdReached() {
         mStackScroller.setDimmed(false /* dimmed */, true /* animate */);
     }
@@ -3618,6 +3637,11 @@
         mStackScroller.removeLongPressCallback();
     }
 
+    @Override
+    public void setEmptyDragAmount(float amount) {
+        mNotificationPanel.setEmptyDragAmount(amount);
+    }
+
     /**
      * If secure with redaction: Show bouncer, go to unlocked shade.
      *
@@ -3728,6 +3752,10 @@
         notifyUiVisibilityChanged(mSystemUiVisibility);
     }
 
+    public boolean hasNotifications() {
+        return mHasNotifications;
+    }
+
     private final class ShadeUpdates {
         private final ArraySet<String> mVisibleNotifications = new ArraySet<String>();
         private final ArraySet<String> mNewVisibleNotifications = new ArraySet<String>();