TaskView.java revision 863db8a4e7906826bc105f49a4596dc7b336088a
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.recents.views; 18 19import android.animation.TimeInterpolator; 20import android.animation.ValueAnimator; 21import android.content.ContentResolver; 22import android.content.Context; 23import android.graphics.Canvas; 24import android.graphics.Outline; 25import android.graphics.Path; 26import android.graphics.Point; 27import android.graphics.Rect; 28import android.graphics.RectF; 29import android.provider.Settings; 30import android.util.AttributeSet; 31import android.view.MotionEvent; 32import android.view.View; 33import android.view.animation.AccelerateInterpolator; 34import android.widget.FrameLayout; 35import com.android.systemui.R; 36import com.android.systemui.recents.Constants; 37import com.android.systemui.recents.RecentsConfiguration; 38import com.android.systemui.recents.model.Task; 39 40 41/* A task view */ 42public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.OnClickListener, 43 View.OnLongClickListener { 44 /** The TaskView callbacks */ 45 interface TaskViewCallbacks { 46 public void onTaskIconClicked(TaskView tv); 47 public void onTaskAppInfoClicked(TaskView tv); 48 public void onTaskDismissed(TaskView tv); 49 50 // public void onTaskViewReboundToTask(TaskView tv, Task t); 51 } 52 53 int mDim; 54 int mMaxDim; 55 TimeInterpolator mDimInterpolator = new AccelerateInterpolator(); 56 57 Task mTask; 58 boolean mTaskDataLoaded; 59 boolean mIsFocused; 60 Point mLastTouchDown = new Point(); 61 Path mRoundedRectClipPath = new Path(); 62 63 TaskThumbnailView mThumbnailView; 64 TaskBarView mBarView; 65 TaskViewCallbacks mCb; 66 67 68 public TaskView(Context context) { 69 this(context, null); 70 } 71 72 public TaskView(Context context, AttributeSet attrs) { 73 this(context, attrs, 0); 74 } 75 76 public TaskView(Context context, AttributeSet attrs, int defStyleAttr) { 77 this(context, attrs, defStyleAttr, 0); 78 } 79 80 public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 81 super(context, attrs, defStyleAttr, defStyleRes); 82 setWillNotDraw(false); 83 } 84 85 @Override 86 protected void onFinishInflate() { 87 RecentsConfiguration config = RecentsConfiguration.getInstance(); 88 mMaxDim = config.taskStackMaxDim; 89 90 // Bind the views 91 mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail); 92 mBarView = (TaskBarView) findViewById(R.id.task_view_bar); 93 94 if (mTaskDataLoaded) { 95 onTaskDataLoaded(false); 96 } 97 } 98 99 @Override 100 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 101 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 102 103 // Update the rounded rect clip path 104 RecentsConfiguration config = RecentsConfiguration.getInstance(); 105 float radius = config.taskViewRoundedCornerRadiusPx; 106 mRoundedRectClipPath.reset(); 107 mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()), 108 radius, radius, Path.Direction.CW); 109 110 // Update the outline 111 Outline o = new Outline(); 112 o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), radius); 113 setOutline(o); 114 } 115 116 @Override 117 public boolean onInterceptTouchEvent(MotionEvent ev) { 118 switch (ev.getAction()) { 119 case MotionEvent.ACTION_DOWN: 120 case MotionEvent.ACTION_MOVE: 121 mLastTouchDown.set((int) ev.getX(), (int) ev.getY()); 122 break; 123 } 124 return super.onInterceptTouchEvent(ev); 125 } 126 127 /** Set callback */ 128 void setCallbacks(TaskViewCallbacks cb) { 129 mCb = cb; 130 } 131 132 /** Gets the task */ 133 Task getTask() { 134 return mTask; 135 } 136 137 /** Synchronizes this view's properties with the task's transform */ 138 void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform, 139 TaskViewTransform toTransform, int duration) { 140 RecentsConfiguration config = RecentsConfiguration.getInstance(); 141 int minZ = config.taskViewTranslationZMinPx; 142 int incZ = config.taskViewTranslationZIncrementPx; 143 144 // Update the bar view 145 mBarView.updateViewPropertiesToTaskTransform(animateFromTransform, toTransform, duration); 146 147 // Update this task view 148 if (duration > 0) { 149 if (animateFromTransform != null) { 150 setTranslationY(animateFromTransform.translationY); 151 if (Constants.DebugFlags.App.EnableShadows) { 152 setTranslationZ(Math.max(minZ, minZ + (animateFromTransform.t * incZ))); 153 } 154 setScaleX(animateFromTransform.scale); 155 setScaleY(animateFromTransform.scale); 156 setAlpha(animateFromTransform.alpha); 157 } 158 if (Constants.DebugFlags.App.EnableShadows) { 159 animate().translationZ(Math.max(minZ, minZ + (toTransform.t * incZ))); 160 } 161 animate().translationY(toTransform.translationY) 162 .scaleX(toTransform.scale) 163 .scaleY(toTransform.scale) 164 .alpha(toTransform.alpha) 165 .setDuration(duration) 166 .setInterpolator(config.defaultBezierInterpolator) 167 .withLayer() 168 .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 169 @Override 170 public void onAnimationUpdate(ValueAnimator animation) { 171 updateDimOverlayFromScale(); 172 } 173 }) 174 .start(); 175 } else { 176 setTranslationY(toTransform.translationY); 177 if (Constants.DebugFlags.App.EnableShadows) { 178 setTranslationZ(Math.max(minZ, minZ + (toTransform.t * incZ))); 179 } 180 setScaleX(toTransform.scale); 181 setScaleY(toTransform.scale); 182 setAlpha(toTransform.alpha); 183 } 184 updateDimOverlayFromScale(); 185 invalidate(); 186 } 187 188 /** Resets this view's properties */ 189 void resetViewProperties() { 190 setTranslationX(0f); 191 setTranslationY(0f); 192 if (Constants.DebugFlags.App.EnableShadows) { 193 setTranslationZ(0f); 194 } 195 setScaleX(1f); 196 setScaleY(1f); 197 setAlpha(1f); 198 invalidate(); 199 } 200 201 /** 202 * When we are un/filtering, this method will set up the transform that we are animating to, 203 * in order to hide the task. 204 */ 205 void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) { 206 // Fade the view out and slide it away 207 toTransform.alpha = 0f; 208 toTransform.translationY += 200; 209 } 210 211 /** 212 * When we are un/filtering, this method will setup the transform that we are animating from, 213 * in order to show the task. 214 */ 215 void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) { 216 // Fade the view in 217 fromTransform.alpha = 0f; 218 } 219 220 /** Animates this task view as it enters recents */ 221 public void animateOnEnterRecents() { 222 RecentsConfiguration config = RecentsConfiguration.getInstance(); 223 mBarView.setAlpha(0f); 224 mBarView.animate() 225 .alpha(1f) 226 .setStartDelay(250) 227 .setInterpolator(config.defaultBezierInterpolator) 228 .setDuration(config.taskBarEnterAnimDuration) 229 .withLayer() 230 .start(); 231 } 232 233 /** Animates this task view as it exits recents */ 234 public void animateOnLeavingRecents(final Runnable r) { 235 RecentsConfiguration config = RecentsConfiguration.getInstance(); 236 mBarView.animate() 237 .alpha(0f) 238 .setStartDelay(0) 239 .setInterpolator(config.defaultBezierInterpolator) 240 .setDuration(config.taskBarExitAnimDuration) 241 .withLayer() 242 .withEndAction(new Runnable() { 243 @Override 244 public void run() { 245 post(r); 246 } 247 }) 248 .start(); 249 } 250 251 /** Animates the deletion of this task view */ 252 public void animateRemoval(final Runnable r) { 253 RecentsConfiguration config = RecentsConfiguration.getInstance(); 254 animate().translationX(config.taskViewRemoveAnimTranslationXPx) 255 .alpha(0f) 256 .setStartDelay(0) 257 .setInterpolator(config.defaultBezierInterpolator) 258 .setDuration(config.taskViewRemoveAnimDuration) 259 .withLayer() 260 .withEndAction(new Runnable() { 261 @Override 262 public void run() { 263 post(r); 264 } 265 }) 266 .start(); 267 } 268 269 /** Returns the rect we want to clip (it may not be the full rect) */ 270 Rect getClippingRect(Rect outRect) { 271 getHitRect(outRect); 272 // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster 273 outRect.right = outRect.left + mThumbnailView.getRight(); 274 outRect.bottom = outRect.top + mThumbnailView.getBottom(); 275 return outRect; 276 } 277 278 /** Enable the hw layers on this task view */ 279 void enableHwLayers() { 280 mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 281 } 282 283 /** Disable the hw layers on this task view */ 284 void disableHwLayers() { 285 mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null); 286 } 287 288 /** Update the dim as a function of the scale of this view. */ 289 void updateDimOverlayFromScale() { 290 float minScale = Constants.Values.TaskStackView.StackPeekMinScale; 291 float scaleRange = 1f - minScale; 292 float dim = (1f - getScaleX()) / scaleRange; 293 dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f)); 294 mDim = Math.max(0, Math.min(mMaxDim, (int) (dim * 255))); 295 invalidate(); 296 } 297 298 @Override 299 public void draw(Canvas canvas) { 300 // Apply the rounded rect clip path on the whole view 301 canvas.clipPath(mRoundedRectClipPath); 302 303 super.draw(canvas); 304 305 // Apply the dim if necessary 306 if (mDim > 0) { 307 canvas.drawColor(mDim << 24); 308 } 309 } 310 311 /** 312 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen 313 * if the view is not currently visible, or we are in touch state (where we still want to keep 314 * track of focus). 315 */ 316 public void setFocusedTask() { 317 mIsFocused = true; 318 requestFocus(); 319 } 320 321 /** 322 * Updates the explicitly focused state when the view focus changes. 323 */ 324 @Override 325 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 326 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 327 if (!gainFocus) { 328 mIsFocused = false; 329 } 330 } 331 332 /** 333 * Returns whether we have explicitly been focused. 334 */ 335 public boolean isFocusedTask() { 336 return mIsFocused || isFocused(); 337 } 338 339 /**** TaskCallbacks Implementation ****/ 340 341 /** Binds this task view to the task */ 342 public void onTaskBound(Task t) { 343 mTask = t; 344 mTask.setCallbacks(this); 345 } 346 347 @Override 348 public void onTaskDataLoaded(boolean reloadingTaskData) { 349 if (mThumbnailView != null && mBarView != null) { 350 // Bind each of the views to the new task data 351 mThumbnailView.rebindToTask(mTask, reloadingTaskData); 352 mBarView.rebindToTask(mTask, reloadingTaskData); 353 // Rebind any listeners 354 mBarView.mApplicationIcon.setOnClickListener(this); 355 mBarView.mDismissButton.setOnClickListener(this); 356 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 357 ContentResolver cr = getContext().getContentResolver(); 358 boolean devOptsEnabled = Settings.Global.getInt(cr, 359 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; 360 if (devOptsEnabled) { 361 mBarView.mApplicationIcon.setOnLongClickListener(this); 362 } 363 } 364 } 365 mTaskDataLoaded = true; 366 } 367 368 @Override 369 public void onTaskDataUnloaded() { 370 if (mThumbnailView != null && mBarView != null) { 371 // Unbind each of the views from the task data and remove the task callback 372 mTask.setCallbacks(null); 373 mThumbnailView.unbindFromTask(); 374 mBarView.unbindFromTask(); 375 // Unbind any listeners 376 mBarView.mApplicationIcon.setOnClickListener(null); 377 mBarView.mDismissButton.setOnClickListener(null); 378 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 379 mBarView.mApplicationIcon.setOnLongClickListener(null); 380 } 381 } 382 mTaskDataLoaded = false; 383 } 384 385 @Override 386 public void onClick(View v) { 387 if (v == mBarView.mApplicationIcon) { 388 mCb.onTaskIconClicked(this); 389 } else if (v == mBarView.mDismissButton) { 390 // Animate out the view and call the callback 391 final TaskView tv = this; 392 animateRemoval(new Runnable() { 393 @Override 394 public void run() { 395 mCb.onTaskDismissed(tv); 396 } 397 }); 398 } 399 } 400 401 @Override 402 public boolean onLongClick(View v) { 403 if (v == mBarView.mApplicationIcon) { 404 mCb.onTaskAppInfoClicked(this); 405 return true; 406 } 407 return false; 408 } 409}