Home | History | Annotate | Download | only in views
      1 /*
      2  * Copyright (C) 2017 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.quickstep.views;
     18 
     19 import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
     20 
     21 import android.content.Context;
     22 import android.content.res.Configuration;
     23 import android.graphics.Bitmap;
     24 import android.graphics.BitmapShader;
     25 import android.graphics.Canvas;
     26 import android.graphics.Color;
     27 import android.graphics.LightingColorFilter;
     28 import android.graphics.Matrix;
     29 import android.graphics.Paint;
     30 import android.graphics.Rect;
     31 import android.graphics.Shader;
     32 import android.support.v4.graphics.ColorUtils;
     33 import android.util.AttributeSet;
     34 import android.util.FloatProperty;
     35 import android.util.Property;
     36 import android.view.View;
     37 
     38 import com.android.launcher3.BaseActivity;
     39 import com.android.launcher3.DeviceProfile;
     40 import com.android.launcher3.R;
     41 import com.android.launcher3.Utilities;
     42 import com.android.launcher3.config.FeatureFlags;
     43 import com.android.launcher3.util.SystemUiController;
     44 import com.android.launcher3.util.Themes;
     45 import com.android.quickstep.TaskOverlayFactory;
     46 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
     47 import com.android.systemui.shared.recents.model.Task;
     48 import com.android.systemui.shared.recents.model.ThumbnailData;
     49 
     50 /**
     51  * A task in the Recents view.
     52  */
     53 public class TaskThumbnailView extends View {
     54 
     55     private static final LightingColorFilter[] sDimFilterCache = new LightingColorFilter[256];
     56     private static final LightingColorFilter[] sHighlightFilterCache = new LightingColorFilter[256];
     57 
     58     public static final Property<TaskThumbnailView, Float> DIM_ALPHA_MULTIPLIER =
     59             new FloatProperty<TaskThumbnailView>("dimAlphaMultiplier") {
     60                 @Override
     61                 public void setValue(TaskThumbnailView thumbnail, float dimAlphaMultiplier) {
     62                     thumbnail.setDimAlphaMultipler(dimAlphaMultiplier);
     63                 }
     64 
     65                 @Override
     66                 public Float get(TaskThumbnailView thumbnailView) {
     67                     return thumbnailView.mDimAlphaMultiplier;
     68                 }
     69             };
     70 
     71     private final float mCornerRadius;
     72 
     73     private final BaseActivity mActivity;
     74     private final TaskOverlay mOverlay;
     75     private final boolean mIsDarkTextTheme;
     76     private final Paint mPaint = new Paint();
     77     private final Paint mBackgroundPaint = new Paint();
     78 
     79     private final Matrix mMatrix = new Matrix();
     80 
     81     private float mClipBottom = -1;
     82 
     83     private Task mTask;
     84     private ThumbnailData mThumbnailData;
     85     protected BitmapShader mBitmapShader;
     86 
     87     private float mDimAlpha = 1f;
     88     private float mDimAlphaMultiplier = 1f;
     89 
     90     public TaskThumbnailView(Context context) {
     91         this(context, null);
     92     }
     93 
     94     public TaskThumbnailView(Context context, AttributeSet attrs) {
     95         this(context, attrs, 0);
     96     }
     97 
     98     public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
     99         super(context, attrs, defStyleAttr);
    100         mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
    101         mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
    102         mPaint.setFilterBitmap(true);
    103         mBackgroundPaint.setColor(Color.WHITE);
    104         mActivity = BaseActivity.fromContext(context);
    105         mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
    106     }
    107 
    108     public void bind() {
    109         mOverlay.reset();
    110     }
    111 
    112     /**
    113      * Updates this thumbnail.
    114      */
    115     public void setThumbnail(Task task, ThumbnailData thumbnailData) {
    116         mTask = task;
    117         int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
    118         mPaint.setColor(color);
    119         mBackgroundPaint.setColor(color);
    120 
    121         if (thumbnailData != null && thumbnailData.thumbnail != null) {
    122             Bitmap bm = thumbnailData.thumbnail;
    123             bm.prepareToDraw();
    124             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    125             mPaint.setShader(mBitmapShader);
    126             mThumbnailData = thumbnailData;
    127             updateThumbnailMatrix();
    128         } else {
    129             mBitmapShader = null;
    130             mThumbnailData = null;
    131             mPaint.setShader(null);
    132             mOverlay.reset();
    133         }
    134         updateThumbnailPaintFilter();
    135     }
    136 
    137     public void setDimAlphaMultipler(float dimAlphaMultipler) {
    138         mDimAlphaMultiplier = dimAlphaMultipler;
    139         setDimAlpha(mDimAlpha);
    140     }
    141 
    142     /**
    143      * Sets the alpha of the dim layer on top of this view.
    144      *
    145      * If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be black.
    146      */
    147     public void setDimAlpha(float dimAlpha) {
    148         mDimAlpha = dimAlpha;
    149         updateThumbnailPaintFilter();
    150     }
    151 
    152     public Rect getInsets() {
    153         if (mThumbnailData != null) {
    154             return mThumbnailData.insets;
    155         }
    156         return new Rect();
    157     }
    158 
    159     public int getSysUiStatusNavFlags() {
    160         if (mThumbnailData != null) {
    161             int flags = 0;
    162             flags |= (mThumbnailData.systemUiVisibility & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0
    163                     ? SystemUiController.FLAG_LIGHT_STATUS
    164                     : SystemUiController.FLAG_DARK_STATUS;
    165             flags |= (mThumbnailData.systemUiVisibility & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0
    166                     ? SystemUiController.FLAG_LIGHT_NAV
    167                     : SystemUiController.FLAG_DARK_NAV;
    168             return flags;
    169         }
    170         return 0;
    171     }
    172 
    173     @Override
    174     protected void onDraw(Canvas canvas) {
    175         drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
    176     }
    177 
    178     public float getCornerRadius() {
    179         return mCornerRadius;
    180     }
    181 
    182     public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
    183             float cornerRadius) {
    184         // Draw the background in all cases, except when the thumbnail data is opaque
    185         final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
    186                 || mThumbnailData == null;
    187         if (drawBackgroundOnly || mClipBottom > 0 || mThumbnailData.isTranslucent) {
    188             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
    189             if (drawBackgroundOnly) {
    190                 return;
    191             }
    192         }
    193 
    194         if (mClipBottom > 0) {
    195             canvas.save();
    196             canvas.clipRect(x, y, width, mClipBottom);
    197             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
    198             canvas.restore();
    199         } else {
    200             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
    201         }
    202     }
    203 
    204     private void updateThumbnailPaintFilter() {
    205         int mul = (int) ((1 - mDimAlpha * mDimAlphaMultiplier) * 255);
    206         if (mBitmapShader != null) {
    207             LightingColorFilter filter = getDimmingColorFilter(mul, mIsDarkTextTheme);
    208             mPaint.setColorFilter(filter);
    209             mBackgroundPaint.setColorFilter(filter);
    210         } else {
    211             mPaint.setColorFilter(null);
    212             mPaint.setColor(Color.argb(255, mul, mul, mul));
    213         }
    214         invalidate();
    215     }
    216 
    217     private void updateThumbnailMatrix() {
    218         boolean rotate = false;
    219         mClipBottom = -1;
    220         if (mBitmapShader != null && mThumbnailData != null) {
    221             float scale = mThumbnailData.scale;
    222             Rect thumbnailInsets  = mThumbnailData.insets;
    223             final float thumbnailWidth = mThumbnailData.thumbnail.getWidth() -
    224                     (thumbnailInsets.left + thumbnailInsets.right) * scale;
    225             final float thumbnailHeight = mThumbnailData.thumbnail.getHeight() -
    226                     (thumbnailInsets.top + thumbnailInsets.bottom) * scale;
    227 
    228             final float thumbnailScale;
    229             final DeviceProfile profile = mActivity.getDeviceProfile();
    230 
    231             if (getMeasuredWidth() == 0) {
    232                 // If we haven't measured , skip the thumbnail drawing and only draw the background
    233                 // color
    234                 thumbnailScale = 0f;
    235             } else {
    236                 final Configuration configuration =
    237                         getContext().getResources().getConfiguration();
    238                 // Rotate the screenshot if not in multi-window mode
    239                 rotate = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION &&
    240                         configuration.orientation != mThumbnailData.orientation &&
    241                         !mActivity.isInMultiWindowModeCompat() &&
    242                         mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
    243                 // Scale the screenshot to always fit the width of the card.
    244                 thumbnailScale = rotate
    245                         ? getMeasuredWidth() / thumbnailHeight
    246                         : getMeasuredWidth() / thumbnailWidth;
    247             }
    248 
    249             if (rotate) {
    250                 int rotationDir = profile.isVerticalBarLayout() && !profile.isSeascape() ? -1 : 1;
    251                 mMatrix.setRotate(90 * rotationDir);
    252                 int newLeftInset = rotationDir == 1 ? thumbnailInsets.bottom : thumbnailInsets.top;
    253                 int newTopInset = rotationDir == 1 ? thumbnailInsets.left : thumbnailInsets.right;
    254                 mMatrix.postTranslate(-newLeftInset * scale, -newTopInset * scale);
    255                 if (rotationDir == -1) {
    256                     // Crop the right/bottom side of the screenshot rather than left/top
    257                     float excessHeight = thumbnailWidth * thumbnailScale - getMeasuredHeight();
    258                     mMatrix.postTranslate(0, -excessHeight);
    259                 }
    260                 // Move the screenshot to the thumbnail window (rotation moved it out).
    261                 if (rotationDir == 1) {
    262                     mMatrix.postTranslate(mThumbnailData.thumbnail.getHeight(), 0);
    263                 } else {
    264                     mMatrix.postTranslate(0, mThumbnailData.thumbnail.getWidth());
    265                 }
    266             } else {
    267                 mMatrix.setTranslate(-mThumbnailData.insets.left * scale,
    268                         -mThumbnailData.insets.top * scale);
    269             }
    270             mMatrix.postScale(thumbnailScale, thumbnailScale);
    271             mBitmapShader.setLocalMatrix(mMatrix);
    272 
    273             float bitmapHeight = Math.max((rotate ? thumbnailWidth : thumbnailHeight)
    274                     * thumbnailScale, 0);
    275             if (Math.round(bitmapHeight) < getMeasuredHeight()) {
    276                 mClipBottom = bitmapHeight;
    277             }
    278             mPaint.setShader(mBitmapShader);
    279         }
    280 
    281         if (rotate) {
    282             // The overlay doesn't really work when the screenshot is rotated, so don't add it.
    283             mOverlay.reset();
    284         } else {
    285             mOverlay.setTaskInfo(mTask, mThumbnailData, mMatrix);
    286         }
    287         invalidate();
    288     }
    289 
    290     @Override
    291     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    292         super.onSizeChanged(w, h, oldw, oldh);
    293         updateThumbnailMatrix();
    294     }
    295 
    296     private static LightingColorFilter getDimmingColorFilter(int intensity, boolean shouldLighten) {
    297         intensity = Utilities.boundToRange(intensity, 0, 255);
    298         if (intensity == 255) {
    299             return null;
    300         }
    301         if (shouldLighten) {
    302             if (sHighlightFilterCache[intensity] == null) {
    303                 int colorAdd = 255 - intensity;
    304                 sHighlightFilterCache[intensity] = new LightingColorFilter(
    305                         Color.argb(255, intensity, intensity, intensity),
    306                         Color.argb(255, colorAdd, colorAdd, colorAdd));
    307             }
    308             return sHighlightFilterCache[intensity];
    309         } else {
    310             if (sDimFilterCache[intensity] == null) {
    311                 sDimFilterCache[intensity] = new LightingColorFilter(
    312                         Color.argb(255, intensity, intensity, intensity), 0);
    313             }
    314             return sDimFilterCache[intensity];
    315         }
    316     }
    317 }
    318