From 9f9679d91ee5f067cd5dcbf4d780a1f5b522e4ba Mon Sep 17 00:00:00 2001
From: Winson Chung <winsonc@google.com>
Date: Fri, 11 Apr 2014 16:49:09 -0700
Subject: Adding experiment for app-info pane.

Change-Id: I647de1a71a2ac82da0a4f8a24125496dd5457441
---
 packages/SystemUI/proguard.flags                   |   2 +-
 packages/SystemUI/res/layout/recents_task_view.xml |  23 ++-
 packages/SystemUI/res/values/config.xml            |   2 +
 packages/SystemUI/res/values/dimens.xml            |   3 +
 packages/SystemUI/res/values/strings.xml           |   2 +
 .../com/android/systemui/recents/Constants.java    |   5 +-
 .../android/systemui/recents/RecentsActivity.java  |  26 ++--
 .../systemui/recents/RecentsConfiguration.java     |   6 +
 .../android/systemui/recents/RecentsService.java   |   1 -
 .../systemui/recents/views/RecentsView.java        |  32 ++++
 .../systemui/recents/views/TaskInfoView.java       | 163 +++++++++++++++++++++
 .../systemui/recents/views/TaskStackView.java      | 137 +++++++++++++++--
 .../android/systemui/recents/views/TaskView.java   | 101 ++++++++++++-
 13 files changed, 464 insertions(+), 39 deletions(-)
 create mode 100644 packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java

(limited to 'packages')

diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 48d9722..da37803 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -6,7 +6,7 @@
   public void setGlowAlpha(float);
   public void setGlowScale(float);
 }
--keep class com.android.systemui.recents.views.TaskIconView {
+-keep class com.android.systemui.recents.views.TaskInfoView {
 	public void setCircularClipRadius(float);
 	public float getCircularClipRadius();
 }
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index 8297878..7f64032 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -21,6 +21,21 @@
         android:id="@+id/task_view_thumbnail"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
+    <com.android.systemui.recents.views.TaskInfoView
+    	android:id="@+id/task_view_info_pane"
+    	android:layout_width="match_parent"
+    	android:layout_height="match_parent"
+    	android:visibility="invisible"
+        android:background="#e6444444">
+        <Button
+        	android:id="@+id/task_view_app_info_button"
+        	android:layout_width="match_parent"
+        	android:layout_height="wrap_content"
+        	android:layout_marginStart="20dp"
+        	android:layout_marginEnd="20dp"
+    		android:layout_gravity="top|center_horizontal"
+        	android:text="@string/recents_app_info_button_label" />
+    </com.android.systemui.recents.views.TaskInfoView>
     <com.android.systemui.recents.views.TaskBarView
         android:id="@+id/task_view_bar"
         android:layout_width="match_parent"
@@ -31,15 +46,15 @@
             android:id="@+id/application_icon"
             android:layout_width="@dimen/recents_task_view_application_icon_size"
             android:layout_height="@dimen/recents_task_view_application_icon_size"
-            android:layout_gravity="center_vertical|left"
+            android:layout_gravity="center_vertical|start"
             android:padding="8dp" />
         <TextView
             android:id="@+id/activity_description"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical|left"
-            android:layout_marginLeft="@dimen/recents_task_view_application_icon_size"
-            android:layout_marginRight="@dimen/recents_task_view_activity_icon_size"
+            android:layout_marginStart="@dimen/recents_task_view_application_icon_size"
+            android:layout_marginEnd="@dimen/recents_task_view_activity_icon_size"
             android:textSize="24sp"
             android:textColor="#ffffffff"
             android:text="@string/recents_empty_message"
@@ -52,7 +67,7 @@
             android:id="@+id/activity_icon"
             android:layout_width="@dimen/recents_task_view_activity_icon_size"
             android:layout_height="@dimen/recents_task_view_activity_icon_size"
-            android:layout_gravity="center_vertical|right"
+            android:layout_gravity="center_vertical|end"
             android:padding="12dp"
             android:visibility="invisible" />
     </com.android.systemui.recents.views.TaskBarView>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e305d94..478c541 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -114,6 +114,8 @@
     <integer name="recents_filter_animate_new_views_min_duration">125</integer>
     <!-- The min animation duration for animating views that are newly visible. -->
     <integer name="recents_animate_task_bar_enter_duration">200</integer>
+    <!-- The animation duration for animating in the info pane. -->
+    <integer name="recents_animate_task_view_info_pane_duration">150</integer>
 
     <!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
      card. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5e7db8b..94d3541 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -239,6 +239,9 @@
     <!-- The size of the activity icon in the recents task view. -->
     <dimen name="recents_task_view_activity_icon_size">60dp</dimen>
 
+    <!-- The amount of space a user has to scroll to dismiss any info panes. -->
+    <dimen name="recents_task_stack_scroll_dismiss_info_pane_distance">50dp</dimen>
+
     <!-- Used to calculate the translation animation duration, the expected amount of movement 
          in dps over one second of time. -->
     <dimen name="recents_animation_movement_in_dps_per_second">800dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d994a5b..73e5e19 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -509,6 +509,8 @@
 
     <!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
     <string name="recents_empty_message">RECENTS</string>
+    <!-- Recents: The info panel app info button string. [CHAR LIMIT=NONE] -->
+    <string name="recents_app_info_button_label">Application Info</string>
 
 
     <!-- Glyph to be overlaid atop the battery when the level is extremely low. Do not translate. -->
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index cde17f5..64770a4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -28,8 +28,9 @@ public class Constants {
         public static class App {
             public static final boolean EnableTaskFiltering = true;
             public static final boolean EnableTaskStackClipping = false;
-            public static final boolean EnableToggleNewRecentsActivity = false;
-            // This disables the bitmap and icon caches to
+            public static final boolean EnableInfoPane = true;
+
+            // This disables the bitmap and icon caches
             public static final boolean DisableBackgroundCache = false;
             // For debugging, this enables us to create mock recents tasks
             public static final boolean EnableSystemServicesProxy = false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index f61c28c..f61c9f1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -50,11 +50,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
             String action = intent.getAction();
             Console.log(Constants.DebugFlags.App.SystemUIHandshake,
                     "[RecentsActivity|serviceBroadcast]", action, Console.AnsiRed);
-            if (action.equals(RecentsService.ACTION_FINISH_RECENTS_ACTIVITY)) {
-                if (Constants.DebugFlags.App.EnableToggleNewRecentsActivity) {
-                    finish();
-                }
-            } else if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
+            if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
                 // Try and unfilter and filtered stacks
                 if (!mRecentsView.unfilterFilteredStacks()) {
                     // If there are no filtered stacks, dismiss recents and launch the first task
@@ -190,7 +186,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
         // Register the broadcast receiver to handle messages from our service
         IntentFilter filter = new IntentFilter();
         filter.addAction(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY);
-        filter.addAction(RecentsService.ACTION_FINISH_RECENTS_ACTIVITY);
         registerReceiver(mServiceBroadcastReceiver, filter);
 
         // Register the broadcast receiver to handle messages when the screen is turned off
@@ -224,11 +219,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
                 Console.AnsiRed);
         super.onStop();
 
-        // Finish the current recents activity after we have launched a task
-        if (mTaskLaunched && Constants.DebugFlags.App.EnableToggleNewRecentsActivity) {
-            finish();
-        }
-
         mVisible = false;
         mTaskLaunched = false;
     }
@@ -250,8 +240,18 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
 
     @Override
     public void onBackPressed() {
-        if (!mRecentsView.unfilterFilteredStacks()) {
-            super.onBackPressed();
+        boolean interceptedByInfoPanelClose = false;
+
+        // Try and return from any open info panes
+        if (Constants.DebugFlags.App.EnableInfoPane) {
+            interceptedByInfoPanelClose = mRecentsView.closeOpenInfoPanes();
+        }
+
+        // If we haven't been intercepted already, then unfilter any stacks
+        if (!interceptedByInfoPanelClose) {
+            if (!mRecentsView.unfilterFilteredStacks()) {
+                super.onBackPressed();
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 8949663..9fdb5f9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -40,6 +40,8 @@ public class RecentsConfiguration {
     public int filteringCurrentViewsMinAnimDuration;
     public int filteringNewViewsMinAnimDuration;
     public int taskBarEnterAnimDuration;
+    public int taskStackScrollDismissInfoPaneDistance;
+    public int taskViewInfoPaneAnimDuration;
 
     public boolean launchedWithThumbnailAnimation;
 
@@ -81,6 +83,10 @@ public class RecentsConfiguration {
                 res.getInteger(R.integer.recents_filter_animate_new_views_min_duration);
         taskBarEnterAnimDuration =
                 res.getInteger(R.integer.recents_animate_task_bar_enter_duration);
+        taskStackScrollDismissInfoPaneDistance = res.getDimensionPixelSize(
+                R.dimen.recents_task_stack_scroll_dismiss_info_pane_distance);
+        taskViewInfoPaneAnimDuration =
+                res.getInteger(R.integer.recents_animate_task_view_info_pane_duration);
     }
 
     /** Updates the system insets */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
index f78a999..06ca9e2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
@@ -112,7 +112,6 @@ class SystemUIMessageHandler extends Handler {
 
 /* Service */
 public class RecentsService extends Service {
-    final static String ACTION_FINISH_RECENTS_ACTIVITY = "action_finish_recents_activity";
     final static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
 
     Messenger mSystemUIMessenger = new Messenger(new SystemUIMessageHandler(this));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index ec28379..b054a22 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -17,13 +17,16 @@
 package com.android.systemui.recents.views;
 
 import android.app.ActivityOptions;
+import android.app.TaskStackBuilder;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.net.Uri;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.view.View;
 import android.widget.FrameLayout;
 import com.android.systemui.recents.Console;
@@ -179,6 +182,21 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
         return true;
     }
 
+    /** Closes any open info panes */
+    public boolean closeOpenInfoPanes() {
+        if (mBSP != null) {
+            // Get the first stack view
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                TaskStackView stackView = (TaskStackView) getChildAt(i);
+                if (stackView.closeOpenInfoPanes()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /** Unfilters any filtered stacks */
     public boolean unfilterFilteredStacks() {
         if (mBSP != null) {
@@ -206,6 +224,9 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
             mCb.onTaskLaunching();
         }
 
+        // Close any open info panes
+        closeOpenInfoPanes();
+
         final Runnable launchRunnable = new Runnable() {
             @Override
             public void run() {
@@ -283,4 +304,15 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
             tv.animateOnLeavingRecents(launchRunnable);
         }
     }
+
+    @Override
+    public void onTaskAppInfoLaunched(Task t) {
+        // Create a new task stack with the application info details activity
+        Intent baseIntent = t.key.baseIntent;
+        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+                Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null));
+        intent.setComponent(intent.resolveActivity(getContext().getPackageManager()));
+        TaskStackBuilder.create(getContext())
+                .addNextIntentWithParentStack(intent).startActivities();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java
new file mode 100644
index 0000000..233e38c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java
@@ -0,0 +1,163 @@
+/*
+ * 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.recents.views;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import com.android.systemui.R;
+import com.android.systemui.recents.BakedBezierInterpolator;
+import com.android.systemui.recents.Utilities;
+
+
+/* The task info view */
+class TaskInfoView extends FrameLayout {
+
+    Button mAppInfoButton;
+
+    // Circular clip animation
+    boolean mCircularClipEnabled;
+    Path mClipPath = new Path();
+    float mClipRadius;
+    float mMaxClipRadius;
+    Point mClipOrigin = new Point();
+    ObjectAnimator mCircularClipAnimator;
+
+    public TaskInfoView(Context context) {
+        this(context, null);
+    }
+
+    public TaskInfoView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TaskInfoView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public TaskInfoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        // Initialize the buttons on the info panel
+        mAppInfoButton = (Button) findViewById(R.id.task_view_app_info_button);
+    }
+
+    /** Updates the positions of each of the items to fit in the rect specified */
+    void updateContents(Rect visibleRect) {
+        // Offset the app info button
+        LayoutParams lp = (LayoutParams) mAppInfoButton.getLayoutParams();
+        lp.topMargin = visibleRect.top +
+                (visibleRect.height() - mAppInfoButton.getMeasuredHeight()) / 2;
+        requestLayout();
+    }
+
+    /** Sets the circular clip radius on this panel */
+    public void setCircularClipRadius(float r) {
+        mClipRadius = r;
+        invalidate();
+    }
+
+    /** Gets the circular clip radius on this panel */
+    public float getCircularClipRadius() {
+        return mClipRadius;
+    }
+
+    /** Animates the circular clip radius on the icon */
+    void animateCircularClip(Point o, float fromRadius, float toRadius,
+                             final Runnable postRunnable, boolean animateInContent) {
+        if (mCircularClipAnimator != null) {
+            mCircularClipAnimator.cancel();
+        }
+
+        // Calculate the max clip radius to each of the corners
+        int w = getMeasuredWidth() - o.x;
+        int h = getMeasuredHeight() - o.y;
+        // origin to tl, tr, br, bl
+        mMaxClipRadius = (int) Math.ceil(Math.sqrt(o.x * o.x + o.y * o.y));
+        mMaxClipRadius = (int) Math.max(mMaxClipRadius, Math.ceil(Math.sqrt(w * w + o.y * o.y)));
+        mMaxClipRadius = (int) Math.max(mMaxClipRadius, Math.ceil(Math.sqrt(w * w + h * h)));
+        mMaxClipRadius = (int) Math.max(mMaxClipRadius, Math.ceil(Math.sqrt(o.x * o.x + h * h)));
+
+        mClipOrigin.set(o.x, o.y);
+        mClipRadius = fromRadius;
+        int duration = Utilities.calculateTranslationAnimationDuration((int) mMaxClipRadius);
+        mCircularClipAnimator = ObjectAnimator.ofFloat(this, "circularClipRadius", toRadius);
+        mCircularClipAnimator.setDuration(duration);
+        mCircularClipAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE);
+        mCircularClipAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mCircularClipEnabled = false;
+                if (postRunnable != null) {
+                    postRunnable.run();
+                }
+            }
+        });
+        mCircularClipAnimator.start();
+        mCircularClipEnabled = true;
+
+        if (animateInContent) {
+            animateAppInfoButtonIn(duration);
+        }
+    }
+
+    /** Cancels the circular clip animation. */
+    void cancelCircularClipAnimation() {
+        if (mCircularClipAnimator != null) {
+            mCircularClipAnimator.cancel();
+        }
+    }
+
+    void animateAppInfoButtonIn(int duration) {
+        mAppInfoButton.setScaleX(0.75f);
+        mAppInfoButton.setScaleY(0.75f);
+        mAppInfoButton.animate()
+                .scaleX(1f)
+                .scaleY(1f)
+                .setDuration(duration)
+                .setInterpolator(BakedBezierInterpolator.INSTANCE)
+                .withLayer()
+                .start();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int saveCount = 0;
+        if (mCircularClipEnabled) {
+            saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+            mClipPath.reset();
+            mClipPath.addCircle(mClipOrigin.x, mClipOrigin.y, mClipRadius * mMaxClipRadius,
+                    Path.Direction.CW);
+            canvas.clipPath(mClipPath);
+        }
+        super.draw(canvas);
+        if (mCircularClipEnabled) {
+            canvas.restoreToCount(saveCount);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 88fb972..033bd67 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -52,11 +52,12 @@ import java.util.HashMap;
 /* The visual representation of a task stack view */
 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
         TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
-        View.OnClickListener {
+        View.OnClickListener, View.OnLongClickListener {
 
     /** The TaskView callbacks */
     interface TaskStackViewCallbacks {
         public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
+        public void onTaskAppInfoLaunched(Task t);
     }
 
     TaskStack mStack;
@@ -75,6 +76,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
     int mMinScroll;
     int mMaxScroll;
     int mStashedScroll;
+    int mLastInfoPaneStackScroll;
     OverScroller mScroller;
     ObjectAnimator mScrollAnimator;
 
@@ -281,6 +283,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
     public void setStackScroll(int value) {
         mStackScroll = value;
         requestSynchronizeStackViewsWithModel();
+
+        // Close any open info panes if the user has scrolled away from them
+        boolean isAnimatingScroll = (mScrollAnimator != null && mScrollAnimator.isRunning());
+        if (mLastInfoPaneStackScroll > -1 && !isAnimatingScroll) {
+            RecentsConfiguration config = RecentsConfiguration.getInstance();
+            if (Math.abs(mStackScroll - mLastInfoPaneStackScroll) >
+                    config.taskStackScrollDismissInfoPaneDistance) {
+                // Close any open info panes
+                closeOpenInfoPanes();
+            }
+        }
     }
     /** Sets the current stack scroll without synchronizing the stack view with the model */
     public void setStackScrollRaw(int value) {
@@ -300,19 +313,24 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
             // Enable hw layers on the stack
             addHwLayersRefCount("animateBoundScroll");
 
-            // Abort any current animations
-            abortScroller();
-            abortBoundScrollAnimation();
-
             // Start a new scroll animation
-            animateScroll(curScroll, newScroll);
-            mScrollAnimator.start();
+            animateScroll(curScroll, newScroll, new Runnable() {
+                @Override
+                public void run() {
+                    // Disable hw layers on the stack
+                    decHwLayersRefCount("animateBoundScroll");
+                }
+            });
         }
         return mScrollAnimator;
     }
 
     /** Animates the stack scroll */
-    void animateScroll(int curScroll, int newScroll) {
+    void animateScroll(int curScroll, int newScroll, final Runnable postRunnable) {
+        // Abort any current animations
+        abortScroller();
+        abortBoundScrollAnimation();
+
         mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
         mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
                 curScroll, 250));
@@ -326,20 +344,23 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
         mScrollAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                // Disable hw layers on the stack
-                decHwLayersRefCount("animateBoundScroll");
+                if (postRunnable != null) {
+                    postRunnable.run();
+                }
+                mScrollAnimator.removeAllListeners();
             }
         });
+        mScrollAnimator.start();
     }
 
     /** Aborts any current stack scrolls */
     void abortBoundScrollAnimation() {
         if (mScrollAnimator != null) {
             mScrollAnimator.cancel();
-            mScrollAnimator.removeAllListeners();
         }
     }
 
+    /** Aborts the scroller and any current fling */
     void abortScroller() {
         if (!mScroller.isFinished()) {
             // Abort the scroller
@@ -407,6 +428,21 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
         }
     }
 
+    /** Closes any open info panes. */
+    boolean closeOpenInfoPanes() {
+        if (!Constants.DebugFlags.App.EnableInfoPane) return false;
+
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            TaskView tv = (TaskView) getChildAt(i);
+            if (tv.isInfoPaneVisible()) {
+                tv.hideInfoPane();
+                return true;
+            }
+        }
+        return false;
+    }
+
     /** Enables the hw layers and increments the hw layer requirement ref count */
     void addHwLayersRefCount(String reason) {
         Console.log(Constants.DebugFlags.UI.HwLayers,
@@ -644,7 +680,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
         requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
     }
 
-
     /**
      * Creates the animations for all the children views that need to be removed or to move views
      * to their un/filtered position when we are un/filtering a stack, and returns the duration
@@ -789,6 +824,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
     @Override
     public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
                                 Task filteredTask) {
+        // Close any open info panes
+        closeOpenInfoPanes();
+
         // Stash the scroll and filtered task for us to restore to when we unfilter
         mStashedScroll = getStackScroll();
 
@@ -813,6 +851,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
 
     @Override
     public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
+        // Close any open info panes
+        closeOpenInfoPanes();
+
         // Calculate the current task transforms
         final ArrayList<TaskViewTransform> curTaskTransforms =
                 getStackTransforms(curTasks, getStackScroll(), null, true);
@@ -892,6 +933,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
 
             // Set the callbacks and listeners for this new view
             tv.setOnClickListener(this);
+            if (Constants.DebugFlags.App.EnableInfoPane) {
+                tv.setOnLongClickListener(this);
+            }
             tv.setCallbacks(this);
         } else {
             attachViewToParent(tv, insertIndex, tv.getLayoutParams());
@@ -926,6 +970,24 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
         }
     }
 
+    @Override
+    public void onTaskInfoPanelShown(TaskView tv) {
+        // Do nothing
+    }
+
+    @Override
+    public void onTaskInfoPanelHidden(TaskView tv) {
+        // Unset the saved scroll
+        mLastInfoPaneStackScroll = -1;
+    }
+
+    @Override
+    public void onTaskAppInfoClicked(TaskView tv) {
+        if (mCb != null) {
+            mCb.onTaskAppInfoLaunched(tv.getTask());
+        }
+    }
+
     /**** View.OnClickListener Implementation ****/
 
     @Override
@@ -935,10 +997,51 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
         Console.log(Constants.DebugFlags.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
                 task + " cb: " + mCb);
 
+        // Close any open info panes if the user taps on another task
+        if (closeOpenInfoPanes()) {
+            return;
+        }
+
         if (mCb != null) {
             mCb.onTaskLaunched(this, tv, mStack, task);
         }
     }
+
+    @Override
+    public boolean onLongClick(View v) {
+        if (!Constants.DebugFlags.App.EnableInfoPane) return false;
+
+        TaskView tv = (TaskView) v;
+
+        // Close any other task info panels if we launch another info pane
+        closeOpenInfoPanes();
+
+        // Scroll the task view so that it is maximally visible
+        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
+        int taskIndex = mStack.indexOfTask(tv.getTask());
+        int curScroll = getStackScroll();
+        int newScroll = (int) Math.max(mMinScroll, Math.min(mMaxScroll, taskIndex * overlapHeight));
+        TaskViewTransform transform = getStackTransform(taskIndex, curScroll);
+        Rect nonOverlapRect = new Rect(transform.rect);
+        if (taskIndex < (mStack.getTaskCount() - 1)) {
+            nonOverlapRect.bottom = nonOverlapRect.top + (int) overlapHeight;
+        }
+
+        // XXX: Use HW Layers
+        if (transform.t < 0f) {
+            animateScroll(curScroll, newScroll, null);
+        } else if (nonOverlapRect.bottom > mStackRectSansPeek.bottom) {
+            // Check if we are out of bounds, if so, just scroll it in such that the bottom of the
+            // task view is visible
+            newScroll = curScroll - (mStackRectSansPeek.bottom - nonOverlapRect.bottom);
+            animateScroll(curScroll, newScroll, null);
+        }
+        mLastInfoPaneStackScroll = newScroll;
+
+        // Show the info pane for this task view
+        tv.showInfoPane(new Rect(0, 0, 0, (int) overlapHeight));
+        return true;
+    }
 }
 
 /* Handles touch events */
@@ -1153,9 +1256,10 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
                 int activePointerIndex = ev.findPointerIndex(mActivePointerId);
                 int x = (int) ev.getX(activePointerIndex);
                 int y = (int) ev.getY(activePointerIndex);
+                int yTotal = Math.abs(y - mInitialMotionY);
                 int deltaY = mLastMotionY - y;
                 if (!mIsScrolling) {
-                    if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
+                    if (yTotal > mScrollTouchSlop) {
                         mIsScrolling = true;
                         // Initialize the velocity tracker
                         initOrResetVelocityTracker();
@@ -1277,6 +1381,13 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
         if (parent != null) {
             parent.requestDisallowInterceptTouchEvent(true);
         }
+        // If the info panel is currently showing on this view, then we need to dismiss it
+        if (Constants.DebugFlags.App.EnableInfoPane) {
+            TaskView tv = (TaskView) v;
+            if (tv.isInfoPaneVisible()) {
+                tv.hideInfoPane();
+            }
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index b410012..81805d5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -17,8 +17,10 @@
 package com.android.systemui.recents.views;
 
 import android.content.Context;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.widget.FrameLayout;
 import com.android.systemui.R;
@@ -29,18 +31,26 @@ import com.android.systemui.recents.model.Task;
 
 
 /* A task view */
-public class TaskView extends FrameLayout implements View.OnClickListener, Task.TaskCallbacks {
+public class TaskView extends FrameLayout implements View.OnClickListener,
+        Task.TaskCallbacks {
     /** The TaskView callbacks */
     interface TaskViewCallbacks {
         public void onTaskIconClicked(TaskView tv);
+        public void onTaskInfoPanelShown(TaskView tv);
+        public void onTaskInfoPanelHidden(TaskView tv);
+        public void onTaskAppInfoClicked(TaskView tv);
+
         // public void onTaskViewReboundToTask(TaskView tv, Task t);
     }
 
     Task mTask;
     boolean mTaskDataLoaded;
+    boolean mTaskInfoPaneVisible;
+    Point mLastTouchDown = new Point();
 
     TaskThumbnailView mThumbnailView;
     TaskBarView mBarView;
+    TaskInfoView mInfoView;
     TaskViewCallbacks mCb;
 
 
@@ -65,12 +75,24 @@ public class TaskView extends FrameLayout implements View.OnClickListener, Task.
         // Bind the views
         mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
         mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
-        mBarView.mApplicationIcon.setOnClickListener(this);
+        mInfoView = (TaskInfoView) findViewById(R.id.task_view_info_pane);
+
         if (mTaskDataLoaded) {
             onTaskDataLoaded(false);
         }
     }
 
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_MOVE:
+                mLastTouchDown.set((int) ev.getX(), (int) ev.getY());
+                break;
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+
     /** Set callback */
     void setCallbacks(TaskViewCallbacks cb) {
         mCb = cb;
@@ -189,6 +211,63 @@ public class TaskView extends FrameLayout implements View.OnClickListener, Task.
         return outRect;
     }
 
+    /** Returns whether this task has an info pane visible */
+    boolean isInfoPaneVisible() {
+        return mTaskInfoPaneVisible;
+    }
+
+    /** Shows the info pane if it is not visible. */
+    void showInfoPane(Rect taskVisibleRect) {
+        if (mTaskInfoPaneVisible) return;
+
+        // Remove the bar view from the visible rect and update the info pane contents
+        taskVisibleRect.top += mBarView.getMeasuredHeight();
+        mInfoView.updateContents(taskVisibleRect);
+
+        // Show the info pane and animate it into view
+        mInfoView.setVisibility(View.VISIBLE);
+        mInfoView.animateCircularClip(mLastTouchDown, 0f, 1f, null, true);
+        mInfoView.setOnClickListener(this);
+        mTaskInfoPaneVisible = true;
+
+        // Notify any callbacks
+        if (mCb != null) {
+            mCb.onTaskInfoPanelShown(this);
+        }
+    }
+
+    /** Hides the info pane if it is visible. */
+    void hideInfoPane() {
+        if (!mTaskInfoPaneVisible) return;
+        RecentsConfiguration config = RecentsConfiguration.getInstance();
+
+        // Cancel any circular clip animation
+        mInfoView.cancelCircularClipAnimation();
+
+        // Animate the info pane out
+        mInfoView.animate()
+                .alpha(0f)
+                .setDuration(config.taskViewInfoPaneAnimDuration)
+                .setInterpolator(BakedBezierInterpolator.INSTANCE)
+                .withLayer()
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        mInfoView.setVisibility(View.INVISIBLE);
+                        mInfoView.setOnClickListener(null);
+
+                        mInfoView.setAlpha(1f);
+                    }
+                })
+                .start();
+        mTaskInfoPaneVisible = false;
+
+        // Notify any callbacks
+        if (mCb != null) {
+            mCb.onTaskInfoPanelHidden(this);
+        }
+    }
+
     /** Enable the hw layers on this task view */
     void enableHwLayers() {
         mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
@@ -209,27 +288,39 @@ public class TaskView extends FrameLayout implements View.OnClickListener, Task.
 
     @Override
     public void onTaskDataLoaded(boolean reloadingTaskData) {
-        if (mThumbnailView != null && mBarView != null) {
+        if (mThumbnailView != null && mBarView != null && mInfoView != null) {
             // Bind each of the views to the new task data
             mThumbnailView.rebindToTask(mTask, reloadingTaskData);
             mBarView.rebindToTask(mTask, reloadingTaskData);
+            // Rebind any listeners
+            mBarView.mApplicationIcon.setOnClickListener(this);
+            mInfoView.mAppInfoButton.setOnClickListener(this);
         }
         mTaskDataLoaded = true;
     }
 
     @Override
     public void onTaskDataUnloaded() {
-        if (mThumbnailView != null && mBarView != null) {
+        if (mThumbnailView != null && mBarView != null && mInfoView != null) {
             // Unbind each of the views from the task data and remove the task callback
             mTask.setCallbacks(null);
             mThumbnailView.unbindFromTask();
             mBarView.unbindFromTask();
+            // Unbind any listeners
+            mBarView.mApplicationIcon.setOnClickListener(null);
+            mInfoView.mAppInfoButton.setOnClickListener(null);
         }
         mTaskDataLoaded = false;
     }
 
     @Override
     public void onClick(View v) {
-        mCb.onTaskIconClicked(this);
+        if (v == mInfoView) {
+            // Do nothing
+        } else if (v == mBarView.mApplicationIcon) {
+            mCb.onTaskIconClicked(this);
+        } else if (v == mInfoView.mAppInfoButton) {
+            mCb.onTaskAppInfoClicked(this);
+        }
     }
 }
\ No newline at end of file
-- 
cgit v1.1