blob: b1d0d1360cb4080fea0d5323799a5579d0d63198 [file] [log] [blame]
/*
* 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);
}
}