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.shared.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 
     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             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. */
    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      */
    302     public void setOverlayHeaderOnThumbnailActionBar(boolean flag) {
    303         mOverlayHeaderOnThumbnailActionBar = flag;
    304     }
    305 
    306     /** Updates the clip rect based on the given task bar. */
    307     void updateClipToTaskBar(View taskBar) {
    308         mTaskBar = taskBar;
    309         invalidate();
    310     }
    311 
    312     /** Updates the visibility of the the thumbnail. */
    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      */
    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      */
    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      */
    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      */
    365     void onTaskDataLoaded(ThumbnailData thumbnailData) {
    366         setThumbnail(thumbnailData);
    367     }
    368 
    369     /** Unbinds the thumbnail view from the task */
    370     void unbindFromTask() {
    371         mTask = null;
    372         setThumbnail(null);
    373         EventBus.getDefault().unregister(this);
    374     }
    375 
    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 
    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