Home | History | Annotate | Download | only in views
      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