Initial changes for recents.

Change-Id: Ide2c202b4a5b25410f0f32bd0a81ccf817ede38f
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
new file mode 100644
index 0000000..b1d0d13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -0,0 +1,386 @@
+/*
+ * 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.animation.TimeInterpolator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import com.android.systemui.recents.Console;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskCallbacks;
+
+/** The TaskView callbacks */
+interface TaskViewCallbacks {
+    public void onTaskIconClicked(TaskView tv);
+    // public void onTaskViewReboundToTask(TaskView tv, Task t);
+}
+
+/** The task thumbnail view */
+class TaskThumbnailView extends ImageView {
+    Task mTask;
+    int mBarColor;
+
+    Path mRoundedRectClipPath = new Path();
+
+    public TaskThumbnailView(Context context) {
+        super(context);
+        setScaleType(ScaleType.FIT_XY);
+    }
+
+    /** Binds the thumbnail view to the task */
+    void rebindToTask(Task t, boolean animate) {
+        mTask = t;
+        if (t.thumbnail != null) {
+            // Update the bar color
+            if (Constants.Values.TaskView.DrawColoredTaskBars) {
+                int[] colors = {0xFFCC0C39, 0xFFE6781E, 0xFFC8CF02, 0xFF1693A7};
+                mBarColor = colors[mTask.intent.getComponent().getPackageName().length() % colors.length];
+            }
+
+            setImageBitmap(t.thumbnail);
+            if (animate) {
+                setAlpha(0f);
+                animate().alpha(1f)
+                        .setDuration(Constants.Values.TaskView.Animation.TaskDataUpdatedFadeDuration)
+                        .start();
+            }
+        }
+    }
+
+    /** Unbinds the thumbnail view from the task */
+    void unbindFromTask() {
+        mTask = null;
+        setImageDrawable(null);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        // Update the rounded rect clip path
+        RecentsConfiguration config = RecentsConfiguration.getInstance();
+        float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps);
+        mRoundedRectClipPath.reset();
+        mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()),
+                radius, radius, Path.Direction.CW);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (Constants.Values.TaskView.UseRoundedCorners) {
+            canvas.clipPath(mRoundedRectClipPath);
+        }
+
+        super.onDraw(canvas);
+
+        if (Constants.Values.TaskView.DrawColoredTaskBars) {
+            RecentsConfiguration config = RecentsConfiguration.getInstance();
+            int taskBarHeight = config.pxFromDp(Constants.Values.TaskView.TaskBarHeightDps);
+            // XXX: If we actually use this, this should be pulled out into a TextView that we
+            // inflate
+
+            // Draw the task bar
+            Rect r = new Rect();
+            Paint p = new Paint();
+            p.setAntiAlias(true);
+            p.setSubpixelText(true);
+            p.setColor(mBarColor);
+            p.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
+            canvas.drawRect(0, 0, getMeasuredWidth(), taskBarHeight, p);
+            p.setColor(0xFFffffff);
+            p.setTextSize(68);
+            p.getTextBounds("X", 0, 1, r);
+            int offset = (int) (taskBarHeight - r.height()) / 2;
+            canvas.drawText(mTask.title, offset, offset + r.height(), p);
+        }
+    }
+}
+
+/* The task icon view */
+class TaskIconView extends ImageView {
+    Task mTask;
+
+    Path mClipPath = new Path();
+    float mClipRadius;
+    Point mClipOrigin = new Point();
+    ObjectAnimator mCircularClipAnimator;
+
+    public TaskIconView(Context context) {
+        super(context);
+        mClipPath = new Path();
+        mClipRadius = 1f;
+    }
+
+    /** Binds the icon view to the task */
+    void rebindToTask(Task t, boolean animate) {
+        mTask = t;
+        if (t.icon != null) {
+            setImageDrawable(t.icon);
+            if (animate) {
+                setAlpha(0f);
+                animate().alpha(1f)
+                        .setDuration(Constants.Values.TaskView.Animation.TaskDataUpdatedFadeDuration)
+                        .start();
+            }
+        }
+    }
+
+    /** Unbinds the icon view from the task */
+    void unbindFromTask() {
+        mTask = null;
+        setImageDrawable(null);
+    }
+
+    /** Sets the circular clip radius on the icon */
+    public void setCircularClipRadius(float r) {
+        Console.log(Constants.DebugFlags.UI.Clipping, "[TaskView|setCircularClip]", "" + r);
+        mClipRadius = r;
+        invalidate();
+    }
+
+    /** Gets the circular clip radius on the icon */
+    public float getCircularClipRadius() {
+        return mClipRadius;
+    }
+
+    /** Animates the circular clip radius on the icon */
+    void animateCircularClip(boolean brNotTl, float newRadius, int duration, int startDelay,
+                             TimeInterpolator interpolator,
+                             AnimatorListenerAdapter listener) {
+        if (mCircularClipAnimator != null) {
+            mCircularClipAnimator.cancel();
+            mCircularClipAnimator.removeAllListeners();
+        }
+        if (brNotTl) {
+            mClipOrigin.set(0, 0);
+        } else {
+            mClipOrigin.set(getMeasuredWidth(), getMeasuredHeight());
+        }
+        mCircularClipAnimator = ObjectAnimator.ofFloat(this, "circularClipRadius", newRadius);
+        mCircularClipAnimator.setStartDelay(startDelay);
+        mCircularClipAnimator.setDuration(duration);
+        mCircularClipAnimator.setInterpolator(interpolator);
+        if (listener != null) {
+            mCircularClipAnimator.addListener(listener);
+        }
+        mCircularClipAnimator.start();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+        int width = getMeasuredWidth();
+        int height = getMeasuredHeight();
+        int maxSize = (int) Math.ceil(Math.sqrt(width * width + height * height));
+        mClipPath.reset();
+        mClipPath.addCircle(mClipOrigin.x, mClipOrigin.y, mClipRadius * maxSize, Path.Direction.CW);
+        canvas.clipPath(mClipPath);
+        super.onDraw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+}
+
+/* A task view */
+public class TaskView extends FrameLayout implements View.OnClickListener, TaskCallbacks {
+    Task mTask;
+    TaskThumbnailView mThumbnailView;
+    TaskIconView mIconView;
+    TaskViewCallbacks mCb;
+
+    public TaskView(Context context) {
+        super(context);
+        mThumbnailView = new TaskThumbnailView(context);
+        mIconView = new TaskIconView(context);
+        mIconView.setOnClickListener(this);
+        addView(mThumbnailView);
+        addView(mIconView);
+
+        RecentsConfiguration config = RecentsConfiguration.getInstance();
+        int barHeight = config.pxFromDp(Constants.Values.TaskView.TaskBarHeightDps);
+        int iconSize = config.pxFromDp(Constants.Values.TaskView.TaskIconSizeDps);
+        int offset = barHeight - (iconSize / 2);
+
+        // XXX: Lets keep the icon in the corner for the time being
+        offset = iconSize / 4;
+
+        /*
+        ((LayoutParams) mThumbnailView.getLayoutParams()).leftMargin = barHeight / 2;
+        ((LayoutParams) mThumbnailView.getLayoutParams()).rightMargin = barHeight / 2;
+        ((LayoutParams) mThumbnailView.getLayoutParams()).bottomMargin = barHeight;
+        */
+        ((LayoutParams) mIconView.getLayoutParams()).gravity = Gravity.END;
+        ((LayoutParams) mIconView.getLayoutParams()).width = iconSize;
+        ((LayoutParams) mIconView.getLayoutParams()).height = iconSize;
+        ((LayoutParams) mIconView.getLayoutParams()).topMargin = offset;
+        ((LayoutParams) mIconView.getLayoutParams()).rightMargin = offset;
+    }
+
+    /** Set the task and callback */
+    void bindToTask(Task t, TaskViewCallbacks cb) {
+        mTask = t;
+        mTask.setCallbacks(this);
+        mCb = cb;
+    }
+
+    /** Actually synchronizes the model data into the views */
+    void syncToTask() {
+        mThumbnailView.rebindToTask(mTask, false);
+        mIconView.rebindToTask(mTask, false);
+    }
+
+    /** Unset the task and callback */
+    void unbindFromTask() {
+        mTask.setCallbacks(null);
+        mThumbnailView.unbindFromTask();
+        mIconView.unbindFromTask();
+    }
+
+    /** Gets the task */
+    Task getTask() {
+        return mTask;
+    }
+
+    /** Synchronizes this view's properties with the task's transform */
+    void updateViewPropertiesFromTask(TaskViewTransform animateFromTransform,
+                                      TaskViewTransform transform, int duration) {
+        if (duration > 0) {
+            if (animateFromTransform != null) {
+                setTranslationY(animateFromTransform.translationY);
+                setScaleX(animateFromTransform.scale);
+                setScaleY(animateFromTransform.scale);
+            }
+            animate().translationY(transform.translationY)
+                    .scaleX(transform.scale)
+                    .scaleY(transform.scale)
+                    .setDuration(duration)
+                    .setInterpolator(new AccelerateDecelerateInterpolator())
+                    .start();
+        } else {
+            setTranslationY(transform.translationY);
+            setScaleX(transform.scale);
+            setScaleY(transform.scale);
+        }
+    }
+
+    /** Resets this view's properties */
+    void resetViewProperties() {
+        setTranslationX(0f);
+        setTranslationY(0f);
+        setScaleX(1f);
+        setScaleY(1f);
+        setAlpha(1f);
+    }
+
+    /** Animates this task view as it enters recents */
+    public void animateOnEnterRecents() {
+        mIconView.setCircularClipRadius(0f);
+        mIconView.animateCircularClip(true, 1f,
+            Constants.Values.TaskView.Animation.TaskIconCircularClipInDuration,
+            300, new AccelerateInterpolator(), null);
+    }
+
+    /** Animates this task view as it exits recents */
+    public void animateOnLeavingRecents(final Runnable r) {
+        if (Constants.Values.TaskView.AnimateFrontTaskIconOnLeavingUseClip) {
+            mIconView.animateCircularClip(false, 0f,
+                Constants.Values.TaskView.Animation.TaskIconCircularClipOutDuration, 0,
+                new DecelerateInterpolator(),
+                new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        r.run();
+                    }
+                });
+        } else {
+            mIconView.animate()
+                .alpha(0f)
+                .setDuration(Constants.Values.TaskView.Animation.TaskIconCircularClipOutDuration)
+                .setInterpolator(new DecelerateInterpolator())
+                .setListener(
+                    new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            r.run();
+                        }
+                    })
+                .start();
+        }
+    }
+
+    /** Returns the rect we want to clip (it may not be the full rect) */
+    Rect getClippingRect(Rect outRect, boolean accountForRoundedRects) {
+        getHitRect(outRect);
+        // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster
+        outRect.right = outRect.left + mThumbnailView.getRight();
+        outRect.bottom = outRect.top + mThumbnailView.getBottom();
+        // We need to shrink the next rect by the rounded corners since those are draw on
+        // top of the current view
+        if (accountForRoundedRects) {
+            RecentsConfiguration config = RecentsConfiguration.getInstance();
+            float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps);
+            outRect.inset((int) radius, (int) radius);
+        }
+        return outRect;
+    }
+
+    /** Enable the hw layers on this task view */
+    void enableHwLayers() {
+        Console.log(Constants.DebugFlags.UI.HwLayers, "[TaskView|enableHwLayers]");
+        mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+    }
+
+    /** Disable the hw layers on this task view */
+    void disableHwLayers() {
+        Console.log(Constants.DebugFlags.UI.HwLayers, "[TaskView|disableHwLayers]");
+        mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
+    }
+
+    @Override
+    public void onTaskDataChanged(Task task) {
+        Console.log(Constants.DebugFlags.App.EnableBackgroundTaskLoading,
+                "[TaskView|onTaskDataChanged]", task);
+
+        // Only update this task view if the changed task is the same as the task for this view
+        if (mTask == task) {
+            mThumbnailView.rebindToTask(mTask, true);
+            mIconView.rebindToTask(mTask, true);
+        }
+    }
+
+    @Override
+    public void onClick(View v) {
+        mCb.onTaskIconClicked(this);
+    }
+}
\ No newline at end of file