Introduce SHADE_LOCKED as a special state for the shade.

This state can be either reached by tapping the more card or dragging
down on any card. In this state, the shade is fully interactive, but
the phone is stil locked. This state can only be enterred if
redaction is off but security is on. If redaction is on, we will show
the bouncer instead and go to the normal shade.

Bug: 14161523
Change-Id: I95ca0991745ffc11ed1028581e3da15265c12ae5
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 712eec8..ec7d80a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -25,6 +25,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.GestureRecorder;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 public class NotificationPanelView extends PanelView implements
@@ -76,7 +77,7 @@
         super.onLayout(changed, left, top, right, bottom);
         int keyguardBottomMargin =
                 ((MarginLayoutParams) mKeyguardStatusView.getLayoutParams()).bottomMargin;
-        mNotificationStackScroller.setTopPadding(mStatusBar.isOnKeyguard()
+        mNotificationStackScroller.setTopPadding(mStatusBar.getBarState() == StatusBarState.KEYGUARD
                 ? mKeyguardStatusView.getBottom() + keyguardBottomMargin
                 : mHeader.getBottom() + mNotificationTopPadding);
     }
@@ -107,14 +108,18 @@
     public boolean onInterceptTouchEvent(MotionEvent event) {
         // intercept for quick settings
         if (event.getAction() == MotionEvent.ACTION_DOWN) {
-            final View target = mStatusBar.isOnKeyguard() ?  mKeyguardStatusView : mHeader;
+            final View target = mStatusBar.getBarState() == StatusBarState.KEYGUARD
+                    ? mKeyguardStatusView
+                    : mHeader;
             final boolean inTarget = PhoneStatusBar.inBounds(target, event, true);
             if (inTarget && !isInSettings()) {
                 mTrackingSettings = true;
+                requestDisallowInterceptTouchEvent(true);
                 return true;
             }
             if (!inTarget && isInSettings()) {
                 mTrackingSettings = true;
+                requestDisallowInterceptTouchEvent(true);
                 return true;
             }
         }
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 0c3462c..7d8f3ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -96,13 +96,17 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DragDownHelper;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.InterceptedNotifications;
+import com.android.systemui.statusbar.NotificationActivatable;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
 import com.android.systemui.statusbar.NotificationOverflowContainer;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.DateView;
@@ -120,7 +124,8 @@
 import java.util.Collection;
 import java.util.Collections;
 
-public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
+public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
+        DragDownHelper.OnDragDownListener {
     static final String TAG = "PhoneStatusBar";
     public static final boolean DEBUG = BaseStatusBar.DEBUG;
     public static final boolean SPEW = false;
@@ -233,6 +238,7 @@
     View mNotificationPanelHeader;
     View mKeyguardStatusView;
     View mKeyguardBottomArea;
+    boolean mLeaveOpenOnKeyguardHide;
     KeyguardIndicationTextView mKeyguardIndicationTextView;
 
     // TODO: Fetch phrase from search/hotword provider.
@@ -470,6 +476,13 @@
         }
     };
 
+    private final View.OnClickListener mOverflowClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            goToLockedShade(null);
+        }
+    };
+
     @Override
     public void setZenMode(int mode) {
         super.setZenMode(mode);
@@ -619,6 +632,7 @@
                         R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false);
         mKeyguardIconOverflowContainer.setOnActivatedListener(this);
         mKeyguardCarrierLabel = mStatusBarWindow.findViewById(R.id.keyguard_carrier_text);
+        mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener);
         mStackScroller.addView(mKeyguardIconOverflowContainer);
 
         mExpandedContents = mStackScroller;
@@ -784,7 +798,9 @@
         } else if (mSettingsTracker != null && (event.getAction() == MotionEvent.ACTION_UP
                 || event.getAction() == MotionEvent.ACTION_CANCEL)) {
             final float dy = event.getY() - mSettingsDownY;
-            final FlipperButton flipper = mOnKeyguard ? mKeyguardFlipper : mHeaderFlipper;
+            final FlipperButton flipper = mState == StatusBarState.KEYGUARD
+                    ? mKeyguardFlipper
+                    : mHeaderFlipper;
             final boolean inButton = flipper.inHolderBounds(event);
             final boolean qsTap = mSettingsClosing && Math.abs(dy) < slop;
             if (!qsTap && !inButton) {
@@ -1192,7 +1208,7 @@
             }
 
             if (CLOSE_PANEL_WHEN_EMPTIED && mNotificationData.size() == 0
-                    && !mNotificationPanel.isTracking() && !mOnKeyguard) {
+                    && !mNotificationPanel.isTracking() && mState != StatusBarState.KEYGUARD) {
                 animateCollapsePanels();
             }
         }
@@ -1346,7 +1362,7 @@
             && mStackScroller.getHeight() < (mNotificationPanel.getHeight()
                     - mCarrierLabelHeight - mNotificationHeaderHeight)
             && mStackScroller.getVisibility() == View.VISIBLE
-            && !mOnKeyguard;
+            && mState != StatusBarState.KEYGUARD;
 
         if (force || mCarrierLabelVisible != makeVisible) {
             mCarrierLabelVisible = makeVisible;
@@ -2097,7 +2113,7 @@
         if (mDemoMode) return;
         int sbMode = mStatusBarMode;
         if (panelsEnabled() && (mInteractingWindows & StatusBarManager.WINDOW_STATUS_BAR) != 0
-                && !mOnKeyguard) {
+                && mState != StatusBarState.KEYGUARD) {
             // if panels are expandable, force the status bar opaque on any interaction
             sbMode = MODE_OPAQUE;
         }
@@ -2957,28 +2973,37 @@
         }
     }
 
-    public boolean isOnKeyguard() {
-        return mOnKeyguard;
+    /**
+     * @return The {@link StatusBarState} the status bar is in.
+     */
+    public int getBarState() {
+        return mState;
     }
 
     public void showKeyguard() {
-        mOnKeyguard = true;
+        setBarState(StatusBarState.KEYGUARD);
         updateKeyguardState();
         instantExpandNotificationsPanel();
+        mLeaveOpenOnKeyguardHide = false;
     }
 
     public void hideKeyguard() {
-        mOnKeyguard = false;
+        setBarState(StatusBarState.SHADE);
+        if (mLeaveOpenOnKeyguardHide) {
+            mLeaveOpenOnKeyguardHide = false;
+        } else {
+            instantCollapseNotificationPanel();
+        }
         updateKeyguardState();
-        instantCollapseNotificationPanel();
     }
 
     private void updatePublicMode() {
-        setLockscreenPublicMode(mOnKeyguard && mStatusBarKeyguardViewManager.isSecure());
+        setLockscreenPublicMode(mState == StatusBarState.KEYGUARD
+                && mStatusBarKeyguardViewManager.isSecure());
     }
 
     private void updateKeyguardState() {
-        if (mOnKeyguard) {
+        if (mState == StatusBarState.KEYGUARD) {
             if (isFlippedToSettings()) {
                 flipToNotifications(false /*animate*/);
             }
@@ -3010,17 +3035,17 @@
     }
 
     public void userActivity() {
-        if (mOnKeyguard) {
+        if (mState == StatusBarState.KEYGUARD) {
             mKeyguardViewMediatorCallback.userActivity();
         }
     }
 
     public boolean onMenuPressed() {
-        return mOnKeyguard && mStatusBarKeyguardViewManager.onMenuPressed();
+        return mState == StatusBarState.KEYGUARD && mStatusBarKeyguardViewManager.onMenuPressed();
     }
 
     public boolean onBackPressed() {
-        if (mOnKeyguard) {
+        if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
             return mStatusBarKeyguardViewManager.onBackPressed();
         } else {
             animateCollapsePanels();
@@ -3029,7 +3054,7 @@
     }
 
     private void showBouncer() {
-        if (mOnKeyguard) {
+        if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
             mStatusBarKeyguardViewManager.dismiss();
         }
     }
@@ -3065,6 +3090,14 @@
         super.onActivated(view);
     }
 
+    /**
+     * @param state The {@link StatusBarState} to set.
+     */
+    public void setBarState(int state) {
+        mState = state;
+        mStatusBarWindowManager.setStatusBarState(state);
+    }
+
     @Override
     public void onReset(View view) {
         super.onReset(view);
@@ -3072,13 +3105,13 @@
     }
 
     public void onTrackingStarted() {
-        if (mOnKeyguard) {
+        if (mState == StatusBarState.KEYGUARD) {
             mKeyguardIndicationTextView.switchIndication(R.string.keyguard_unlock);
         }
     }
 
     public void onTrackingStopped() {
-        if (mOnKeyguard) {
+        if (mState == StatusBarState.KEYGUARD) {
             mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase);
         }
     }
@@ -3092,6 +3125,56 @@
         return mNavigationBarView;
     }
 
+    // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
+
+    @Override
+    public void onDraggedDown(View startingChild) {
+        goToLockedShade(startingChild);
+    }
+
+    @Override
+    public void onReset() {
+        onReset(null);
+    }
+
+    public void onHover(View child) {
+        if (child instanceof NotificationActivatable) {
+            NotificationActivatable activatable = (NotificationActivatable) child;
+            activatable.getActivator().activate();
+            activatable.getActivator().addHotspot();
+        }
+    }
+
+    public void onThresholdReached() {
+        // TODO: Add visual hint that threshold is reached.
+    }
+
+    /**
+     * If secure with redaction: Show bouncer, go to unlocked shade.
+     *
+     * <p>If secure without redaction: Go to {@link StatusBarState#SHADE_LOCKED}.</p>
+     *
+     * <p>Otherwise go directly to unlocked shade.</p>
+     *
+     * @param expandView The view to expand after going to the shade.
+     */
+    public void goToLockedShade(View expandView) {
+        if (expandView instanceof ExpandableNotificationRow) {
+            ExpandableNotificationRow row = (ExpandableNotificationRow) expandView;
+            row.setUserExpanded(true);
+        }
+        if (isLockscreenPublicMode() && !userAllowsPrivateNotificationsInPublic(mCurrentUserId)) {
+            mLeaveOpenOnKeyguardHide = true;
+            showBouncer();
+        } else if (mStatusBarKeyguardViewManager.isSecure()) {
+            setBarState(StatusBarState.SHADE_LOCKED);
+            updateKeyguardState();
+        } else {
+            mLeaveOpenOnKeyguardHide = true;
+            mStatusBarKeyguardViewManager.dismiss();
+        }
+    }
+
     /**
      * @return a ViewGroup that spans the entire panel which contains the quick settings
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 79c63f7..c9c2867 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -29,6 +29,7 @@
 
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusBarState;
 
 public class PhoneStatusBarView extends PanelBar {
     private static final String TAG = "PhoneStatusBarView";
@@ -187,7 +188,9 @@
         if (panel == mFadingPanel && mScrimColor != 0 && ActivityManager.isHighEndGfx()
                 && mBar.mStatusBarWindow != null) {
             if (mShouldFade) {
-                int scrimColor = mBar.isOnKeyguard() ? mScrimColorKeyguard : mScrimColor;
+                int scrimColor = mBar.getBarState() == StatusBarState.KEYGUARD
+                        ? mScrimColorKeyguard
+                        : mScrimColor;
                 frac = mPanelExpandedFractionSum; // don't judge me
                 // let's start this 20% of the way down the screen
                 frac = frac * 1.2f - 0.2f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index d175d7a..a04baea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.app.ActionBar;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
@@ -28,6 +27,8 @@
 import android.view.WindowManager;
 
 import com.android.keyguard.R;
+import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.StatusBarState;
 
 /**
  * Encapsulates all logic for the status bar window state management.
@@ -137,7 +138,8 @@
     }
 
     private void applyUserActivityTimeout(State state) {
-        if (state.isKeyguardShowingAndNotOccluded()) {
+        if (state.isKeyguardShowingAndNotOccluded()
+                && state.statusBarState == StatusBarState.KEYGUARD) {
             mLp.userActivityTimeout = state.keyguardUserActivityTimeout;
         } else {
             mLp.userActivityTimeout = -1;
@@ -194,6 +196,14 @@
         apply(mCurrentState);
     }
 
+    /**
+     * @param state The {@link StatusBarState} of the status bar.
+     */
+    public void setStatusBarState(int state) {
+        mCurrentState.statusBarState = state;
+        apply(mCurrentState);
+    }
+
     private static class State {
         boolean keyguardShowing;
         boolean keyguardOccluded;
@@ -202,6 +212,11 @@
         boolean statusBarFocusable;
         long keyguardUserActivityTimeout;
 
+        /**
+         * The {@link BaseStatusBar} state from the status bar.
+         */
+        int statusBarState;
+
         private boolean isKeyguardShowingAndNotOccluded() {
             return keyguardShowing && !keyguardOccluded;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 6b5ef5a..3e5ba16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -31,16 +31,17 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.statusbar.policy.ScrollAdapter;
+import com.android.systemui.statusbar.DragDownHelper;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 
-public class StatusBarWindowView extends FrameLayout
-{
+public class StatusBarWindowView extends FrameLayout {
     public static final String TAG = "StatusBarWindowView";
     public static final boolean DEBUG = BaseStatusBar.DEBUG;
 
     private ExpandHelper mExpandHelper;
+    private DragDownHelper mDragDownHelper;
     private NotificationStackScrollLayout mStackScrollLayout;
     private NotificationPanelView mNotificationPanel;
 
@@ -75,6 +76,7 @@
                 minHeight, maxHeight);
         mExpandHelper.setEventSource(this);
         mExpandHelper.setScrollAdapter(mStackScrollLayout);
+        mDragDownHelper = new DragDownHelper(getContext(), this, mStackScrollLayout, mService);
 
         // We really need to be able to animate while window animations are going on
         // so that activities may be started asynchronously from panel animations
@@ -106,8 +108,12 @@
         boolean intercept = false;
         if (mNotificationPanel.isFullyExpanded()
                 && mStackScrollLayout.getVisibility() == View.VISIBLE
-                && !mService.isOnKeyguard()) {
+                && mService.getBarState() != StatusBarState.KEYGUARD) {
             intercept = mExpandHelper.onInterceptTouchEvent(ev);
+        } else if (mNotificationPanel.isFullyExpanded()
+                && mStackScrollLayout.getVisibility() == View.VISIBLE
+                && mService.getBarState() == StatusBarState.KEYGUARD) {
+            intercept = mDragDownHelper.onInterceptTouchEvent(ev);
         }
         if (!intercept) {
             super.onInterceptTouchEvent(ev);
@@ -124,8 +130,11 @@
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         boolean handled = false;
-        if (mNotificationPanel.isFullyExpanded()) {
+        if (mNotificationPanel.isFullyExpanded()
+                && mService.getBarState() != StatusBarState.KEYGUARD) {
             handled = mExpandHelper.onTouchEvent(ev);
+        } else if (mService.getBarState() == StatusBarState.KEYGUARD) {
+            handled = mDragDownHelper.onTouchEvent(ev);
         }
         if (!handled) {
             handled = super.onTouchEvent(ev);