Zoom notifications on lockscreen after tapping.

For the double tap interaction, this change introduces a new cue
that the notifications must be double-tapped: With the first tap,
the tapped notifications gets larger and the others fade out a bit.

Change-Id: Ib48ff0291aee1a5ec083b9e7ed1021bc420514cf
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index d48637d..2ea5add 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -83,7 +83,7 @@
 import java.util.Locale;
 
 public abstract class BaseStatusBar extends SystemUI implements
-        CommandQueue.Callbacks {
+        CommandQueue.Callbacks, LatestItemView.OnActivatedListener {
     public static final String TAG = "StatusBar";
     public static final boolean DEBUG = false;
     public static final boolean MULTIUSER_DEBUG = false;
@@ -169,8 +169,7 @@
     protected int mZenMode;
 
     protected boolean mOnKeyguard;
-    protected View mKeyguardIconOverflowContainer;
-    protected NotificationOverflowIconsView mOverflowIconsView;
+    protected NotificationOverflowContainer mKeyguardIconOverflowContainer;
 
     public boolean isDeviceProvisioned() {
         return mDeviceProvisioned;
@@ -882,6 +881,7 @@
         }
         entry.row = row;
         entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight);
+        entry.row.setOnActivatedListener(this);
         entry.content = content;
         entry.expanded = contentViewLocal;
         entry.expandedPublic = publicViewLocal;
@@ -1067,7 +1067,7 @@
      */
     protected void updateRowStates() {
         int maxKeyguardNotifications = getMaxKeyguardNotifications();
-        mOverflowIconsView.removeAllViews();
+        mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
         int n = mNotificationData.size();
         int visibleNotifications = 0;
         for (int i = n-1; i >= 0; i--) {
@@ -1087,7 +1087,7 @@
                     || !showOnKeyguard)) {
                 entry.row.setVisibility(View.GONE);
                 if (showOnKeyguard) {
-                    mOverflowIconsView.addNotification(entry);
+                    mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
                 }
             } else {
                 entry.row.setVisibility(View.VISIBLE);
@@ -1095,13 +1095,49 @@
             }
         }
 
-        if (mOnKeyguard && mOverflowIconsView.getChildCount() > 0) {
+        if (mOnKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) {
             mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE);
         } else {
             mKeyguardIconOverflowContainer.setVisibility(View.GONE);
         }
     }
 
+    @Override
+    public void onActivated(View view) {
+        int n = mNotificationData.size();
+        for (int i = 0; i < n; i++) {
+            NotificationData.Entry entry = mNotificationData.get(i);
+            if (entry.row.getVisibility() != View.GONE) {
+                if (view == entry.row) {
+                    entry.row.getActivator().activate();
+                } else {
+                    entry.row.getActivator().activateInverse();
+                }
+            }
+        }
+        if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
+            if (view == mKeyguardIconOverflowContainer) {
+                mKeyguardIconOverflowContainer.getActivator().activate();
+            } else {
+                mKeyguardIconOverflowContainer.getActivator().activateInverse();
+            }
+        }
+    }
+
+    @Override
+    public void onReset(View view) {
+        int n = mNotificationData.size();
+        for (int i = 0; i < n; i++) {
+            NotificationData.Entry entry = mNotificationData.get(i);
+            if (entry.row.getVisibility() != View.GONE) {
+                entry.row.getActivator().reset();
+            }
+        }
+        if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
+            mKeyguardIconOverflowContainer.getActivator().reset();
+        }
+    }
+
     private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
         return sbn.getNotification().priority >= Notification.PRIORITY_LOW;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index a9fda63..fdf4dbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -25,7 +25,8 @@
 import com.android.internal.widget.SizeAdaptiveLayout;
 import com.android.systemui.R;
 
-public class ExpandableNotificationRow extends FrameLayout {
+public class ExpandableNotificationRow extends FrameLayout
+        implements LatestItemView.OnActivatedListener {
     private int mRowMinHeight;
     private int mRowMaxHeight;
 
@@ -51,6 +52,8 @@
     private SizeAdaptiveLayout mPrivateLayout;
     private int mMaxExpandHeight;
     private boolean mMaxHeightNeedsUpdate;
+    private NotificationActivator mActivator;
+    private LatestItemView.OnActivatedListener mOnActivatedListener;
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -62,8 +65,10 @@
         mPublicLayout = (SizeAdaptiveLayout) findViewById(R.id.expandedPublic);
         mPrivateLayout = (SizeAdaptiveLayout) findViewById(R.id.expanded);
         mLatestItemView = (LatestItemView) findViewById(R.id.container);
-    }
 
+        mActivator = new NotificationActivator(this);
+        mLatestItemView.setOnActivatedListener(this);
+    }
 
     public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
         mRowMinHeight = rowMinHeight;
@@ -202,6 +207,7 @@
      */
     public void setDimmed(boolean dimmed) {
         mLatestItemView.setDimmed(dimmed);
+        mActivator.setDimmed(dimmed);
     }
 
     public int getMaxExpandHeight() {
@@ -220,6 +226,28 @@
         mLatestItemView.setLocked(locked);
     }
 
+    public void setOnActivatedListener(LatestItemView.OnActivatedListener listener) {
+        mOnActivatedListener = listener;
+    }
+
+    public NotificationActivator getActivator() {
+        return mActivator;
+    }
+
+    @Override
+    public void onActivated(View view) {
+        if (mOnActivatedListener != null) {
+            mOnActivatedListener.onActivated(this);
+        }
+    }
+
+    @Override
+    public void onReset(View view) {
+        if (mOnActivatedListener != null) {
+            mOnActivatedListener.onReset(this);
+        }
+    }
+
     /**
      * Sets the resource id for the background of this notification.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java
index 74d837d..5e90084 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar;
 
 import android.content.Context;
-import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -46,7 +45,8 @@
     private float mDownX;
     private float mDownY;
     private final float mTouchSlop;
-    private boolean mHotspotActive;
+
+    private OnActivatedListener mOnActivatedListener;
 
     public LatestItemView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -90,14 +90,17 @@
 
     private boolean handleTouchEventLocked(MotionEvent event) {
         int action = event.getActionMasked();
-        Drawable background = getBackground();
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 mDownX = event.getX();
                 mDownY = event.getY();
-                if (!mActivated) {
-                    background.setHotspot(0, event.getX(), event.getY());
-                    mHotspotActive = true;
+
+                // Call the listener tentatively directly, even if we don't know whether the user
+                // will stay within the touch slop, as the listener is implemented as a scale
+                // animation, which is cancellable without jarring effects when swiping away
+                // notifications.
+                if (mOnActivatedListener != null) {
+                    mOnActivatedListener.onActivated(this);
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
@@ -109,7 +112,7 @@
             case MotionEvent.ACTION_UP:
                 if (isWithinTouchSlop(event)) {
                     if (!mActivated) {
-                        mActivated = true;
+                        makeActive(event.getX(), event.getY());
                         postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
                     } else {
                         performClick();
@@ -128,17 +131,24 @@
         return true;
     }
 
+    private void makeActive(float x, float y) {
+        getBackground().setHotspot(0, x, y);
+        mActivated = true;
+    }
+
     /**
      * Cancels the hotspot and makes the notification inactive.
      */
     private void makeInactive() {
-        if (mHotspotActive) {
+        if (mActivated) {
             // Make sure that we clear the hotspot from the center.
-            getBackground().setHotspot(0, getWidth()/2, getHeight()/2);
+            getBackground().setHotspot(0, getWidth() / 2, getHeight() / 2);
             getBackground().removeHotspot(0);
-            mHotspotActive = false;
+            mActivated = false;
         }
-        mActivated = false;
+        if (mOnActivatedListener != null) {
+            mOnActivatedListener.onReset(this);
+        }
         removeCallbacks(mTapTimeoutRunnable);
     }
 
@@ -180,4 +190,13 @@
     private void updateBackgroundResource() {
         setBackgroundResource(mDimmed ? mDimmedBgResId : mBgResId);
     }
+
+    public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
+        mOnActivatedListener = onActivatedListener;
+    }
+
+    public interface OnActivatedListener {
+        void onActivated(View view);
+        void onReset(View view);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationActivator.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationActivator.java
new file mode 100644
index 0000000..620e457
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationActivator.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 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.statusbar;
+
+import android.content.Context;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import com.android.systemui.R;
+
+/**
+ * A helper class used by both {@link com.android.systemui.statusbar.ExpandableNotificationRow} and
+ * {@link com.android.systemui.statusbar.NotificationOverflowIconsView} to make a notification look
+ * active after tapping it once on the Keyguard.
+ */
+public class NotificationActivator {
+
+    private static final int ANIMATION_LENGTH_MS = 220;
+    private static final float INVERSE_ALPHA = 0.9f;
+    private static final float DIMMED_SCALE = 0.95f;
+
+    private final View mTargetView;
+
+    private final Interpolator mFastOutSlowInInterpolator;
+    private final Interpolator mLinearOutSlowInInterpolator;
+    private final int mTranslationZ;
+
+    public NotificationActivator(View targetView) {
+        mTargetView = targetView;
+        Context ctx = targetView.getContext();
+        mFastOutSlowInInterpolator =
+                AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_slow_in);
+        mLinearOutSlowInInterpolator =
+                AnimationUtils.loadInterpolator(ctx, android.R.interpolator.linear_out_slow_in);
+        mTranslationZ =
+                ctx.getResources().getDimensionPixelSize(R.dimen.z_distance_between_notifications);
+        mTargetView.animate().setDuration(ANIMATION_LENGTH_MS);
+    }
+
+    public void activateInverse() {
+        mTargetView.animate().withLayer().alpha(INVERSE_ALPHA);
+    }
+
+    public void activate() {
+        mTargetView.animate()
+                .setInterpolator(mLinearOutSlowInInterpolator)
+                .scaleX(1)
+                .scaleY(1)
+                .translationZBy(mTranslationZ);
+    }
+
+    public void reset() {
+        mTargetView.animate()
+                .setInterpolator(mFastOutSlowInInterpolator)
+                .scaleX(DIMMED_SCALE)
+                .scaleY(DIMMED_SCALE)
+                .translationZBy(-mTranslationZ);
+        if (mTargetView.getAlpha() != 1.0f) {
+            mTargetView.animate().withLayer().alpha(1);
+        }
+    }
+
+    public void setDimmed(boolean dimmed) {
+        if (dimmed) {
+            mTargetView.setScaleX(DIMMED_SCALE);
+            mTargetView.setScaleY(DIMMED_SCALE);
+        } else {
+            mTargetView.setScaleX(1);
+            mTargetView.setScaleY(1);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
new file mode 100644
index 0000000..be58dad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2014 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.statusbar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+/**
+ * Container view for overflowing notification icons on Keyguard.
+ */
+public class NotificationOverflowContainer extends FrameLayout
+        implements LatestItemView.OnActivatedListener {
+
+    private NotificationOverflowIconsView mIconsView;
+    private LatestItemView.OnActivatedListener mOnActivatedListener;
+    private NotificationActivator mActivator;
+
+    public NotificationOverflowContainer(Context context) {
+        super(context);
+    }
+
+    public NotificationOverflowContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public NotificationOverflowContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public NotificationOverflowContainer(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view);
+        mIconsView.setMoreText((TextView) findViewById(R.id.more_text));
+
+        LatestItemView latestItemView = (LatestItemView) findViewById(R.id.container);
+        mActivator = new NotificationActivator(this);
+        mActivator.setDimmed(true);
+        latestItemView.setOnActivatedListener(this);
+        latestItemView.setLocked(true);
+    }
+
+    public NotificationOverflowIconsView getIconsView() {
+        return mIconsView;
+    }
+
+    public void setOnActivatedListener(LatestItemView.OnActivatedListener onActivatedListener) {
+        mOnActivatedListener = onActivatedListener;
+    }
+
+    @Override
+    public void onActivated(View view) {
+        if (mOnActivatedListener != null) {
+            mOnActivatedListener.onActivated(this);
+        }
+    }
+
+    @Override
+    public void onReset(View view) {
+        if (mOnActivatedListener != null) {
+            mOnActivatedListener.onReset(this);
+        }
+    }
+
+    public NotificationActivator getActivator() {
+        return mActivator;
+    }
+}
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 ec9f3ab..1d01f91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -99,6 +99,7 @@
 import com.android.systemui.statusbar.LatestItemView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationOverflowContainer;
 import com.android.systemui.statusbar.NotificationOverflowIconsView;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarIconView;
@@ -519,13 +520,10 @@
         mStackScroller.setLongPressListener(getNotificationLongClicker());
         mStackScroller.setChildLocationsChangedListener(mOnChildLocationsChangedListener);
 
-        mKeyguardIconOverflowContainer = LayoutInflater.from(mContext).inflate(
-                R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false);
-        ((LatestItemView) mKeyguardIconOverflowContainer.findViewById(R.id.container)).setLocked(true);
-        mOverflowIconsView = (NotificationOverflowIconsView) mKeyguardIconOverflowContainer.findViewById(
-                R.id.overflow_icons_view);
-        mOverflowIconsView.setMoreText(
-                (TextView) mKeyguardIconOverflowContainer.findViewById(R.id.more_text));
+        mKeyguardIconOverflowContainer =
+                (NotificationOverflowContainer) LayoutInflater.from(mContext).inflate(
+                        R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false);
+        mKeyguardIconOverflowContainer.setOnActivatedListener(this);
         mStackScroller.addView(mKeyguardIconOverflowContainer);
 
         mExpandedContents = mStackScroller;
@@ -2879,6 +2877,12 @@
     }
 
     @Override
+    public void onActivated(View view) {
+        userActivity();
+        super.onActivated(view);
+    }
+
+    @Override
     protected int getMaxKeyguardNotifications() {
         return mKeyguardMaxNotificationCount;
     }