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.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.misc.Utilities;
     41 import com.android.systemui.recents.model.Task;
     42 import com.android.systemui.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 
     96     public TaskViewThumbnail(Context context) {
     97         this(context, null);
     98     }
     99 
    100     public TaskViewThumbnail(Context context, AttributeSet attrs) {
    101         this(context, attrs, 0);
    102     }
    103 
    104     public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) {
    105         this(context, attrs, defStyleAttr, 0);
    106     }
    107 
    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      */
    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
    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. */
    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. */
    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      */
    245     public void updateThumbnailMatrix() {
    246         mThumbnailScale = 1f;
    247         if (mBitmapShader != null && mThumbnailData != null) {
    248             // We consider this a stack task if it is not freeform (ie. has no bounds) or has been
    249             // dragged into the stack from the freeform workspace
    250             boolean isStackTask = !mTask.isFreeformTask() || mTask.bounds == null;
    251             int xOffset, yOffset = 0;
    252             if (mTaskViewRect.isEmpty()) {
    253                 // If we haven't measured , skip the thumbnail drawing and only draw the background
    254                 // color
    255                 mThumbnailScale = 0f;
    256             } else if (mSizeToFit) {
    257                 // Make sure we fill the entire space regardless of the orientation.
    258                 float viewAspectRatio = (float) mTaskViewRect.width() /
    259                         (float) (mTaskViewRect.height() - mTitleBarHeight);
    260                 float thumbnailAspectRatio =
    261                         (float) mThumbnailRect.width() / (float) mThumbnailRect.height();
    262                 if (viewAspectRatio > thumbnailAspectRatio) {
    263                     mThumbnailScale =
    264                             (float) mTaskViewRect.width() / (float) mThumbnailRect.width();
    265                 } else {
    266                     mThumbnailScale = (float) (mTaskViewRect.height() - mTitleBarHeight)
    267                             / (float) mThumbnailRect.height();
    268                 }
    269             } else if (isStackTask) {
    270                 float invThumbnailScale = 1f / mFullscreenThumbnailScale;
    271                 if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) {
    272                     if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) {
    273                         // If we are in the same orientation as the screenshot, just scale it to the
    274                         // width of the task view
    275                         mThumbnailScale = (float) mTaskViewRect.width() / mThumbnailRect.width();
    276                     } else {
    277                         // Scale the landscape thumbnail up to app size, then scale that to the task
    278                         // view size to match other portrait screenshots
    279                         mThumbnailScale = invThumbnailScale *
    280                                 ((float) mTaskViewRect.width() / mDisplayRect.width());
    281                     }
    282                 } else {
    283                     // Otherwise, scale the screenshot to fit 1:1 in the current orientation
    284                     mThumbnailScale = invThumbnailScale;
    285                 }
    286             } else {
    287                 // Otherwise, if this is a freeform task with task bounds, then scale the thumbnail
    288                 // to fit the entire bitmap into the task bounds
    289                 mThumbnailScale = Math.min(
    290                         (float) mTaskViewRect.width() / mThumbnailRect.width(),
    291                         (float) mTaskViewRect.height() / mThumbnailRect.height());
    292             }
    293             mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale,
    294                     -mThumbnailData.insets.top * mFullscreenThumbnailScale);
    295             mMatrix.postScale(mThumbnailScale, mThumbnailScale);
    296             mBitmapShader.setLocalMatrix(mMatrix);
    297         }
    298         if (!mInvisible) {
    299             invalidate();
    300         }
    301     }
    302 
    303     /** Sets whether the thumbnail should be resized to fit the task view in all orientations. */
    304     public void setSizeToFit(boolean flag) {
    305         mSizeToFit = flag;
    306     }
    307 
    308     /**
    309      * Sets whether the header should overlap (and hide) the action bar in the thumbnail, or
    310      * be stacked just above it.
    311      */
    312     public void setOverlayHeaderOnThumbnailActionBar(boolean flag) {
    313         mOverlayHeaderOnThumbnailActionBar = flag;
    314     }
    315 
    316     /** Updates the clip rect based on the given task bar. */
    317     void updateClipToTaskBar(View taskBar) {
    318         mTaskBar = taskBar;
    319         invalidate();
    320     }
    321 
    322     /** Updates the visibility of the the thumbnail. */
    323     void updateThumbnailVisibility(int clipBottom) {
    324         boolean invisible = mTaskBar != null && (getHeight() - clipBottom) <= mTaskBar.getHeight();
    325         if (invisible != mInvisible) {
    326             mInvisible = invisible;
    327             if (!mInvisible) {
    328                 updateThumbnailPaintFilter();
    329             }
    330         }
    331     }
    332 
    333     /**
    334      * Sets the dim alpha, only used when we are not using hardware layers.
    335      * (see RecentsConfiguration.useHardwareLayers)
    336      */
    337     public void setDimAlpha(float dimAlpha) {
    338         mDimAlpha = dimAlpha;
    339         updateThumbnailPaintFilter();
    340     }
    341 
    342     /**
    343      * Returns the {@link Paint} used to draw a task screenshot, or {@link #mLockedPaint} if the
    344      * thumbnail shouldn't be drawn because it belongs to a locked user.
    345      */
    346     protected Paint getDrawPaint() {
    347         if (mUserLocked) {
    348             return mLockedPaint;
    349         }
    350         return mDrawPaint;
    351     }
    352 
    353     /**
    354      * Binds the thumbnail view to the task.
    355      */
    356     void bindToTask(Task t, boolean disabledInSafeMode, int displayOrientation, Rect displayRect) {
    357         mTask = t;
    358         mDisabledInSafeMode = disabledInSafeMode;
    359         mDisplayOrientation = displayOrientation;
    360         mDisplayRect.set(displayRect);
    361         if (t.colorBackground != 0) {
    362             mBgFillPaint.setColor(t.colorBackground);
    363         }
    364         if (t.colorPrimary != 0) {
    365             mLockedPaint.setColor(t.colorPrimary);
    366         }
    367         mUserLocked = t.isLocked;
    368         EventBus.getDefault().register(this);
    369     }
    370 
    371     /**
    372      * Called when the bound task's data has loaded and this view should update to reflect the
    373      * changes.
    374      */
    375     void onTaskDataLoaded(ThumbnailData thumbnailData) {
    376         setThumbnail(thumbnailData);
    377     }
    378 
    379     /** Unbinds the thumbnail view from the task */
    380     void unbindFromTask() {
    381         mTask = null;
    382         setThumbnail(null);
    383         EventBus.getDefault().unregister(this);
    384     }
    385 
    386     public final void onBusEvent(TaskSnapshotChangedEvent event) {
    387         if (mTask == null || event.taskId != mTask.key.id || event.thumbnailData == null
    388                 || event.thumbnailData.thumbnail == null) {
    389             return;
    390         }
    391         setThumbnail(event.thumbnailData);
    392     }
    393 
    394     public void dump(String prefix, PrintWriter writer) {
    395         writer.print(prefix); writer.print("TaskViewThumbnail");
    396         writer.print(" mTaskViewRect="); writer.print(Utilities.dumpRect(mTaskViewRect));
    397         writer.print(" mThumbnailRect="); writer.print(Utilities.dumpRect(mThumbnailRect));
    398         writer.print(" mThumbnailScale="); writer.print(mThumbnailScale);
    399         writer.print(" mDimAlpha="); writer.print(mDimAlpha);
    400         writer.println();
    401     }
    402 }
    403