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