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 TaskViewThumbnail(Context context)85 public TaskViewThumbnail(Context context) { 86 this(context, null); 87 } 88 TaskViewThumbnail(Context context, AttributeSet attrs)89 public TaskViewThumbnail(Context context, AttributeSet attrs) { 90 this(context, attrs, 0); 91 } 92 TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr)93 public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) { 94 this(context, attrs, defStyleAttr, 0); 95 } 96 TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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 */ onTaskViewSizeChanged(int width, int height)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 onDraw(Canvas canvas)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. */ setThumbnail(Bitmap bm, ActivityManager.TaskThumbnailInfo thumbnailInfo)165 void setThumbnail(Bitmap bm, ActivityManager.TaskThumbnailInfo thumbnailInfo) { 166 if (bm != null) { 167 mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 168 mDrawPaint.setShader(mBitmapShader); 169 mThumbnailRect.set(0, 0, bm.getWidth(), bm.getHeight()); 170 mThumbnailInfo = thumbnailInfo; 171 updateThumbnailScale(); 172 } else { 173 mBitmapShader = null; 174 mDrawPaint.setShader(null); 175 mThumbnailRect.setEmpty(); 176 mThumbnailInfo = null; 177 } 178 } 179 180 /** Updates the paint to draw the thumbnail. */ updateThumbnailPaintFilter()181 void updateThumbnailPaintFilter() { 182 if (mInvisible) { 183 return; 184 } 185 int mul = (int) ((1.0f - mDimAlpha) * 255); 186 if (mBitmapShader != null) { 187 if (mDisabledInSafeMode) { 188 // Brightness: C-new = C-old*(1-amount) + amount 189 TMP_FILTER_COLOR_MATRIX.setSaturation(0); 190 float scale = 1f - mDimAlpha; 191 float[] mat = TMP_BRIGHTNESS_COLOR_MATRIX.getArray(); 192 mat[0] = scale; 193 mat[6] = scale; 194 mat[12] = scale; 195 mat[4] = mDimAlpha * 255f; 196 mat[9] = mDimAlpha * 255f; 197 mat[14] = mDimAlpha * 255f; 198 TMP_FILTER_COLOR_MATRIX.preConcat(TMP_BRIGHTNESS_COLOR_MATRIX); 199 ColorMatrixColorFilter filter = new ColorMatrixColorFilter(TMP_FILTER_COLOR_MATRIX); 200 mDrawPaint.setColorFilter(filter); 201 mBgFillPaint.setColorFilter(filter); 202 } else { 203 mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul)); 204 mDrawPaint.setColorFilter(mLightingColorFilter); 205 mDrawPaint.setColor(0xFFffffff); 206 mBgFillPaint.setColorFilter(mLightingColorFilter); 207 } 208 } else { 209 int grey = mul; 210 mDrawPaint.setColorFilter(null); 211 mDrawPaint.setColor(Color.argb(255, grey, grey, grey)); 212 } 213 if (!mInvisible) { 214 invalidate(); 215 } 216 } 217 218 /** 219 * Updates the scale of the bitmap relative to this view. 220 */ updateThumbnailScale()221 public void updateThumbnailScale() { 222 mThumbnailScale = 1f; 223 if (mBitmapShader != null) { 224 // We consider this a stack task if it is not freeform (ie. has no bounds) or has been 225 // dragged into the stack from the freeform workspace 226 boolean isStackTask = !mTask.isFreeformTask() || mTask.bounds == null; 227 if (mTaskViewRect.isEmpty() || mThumbnailInfo == null || 228 mThumbnailInfo.taskWidth == 0 || mThumbnailInfo.taskHeight == 0) { 229 // If we haven't measured or the thumbnail is invalid, skip the thumbnail drawing 230 // and only draw the background color 231 mThumbnailScale = 0f; 232 } else if (isStackTask) { 233 float invThumbnailScale = 1f / mFullscreenThumbnailScale; 234 if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) { 235 if (mThumbnailInfo.screenOrientation == Configuration.ORIENTATION_PORTRAIT) { 236 // If we are in the same orientation as the screenshot, just scale it to the 237 // width of the task view 238 mThumbnailScale = (float) mTaskViewRect.width() / mThumbnailRect.width(); 239 } else { 240 // Scale the landscape thumbnail up to app size, then scale that to the task 241 // view size to match other portrait screenshots 242 mThumbnailScale = invThumbnailScale * 243 ((float) mTaskViewRect.width() / mDisplayRect.width()); 244 } 245 } else { 246 // Otherwise, scale the screenshot to fit 1:1 in the current orientation 247 mThumbnailScale = invThumbnailScale; 248 } 249 } else { 250 // Otherwise, if this is a freeform task with task bounds, then scale the thumbnail 251 // to fit the entire bitmap into the task bounds 252 mThumbnailScale = Math.min( 253 (float) mTaskViewRect.width() / mThumbnailRect.width(), 254 (float) mTaskViewRect.height() / mThumbnailRect.height()); 255 } 256 mScaleMatrix.setScale(mThumbnailScale, mThumbnailScale); 257 mBitmapShader.setLocalMatrix(mScaleMatrix); 258 } 259 if (!mInvisible) { 260 invalidate(); 261 } 262 } 263 264 /** Updates the clip rect based on the given task bar. */ updateClipToTaskBar(View taskBar)265 void updateClipToTaskBar(View taskBar) { 266 mTaskBar = taskBar; 267 invalidate(); 268 } 269 270 /** Updates the visibility of the the thumbnail. */ updateThumbnailVisibility(int clipBottom)271 void updateThumbnailVisibility(int clipBottom) { 272 boolean invisible = mTaskBar != null && (getHeight() - clipBottom) <= mTaskBar.getHeight(); 273 if (invisible != mInvisible) { 274 mInvisible = invisible; 275 if (!mInvisible) { 276 updateThumbnailPaintFilter(); 277 } 278 } 279 } 280 281 /** 282 * Sets the dim alpha, only used when we are not using hardware layers. 283 * (see RecentsConfiguration.useHardwareLayers) 284 */ setDimAlpha(float dimAlpha)285 public void setDimAlpha(float dimAlpha) { 286 mDimAlpha = dimAlpha; 287 updateThumbnailPaintFilter(); 288 } 289 290 /** 291 * Binds the thumbnail view to the task. 292 */ bindToTask(Task t, boolean disabledInSafeMode, int displayOrientation, Rect displayRect)293 void bindToTask(Task t, boolean disabledInSafeMode, int displayOrientation, Rect displayRect) { 294 mTask = t; 295 mDisabledInSafeMode = disabledInSafeMode; 296 mDisplayOrientation = displayOrientation; 297 mDisplayRect.set(displayRect); 298 if (t.colorBackground != 0) { 299 mBgFillPaint.setColor(t.colorBackground); 300 } 301 } 302 303 /** 304 * Called when the bound task's data has loaded and this view should update to reflect the 305 * changes. 306 */ onTaskDataLoaded(ActivityManager.TaskThumbnailInfo thumbnailInfo)307 void onTaskDataLoaded(ActivityManager.TaskThumbnailInfo thumbnailInfo) { 308 if (mTask.thumbnail != null) { 309 setThumbnail(mTask.thumbnail, thumbnailInfo); 310 } else { 311 setThumbnail(null, null); 312 } 313 } 314 315 /** Unbinds the thumbnail view from the task */ unbindFromTask()316 void unbindFromTask() { 317 mTask = null; 318 setThumbnail(null, null); 319 } 320 } 321