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.content.Context; 20 import android.content.res.Configuration; 21 import android.content.res.Resources; 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.events.EventBus; 39 import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent; 40 import com.android.systemui.recents.utilities.Utilities; 41 import com.android.systemui.shared.recents.model.Task; 42 import com.android.systemui.shared.recents.model.ThumbnailData; 43 import java.io.PrintWriter; 44 45 46 /** 47 * The task thumbnail view. It implements an image view that allows for animating the dim and 48 * alpha of the thumbnail image. 49 */ 50 public class TaskViewThumbnail extends View { 51 52 private static final ColorMatrix TMP_FILTER_COLOR_MATRIX = new ColorMatrix(); 53 private static final ColorMatrix TMP_BRIGHTNESS_COLOR_MATRIX = new ColorMatrix(); 54 55 private Task mTask; 56 57 private int mDisplayOrientation = Configuration.ORIENTATION_UNDEFINED; 58 private Rect mDisplayRect = new Rect(); 59 60 // Drawing 61 @ViewDebug.ExportedProperty(category="recents") 62 protected Rect mTaskViewRect = new Rect(); 63 @ViewDebug.ExportedProperty(category="recents") 64 protected Rect mThumbnailRect = new Rect(); 65 @ViewDebug.ExportedProperty(category="recents") 66 protected float mThumbnailScale; 67 private float mFullscreenThumbnailScale = 1f; 68 /** The height, in pixels, of the task view's title bar. */ 69 private int mTitleBarHeight; 70 private boolean mSizeToFit = false; 71 private boolean mOverlayHeaderOnThumbnailActionBar = true; 72 private ThumbnailData mThumbnailData; 73 74 protected int mCornerRadius; 75 @ViewDebug.ExportedProperty(category="recents") 76 private float mDimAlpha; 77 private Matrix mMatrix = new Matrix(); 78 private Paint mDrawPaint = new Paint(); 79 protected Paint mLockedPaint = new Paint(); 80 protected Paint mBgFillPaint = new Paint(); 81 protected BitmapShader mBitmapShader; 82 protected boolean mUserLocked = false; 83 private LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0); 84 85 // Clip the top of the thumbnail against the opaque header bar that overlaps this view 86 private View mTaskBar; 87 88 // Visibility optimization, if the thumbnail height is less than the height of the header 89 // bar for the task view, then just mark this thumbnail view as invisible 90 @ViewDebug.ExportedProperty(category="recents") 91 private boolean mInvisible; 92 93 @ViewDebug.ExportedProperty(category="recents") 94 private boolean mDisabledInSafeMode; 95 TaskViewThumbnail(Context context)96 public TaskViewThumbnail(Context context) { 97 this(context, null); 98 } 99 TaskViewThumbnail(Context context, AttributeSet attrs)100 public TaskViewThumbnail(Context context, AttributeSet attrs) { 101 this(context, attrs, 0); 102 } 103 TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr)104 public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) { 105 this(context, attrs, defStyleAttr, 0); 106 } 107 TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)108 public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 109 super(context, attrs, defStyleAttr, defStyleRes); 110 mDrawPaint.setColorFilter(mLightingColorFilter); 111 mDrawPaint.setFilterBitmap(true); 112 mDrawPaint.setAntiAlias(true); 113 Resources res = getResources(); 114 mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius); 115 mBgFillPaint.setColor(Color.WHITE); 116 mLockedPaint.setColor(Color.WHITE); 117 mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height); 118 } 119 120 /** 121 * Called when the task view frame changes, allowing us to move the contents of the header 122 * to match the frame changes. 123 */ onTaskViewSizeChanged(int width, int height)124 public void onTaskViewSizeChanged(int width, int height) { 125 // Return early if the bounds have not changed 126 if (mTaskViewRect.width() == width && mTaskViewRect.height() == height) { 127 return; 128 } 129 130 mTaskViewRect.set(0, 0, width, height); 131 setLeftTopRightBottom(0, 0, width, height); 132 updateThumbnailMatrix(); 133 } 134 135 @Override onDraw(Canvas canvas)136 protected void onDraw(Canvas canvas) { 137 if (mInvisible) { 138 return; 139 } 140 141 int viewWidth = mTaskViewRect.width(); 142 int viewHeight = mTaskViewRect.height(); 143 int thumbnailWidth = Math.min(viewWidth, 144 (int) (mThumbnailRect.width() * mThumbnailScale)); 145 int thumbnailHeight = Math.min(viewHeight, 146 (int) (mThumbnailRect.height() * mThumbnailScale)); 147 148 if (mUserLocked) { 149 canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius, 150 mLockedPaint); 151 } else if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) { 152 int topOffset = 0; 153 if (mTaskBar != null && mOverlayHeaderOnThumbnailActionBar) { 154 topOffset = mTaskBar.getHeight() - mCornerRadius; 155 } 156 157 // Draw the background, there will be some small overdraw with the thumbnail 158 if (thumbnailWidth < viewWidth) { 159 // Portrait thumbnail on a landscape task view 160 canvas.drawRoundRect(Math.max(0, thumbnailWidth - mCornerRadius), topOffset, 161 viewWidth, viewHeight, 162 mCornerRadius, mCornerRadius, mBgFillPaint); 163 } 164 if (thumbnailHeight < viewHeight) { 165 // Landscape thumbnail on a portrait task view 166 canvas.drawRoundRect(0, Math.max(topOffset, thumbnailHeight - mCornerRadius), 167 viewWidth, viewHeight, 168 mCornerRadius, mCornerRadius, mBgFillPaint); 169 } 170 171 // Draw the thumbnail 172 canvas.drawRoundRect(0, topOffset, thumbnailWidth, thumbnailHeight, 173 mCornerRadius, mCornerRadius, mDrawPaint); 174 } else { 175 canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius, 176 mBgFillPaint); 177 } 178 } 179 180 /** Sets the thumbnail to a given bitmap. */ setThumbnail(ThumbnailData thumbnailData)181 void setThumbnail(ThumbnailData thumbnailData) { 182 if (thumbnailData != null && thumbnailData.thumbnail != null) { 183 Bitmap bm = thumbnailData.thumbnail; 184 bm.prepareToDraw(); 185 mFullscreenThumbnailScale = thumbnailData.scale; 186 mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 187 mDrawPaint.setShader(mBitmapShader); 188 mThumbnailRect.set(0, 0, 189 bm.getWidth() - thumbnailData.insets.left - thumbnailData.insets.right, 190 bm.getHeight() - thumbnailData.insets.top - thumbnailData.insets.bottom); 191 mThumbnailData = thumbnailData; 192 updateThumbnailMatrix(); 193 updateThumbnailPaintFilter(); 194 } else { 195 mBitmapShader = null; 196 mDrawPaint.setShader(null); 197 mThumbnailRect.setEmpty(); 198 mThumbnailData = null; 199 } 200 } 201 202 /** Updates the paint to draw the thumbnail. */ updateThumbnailPaintFilter()203 void updateThumbnailPaintFilter() { 204 if (mInvisible) { 205 return; 206 } 207 int mul = (int) ((1.0f - mDimAlpha) * 255); 208 if (mBitmapShader != null) { 209 if (mDisabledInSafeMode) { 210 // Brightness: C-new = C-old*(1-amount) + amount 211 TMP_FILTER_COLOR_MATRIX.setSaturation(0); 212 float scale = 1f - mDimAlpha; 213 float[] mat = TMP_BRIGHTNESS_COLOR_MATRIX.getArray(); 214 mat[0] = scale; 215 mat[6] = scale; 216 mat[12] = scale; 217 mat[4] = mDimAlpha * 255f; 218 mat[9] = mDimAlpha * 255f; 219 mat[14] = mDimAlpha * 255f; 220 TMP_FILTER_COLOR_MATRIX.preConcat(TMP_BRIGHTNESS_COLOR_MATRIX); 221 ColorMatrixColorFilter filter = new ColorMatrixColorFilter(TMP_FILTER_COLOR_MATRIX); 222 mDrawPaint.setColorFilter(filter); 223 mBgFillPaint.setColorFilter(filter); 224 mLockedPaint.setColorFilter(filter); 225 } else { 226 mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul)); 227 mDrawPaint.setColorFilter(mLightingColorFilter); 228 mDrawPaint.setColor(0xFFffffff); 229 mBgFillPaint.setColorFilter(mLightingColorFilter); 230 mLockedPaint.setColorFilter(mLightingColorFilter); 231 } 232 } else { 233 int grey = mul; 234 mDrawPaint.setColorFilter(null); 235 mDrawPaint.setColor(Color.argb(255, grey, grey, grey)); 236 } 237 if (!mInvisible) { 238 invalidate(); 239 } 240 } 241 242 /** 243 * Updates the scale of the bitmap relative to this view. 244 */ updateThumbnailMatrix()245 public void updateThumbnailMatrix() { 246 mThumbnailScale = 1f; 247 if (mBitmapShader != null && mThumbnailData != null) { 248 if (mTaskViewRect.isEmpty()) { 249 // If we haven't measured , skip the thumbnail drawing and only draw the background 250 // color 251 mThumbnailScale = 0f; 252 } else if (mSizeToFit) { 253 // Make sure we fill the entire space regardless of the orientation. 254 float viewAspectRatio = (float) mTaskViewRect.width() / 255 (float) (mTaskViewRect.height() - mTitleBarHeight); 256 float thumbnailAspectRatio = 257 (float) mThumbnailRect.width() / (float) mThumbnailRect.height(); 258 if (viewAspectRatio > thumbnailAspectRatio) { 259 mThumbnailScale = 260 (float) mTaskViewRect.width() / (float) mThumbnailRect.width(); 261 } else { 262 mThumbnailScale = (float) (mTaskViewRect.height() - mTitleBarHeight) 263 / (float) mThumbnailRect.height(); 264 } 265 } else { 266 float invThumbnailScale = 1f / mFullscreenThumbnailScale; 267 if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) { 268 if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) { 269 // If we are in the same orientation as the screenshot, just scale it to the 270 // width of the task view 271 mThumbnailScale = (float) mTaskViewRect.width() / mThumbnailRect.width(); 272 } else { 273 // Scale the landscape thumbnail up to app size, then scale that to the task 274 // view size to match other portrait screenshots 275 mThumbnailScale = invThumbnailScale * 276 ((float) mTaskViewRect.width() / mDisplayRect.width()); 277 } 278 } else { 279 // Otherwise, scale the screenshot to fit 1:1 in the current orientation 280 mThumbnailScale = invThumbnailScale; 281 } 282 } 283 mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale, 284 -mThumbnailData.insets.top * mFullscreenThumbnailScale); 285 mMatrix.postScale(mThumbnailScale, mThumbnailScale); 286 mBitmapShader.setLocalMatrix(mMatrix); 287 } 288 if (!mInvisible) { 289 invalidate(); 290 } 291 } 292 293 /** Sets whether the thumbnail should be resized to fit the task view in all orientations. */ setSizeToFit(boolean flag)294 public void setSizeToFit(boolean flag) { 295 mSizeToFit = flag; 296 } 297 298 /** 299 * Sets whether the header should overlap (and hide) the action bar in the thumbnail, or 300 * be stacked just above it. 301 */ setOverlayHeaderOnThumbnailActionBar(boolean flag)302 public void setOverlayHeaderOnThumbnailActionBar(boolean flag) { 303 mOverlayHeaderOnThumbnailActionBar = flag; 304 } 305 306 /** Updates the clip rect based on the given task bar. */ updateClipToTaskBar(View taskBar)307 void updateClipToTaskBar(View taskBar) { 308 mTaskBar = taskBar; 309 invalidate(); 310 } 311 312 /** Updates the visibility of the the thumbnail. */ updateThumbnailVisibility(int clipBottom)313 void updateThumbnailVisibility(int clipBottom) { 314 boolean invisible = mTaskBar != null && (getHeight() - clipBottom) <= mTaskBar.getHeight(); 315 if (invisible != mInvisible) { 316 mInvisible = invisible; 317 if (!mInvisible) { 318 updateThumbnailPaintFilter(); 319 } 320 } 321 } 322 323 /** 324 * Sets the dim alpha, only used when we are not using hardware layers. 325 * (see RecentsConfiguration.useHardwareLayers) 326 */ setDimAlpha(float dimAlpha)327 public void setDimAlpha(float dimAlpha) { 328 mDimAlpha = dimAlpha; 329 updateThumbnailPaintFilter(); 330 } 331 332 /** 333 * Returns the {@link Paint} used to draw a task screenshot, or {@link #mLockedPaint} if the 334 * thumbnail shouldn't be drawn because it belongs to a locked user. 335 */ getDrawPaint()336 protected Paint getDrawPaint() { 337 if (mUserLocked) { 338 return mLockedPaint; 339 } 340 return mDrawPaint; 341 } 342 343 /** 344 * Binds the thumbnail view to the task. 345 */ bindToTask(Task t, boolean disabledInSafeMode, int displayOrientation, Rect displayRect)346 void bindToTask(Task t, boolean disabledInSafeMode, int displayOrientation, Rect displayRect) { 347 mTask = t; 348 mDisabledInSafeMode = disabledInSafeMode; 349 mDisplayOrientation = displayOrientation; 350 mDisplayRect.set(displayRect); 351 if (t.colorBackground != 0) { 352 mBgFillPaint.setColor(t.colorBackground); 353 } 354 if (t.colorPrimary != 0) { 355 mLockedPaint.setColor(t.colorPrimary); 356 } 357 mUserLocked = t.isLocked; 358 EventBus.getDefault().register(this); 359 } 360 361 /** 362 * Called when the bound task's data has loaded and this view should update to reflect the 363 * changes. 364 */ onTaskDataLoaded(ThumbnailData thumbnailData)365 void onTaskDataLoaded(ThumbnailData thumbnailData) { 366 setThumbnail(thumbnailData); 367 } 368 369 /** Unbinds the thumbnail view from the task */ unbindFromTask()370 void unbindFromTask() { 371 mTask = null; 372 setThumbnail(null); 373 EventBus.getDefault().unregister(this); 374 } 375 onBusEvent(TaskSnapshotChangedEvent event)376 public final void onBusEvent(TaskSnapshotChangedEvent event) { 377 if (mTask == null || event.taskId != mTask.key.id || event.thumbnailData == null 378 || event.thumbnailData.thumbnail == null) { 379 return; 380 } 381 setThumbnail(event.thumbnailData); 382 } 383 dump(String prefix, PrintWriter writer)384 public void dump(String prefix, PrintWriter writer) { 385 writer.print(prefix); writer.print("TaskViewThumbnail"); 386 writer.print(" mTaskViewRect="); writer.print(Utilities.dumpRect(mTaskViewRect)); 387 writer.print(" mThumbnailRect="); writer.print(Utilities.dumpRect(mThumbnailRect)); 388 writer.print(" mThumbnailScale="); writer.print(mThumbnailScale); 389 writer.print(" mDimAlpha="); writer.print(mDimAlpha); 390 writer.println(); 391 } 392 } 393