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 17 package com.android.systemui.recents.views; 18 19 import android.app.ActivityManager; 20 import android.content.Context; 21 import android.content.res.Configuration; 22 import android.graphics.Bitmap; 23 import android.graphics.BitmapShader; 24 import android.graphics.Canvas; 25 import android.graphics.Color; 26 import android.graphics.ColorMatrix; 27 import android.graphics.ColorMatrixColorFilter; 28 import android.graphics.LightingColorFilter; 29 import android.graphics.Matrix; 30 import android.graphics.Paint; 31 import android.graphics.Rect; 32 import android.graphics.Shader; 33 import android.util.AttributeSet; 34 import android.view.View; 35 import android.view.ViewDebug; 36 37 import com.android.systemui.R; 38 import com.android.systemui.recents.model.Task; 39 40 41 /** 42 * The task thumbnail view. It implements an image view that allows for animating the dim and 43 * alpha of the thumbnail image. 44 */ 45 public class TaskViewThumbnail extends View { 46 47 private static final ColorMatrix TMP_FILTER_COLOR_MATRIX = new ColorMatrix(); 48 private static final ColorMatrix TMP_BRIGHTNESS_COLOR_MATRIX = new ColorMatrix(); 49 50 private Task mTask; 51 52 private int mDisplayOrientation = Configuration.ORIENTATION_UNDEFINED; 53 private Rect mDisplayRect = new Rect(); 54 55 // Drawing 56 @ViewDebug.ExportedProperty(category="recents") 57 private Rect mTaskViewRect = new Rect(); 58 @ViewDebug.ExportedProperty(category="recents") 59 private Rect mThumbnailRect = new Rect(); 60 @ViewDebug.ExportedProperty(category="recents") 61 private float mThumbnailScale; 62 private float mFullscreenThumbnailScale; 63 private ActivityManager.TaskThumbnailInfo mThumbnailInfo; 64 65 private int mCornerRadius; 66 @ViewDebug.ExportedProperty(category="recents") 67 private float mDimAlpha; 68 private Matrix mScaleMatrix = new Matrix(); 69 private Paint mDrawPaint = new Paint(); 70 private Paint mBgFillPaint = new Paint(); 71 private BitmapShader mBitmapShader; 72 private LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0); 73 74 // Clip the top of the thumbnail against the opaque header bar that overlaps this view 75 private View mTaskBar; 76 77 // Visibility optimization, if the thumbnail height is less than the height of the header 78 // bar for the task view, then just mark this thumbnail view as invisible 79 @ViewDebug.ExportedProperty(category="recents") 80 private boolean mInvisible; 81 82 @ViewDebug.ExportedProperty(category="recents") 83 private boolean mDisabledInSafeMode; 84 85 public TaskViewThumbnail(Context context) { 86 this(context, null); 87 } 88 89 public TaskViewThumbnail(Context context, AttributeSet attrs) { 90 this(context, attrs, 0); 91 } 92 93 public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) { 94 this(context, attrs, defStyleAttr, 0); 95 } 96 97 public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 98 super(context, attrs, defStyleAttr, defStyleRes); 99 mDrawPaint.setColorFilter(mLightingColorFilter); 100 mDrawPaint.setFilterBitmap(true); 101 mDrawPaint.setAntiAlias(true); 102 mCornerRadius = getResources().getDimensionPixelSize( 103 R.dimen.recents_task_view_rounded_corners_radius); 104 mBgFillPaint.setColor(Color.WHITE); 105 mFullscreenThumbnailScale = context.getResources().getFraction( 106 com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1); 107 } 108 109 /** 110 * Called when the task view frame changes, allowing us to move the contents of the header 111 * to match the frame changes. 112 */ 113 public void onTaskViewSizeChanged(int width, int height) { 114 // Return early if the bounds have not changed 115 if (mTaskViewRect.width() == width && mTaskViewRect.height() == height) { 116 return; 117 } 118 119 mTaskViewRect.set(0, 0, width, height); 120 setLeftTopRightBottom(0, 0, width, height); 121 updateThumbnailScale(); 122 } 123 124 @Override 125 protected void onDraw(Canvas canvas) { 126 if (mInvisible) { 127 return; 128 } 129 130 int viewWidth = mTaskViewRect.width(); 131 int viewHeight = mTaskViewRect.height(); 132 int thumbnailWidth = Math.min(viewWidth, 133 (int) (mThumbnailRect.width() * mThumbnailScale)); 134 int thumbnailHeight = Math.min(viewHeight, 135 (int) (mThumbnailRect.height() * mThumbnailScale)); 136 if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) { 137 int topOffset = mTaskBar != null 138 ? mTaskBar.getHeight() - mCornerRadius 139 : 0; 140 141 // Draw the background, there will be some small overdraw with the thumbnail 142 if (thumbnailWidth < viewWidth) { 143 // Portrait thumbnail on a landscape task view 144 canvas.drawRoundRect(Math.max(0, thumbnailWidth - mCornerRadius), topOffset, 145 viewWidth, viewHeight, 146 mCornerRadius, mCornerRadius, mBgFillPaint); 147 } 148 if (thumbnailHeight < viewHeight) { 149 // Landscape thumbnail on a portrait task view 150 canvas.drawRoundRect(0, Math.max(topOffset, thumbnailHeight - mCornerRadius), 151 viewWidth, viewHeight, 152 mCornerRadius, mCornerRadius, mBgFillPaint); 153 } 154 155 // Draw the thumbnail 156 canvas.drawRoundRect(0, topOffset, thumbnailWidth, thumbnailHeight, 157 mCornerRadius, mCornerRadius, mDrawPaint); 158 } else { 159 canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius, 160 mBgFillPaint); 161 } 162 } 163 164 /** Sets the thumbnail to a given bitmap. */ 165 void setThumbnail(Bitmap bm, ActivityManager.TaskThumbnailInfo thumbnailInfo) { 166 if (bm != null) { 167 bm.prepareToDraw(); 168 mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 169 mDrawPaint.setShader(mBitmapShader); 170 mThumbnailRect.set(0, 0, bm.getWidth(), bm.getHeight()); 171 mThumbnailInfo = thumbnailInfo; 172 updateThumbnailScale(); 173 } else { 174 mBitmapShader = null; 175 mDrawPaint.setShader(null); 176 mThumbnailRect.setEmpty(); 177 mThumbnailInfo = null; 178 } 179 } 180 181 /** Updates the paint to draw the thumbnail. */ 182 void updateThumbnailPaintFilter() { 183 if (mInvisible) { 184 return; 185 } 186 int mul = (int) ((1.0f - mDimAlpha) * 255); 187 if (mBitmapShader != null) { 188 if (mDisabledInSafeMode) { 189 // Brightness: C-new = C-old*(1-amount) + amount 190 TMP_FILTER_COLOR_MATRIX.setSaturation(0); 191 float scale = 1f - mDimAlpha; 192 float[] mat = TMP_BRIGHTNESS_COLOR_MATRIX.getArray(); 193 mat[0] = scale; 194 mat[6] = scale; 195 mat[12] = scale; 196 mat[4] = mDimAlpha * 255f; 197 mat[9] = mDimAlpha * 255f; 198 mat[14] = mDimAlpha * 255f; 199 TMP_FILTER_COLOR_MATRIX.preConcat(TMP_BRIGHTNESS_COLOR_MATRIX); 200 ColorMatrixColorFilter filter = new ColorMatrixColorFilter(TMP_FILTER_COLOR_MATRIX); 201 mDrawPaint.setColorFilter(filter); 202 mBgFillPaint.setColorFilter(filter); 203 } else { 204 mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul)); 205 mDrawPaint.setColorFilter(mLightingColorFilter); 206 mDrawPaint.setColor(0xFFffffff); 207 mBgFillPaint.setColorFilter(mLightingColorFilter); 208 } 209 } else { 210 int grey = mul; 211 mDrawPaint.setColorFilter(null); 212 mDrawPaint.setColor(Color.argb(255, grey, grey, grey)); 213 } 214 if (!mInvisible) { 215 invalidate(); 216 } 217 } 218 219 /** 220 * Updates the scale of the bitmap relative to this view. 221 */ 222 public void updateThumbnailScale() { 223 mThumbnailScale = 1f; 224 if (mBitmapShader != null) { 225 // We consider this a stack task if it is not freeform (ie. has no bounds) or has been 226 // dragged into the stack from the freeform workspace 227 boolean isStackTask = !mTask.isFreeformTask() || mTask.bounds == null; 228 if (mTaskViewRect.isEmpty() || mThumbnailInfo == null || 229 mThumbnailInfo.taskWidth == 0 || mThumbnailInfo.taskHeight == 0) { 230 // If we haven't measured or the thumbnail is invalid, skip the thumbnail drawing 231 // and only draw the background color 232 mThumbnailScale = 0f; 233 } else if (isStackTask) { 234 float invThumbnailScale = 1f / mFullscreenThumbnailScale; 235 if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) { 236 if (mThumbnailInfo.screenOrientation == Configuration.ORIENTATION_PORTRAIT) { 237 // If we are in the same orientation as the screenshot, just scale it to the 238 // width of the task view 239 mThumbnailScale = (float) mTaskViewRect.width() / mThumbnailRect.width(); 240 } else { 241 // Scale the landscape thumbnail up to app size, then scale that to the task 242 // view size to match other portrait screenshots 243 mThumbnailScale = invThumbnailScale * 244 ((float) mTaskViewRect.width() / mDisplayRect.width()); 245 } 246 } else { 247 // Otherwise, scale the screenshot to fit 1:1 in the current orientation 248 mThumbnailScale = invThumbnailScale; 249 } 250 } else { 251 // Otherwise, if this is a freeform task with task bounds, then scale the thumbnail 252 // to fit the entire bitmap into the task bounds 253 mThumbnailScale = Math.min( 254 (float) mTaskViewRect.width() / mThumbnailRect.width(), 255 (float) mTaskViewRect.height() / mThumbnailRect.height()); 256 } 257 mScaleMatrix.setScale(mThumbnailScale, mThumbnailScale); 258 mBitmapShader.setLocalMatrix(mScaleMatrix); 259 } 260 if (!mInvisible) { 261 invalidate(); 262 } 263 } 264 265 /** Updates the clip rect based on the given task bar. */ 266 void updateClipToTaskBar(View taskBar) { 267 mTaskBar = taskBar; 268 invalidate(); 269 } 270 271 /** Updates the visibility of the the thumbnail. */ 272 void updateThumbnailVisibility(int clipBottom) { 273 boolean invisible = mTaskBar != null && (getHeight() - clipBottom) <= mTaskBar.getHeight(); 274 if (invisible != mInvisible) { 275 mInvisible = invisible; 276 if (!mInvisible) { 277 updateThumbnailPaintFilter(); 278 } 279 } 280 } 281 282 /** 283 * Sets the dim alpha, only used when we are not using hardware layers. 284 * (see RecentsConfiguration.useHardwareLayers) 285 */ 286 public void setDimAlpha(float dimAlpha) { 287 mDimAlpha = dimAlpha; 288 updateThumbnailPaintFilter(); 289 } 290 291 /** 292 * Binds the thumbnail view to the task. 293 */ 294 void bindToTask(Task t, boolean disabledInSafeMode, int displayOrientation, Rect displayRect) { 295 mTask = t; 296 mDisabledInSafeMode = disabledInSafeMode; 297 mDisplayOrientation = displayOrientation; 298 mDisplayRect.set(displayRect); 299 if (t.colorBackground != 0) { 300 mBgFillPaint.setColor(t.colorBackground); 301 } 302 } 303 304 /** 305 * Called when the bound task's data has loaded and this view should update to reflect the 306 * changes. 307 */ 308 void onTaskDataLoaded(ActivityManager.TaskThumbnailInfo thumbnailInfo) { 309 if (mTask.thumbnail != null) { 310 setThumbnail(mTask.thumbnail, thumbnailInfo); 311 } else { 312 setThumbnail(null, null); 313 } 314 } 315 316 /** Unbinds the thumbnail view from the task */ 317 void unbindFromTask() { 318 mTask = null; 319 setThumbnail(null, null); 320 } 321 } 322