Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2018 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.launcher3.graphics;
     18 
     19 import static android.content.Intent.ACTION_SCREEN_OFF;
     20 import static android.content.Intent.ACTION_USER_PRESENT;
     21 
     22 import android.animation.ObjectAnimator;
     23 import android.content.BroadcastReceiver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.IntentFilter;
     27 import android.graphics.Bitmap;
     28 import android.graphics.Canvas;
     29 import android.graphics.Color;
     30 import android.graphics.LinearGradient;
     31 import android.graphics.Paint;
     32 import android.graphics.Rect;
     33 import android.graphics.RectF;
     34 import android.graphics.Region;
     35 import android.graphics.Shader;
     36 import android.graphics.drawable.Drawable;
     37 import android.support.v4.graphics.ColorUtils;
     38 import android.util.DisplayMetrics;
     39 import android.util.Property;
     40 import android.view.View;
     41 
     42 import com.android.launcher3.CellLayout;
     43 import com.android.launcher3.Launcher;
     44 import com.android.launcher3.R;
     45 import com.android.launcher3.Utilities;
     46 import com.android.launcher3.Workspace;
     47 import com.android.launcher3.uioverrides.WallpaperColorInfo;
     48 import com.android.launcher3.util.Themes;
     49 
     50 /**
     51  * View scrim which draws behind hotseat and workspace
     52  */
     53 public class WorkspaceAndHotseatScrim implements
     54         View.OnAttachStateChangeListener, WallpaperColorInfo.OnChangeListener {
     55 
     56     public static Property<WorkspaceAndHotseatScrim, Float> SCRIM_PROGRESS =
     57             new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "scrimProgress") {
     58                 @Override
     59                 public Float get(WorkspaceAndHotseatScrim scrim) {
     60                     return scrim.mScrimProgress;
     61                 }
     62 
     63                 @Override
     64                 public void set(WorkspaceAndHotseatScrim scrim, Float value) {
     65                     scrim.setScrimProgress(value);
     66                 }
     67             };
     68 
     69     public static Property<WorkspaceAndHotseatScrim, Float> SYSUI_PROGRESS =
     70             new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "sysUiProgress") {
     71                 @Override
     72                 public Float get(WorkspaceAndHotseatScrim scrim) {
     73                     return scrim.mSysUiProgress;
     74                 }
     75 
     76                 @Override
     77                 public void set(WorkspaceAndHotseatScrim scrim, Float value) {
     78                     scrim.setSysUiProgress(value);
     79                 }
     80             };
     81 
     82     private static Property<WorkspaceAndHotseatScrim, Float> SYSUI_ANIM_MULTIPLIER =
     83             new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "sysUiAnimMultiplier") {
     84                 @Override
     85                 public Float get(WorkspaceAndHotseatScrim scrim) {
     86                     return scrim.mSysUiAnimMultiplier;
     87                 }
     88 
     89                 @Override
     90                 public void set(WorkspaceAndHotseatScrim scrim, Float value) {
     91                     scrim.mSysUiAnimMultiplier = value;
     92                     scrim.reapplySysUiAlpha();
     93                 }
     94             };
     95 
     96     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
     97         @Override
     98         public void onReceive(Context context, Intent intent) {
     99             final String action = intent.getAction();
    100             if (ACTION_SCREEN_OFF.equals(action)) {
    101                 mAnimateScrimOnNextDraw = true;
    102             } else if (ACTION_USER_PRESENT.equals(action)) {
    103                 // ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where
    104                 // the user unlocked and the Launcher is not in the foreground.
    105                 mAnimateScrimOnNextDraw = false;
    106             }
    107         }
    108     };
    109 
    110     private static final int DARK_SCRIM_COLOR = 0x55000000;
    111     private static final int MAX_HOTSEAT_SCRIM_ALPHA = 100;
    112     private static final int ALPHA_MASK_HEIGHT_DP = 500;
    113     private static final int ALPHA_MASK_BITMAP_DP = 200;
    114     private static final int ALPHA_MASK_WIDTH_DP = 2;
    115 
    116     private final Rect mHighlightRect = new Rect();
    117     private final Launcher mLauncher;
    118     private final WallpaperColorInfo mWallpaperColorInfo;
    119     private final View mRoot;
    120 
    121     private Workspace mWorkspace;
    122 
    123     private final boolean mHasSysUiScrim;
    124     private boolean mDrawTopScrim, mDrawBottomScrim;
    125 
    126     private final RectF mFinalMaskRect = new RectF();
    127     private final Paint mBottomMaskPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
    128     private final Bitmap mBottomMask;
    129     private final int mMaskHeight;
    130 
    131     private final Drawable mTopScrim;
    132 
    133     private int mFullScrimColor;
    134 
    135     private float mScrimProgress;
    136     private int mScrimAlpha = 0;
    137 
    138     private float mSysUiProgress = 1;
    139     private boolean mHideSysUiScrim;
    140 
    141     private boolean mAnimateScrimOnNextDraw = false;
    142     private float mSysUiAnimMultiplier = 1;
    143 
    144     public WorkspaceAndHotseatScrim(View view) {
    145         mRoot = view;
    146         mLauncher = Launcher.getLauncher(view.getContext());
    147         mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher);
    148 
    149         mMaskHeight = Utilities.pxFromDp(ALPHA_MASK_BITMAP_DP,
    150                 view.getResources().getDisplayMetrics());
    151 
    152         mHasSysUiScrim = !mWallpaperColorInfo.supportsDarkText();
    153         if (mHasSysUiScrim) {
    154             mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim);
    155             mBottomMask = createDitheredAlphaMask();
    156         } else {
    157             mTopScrim = null;
    158             mBottomMask = null;
    159         }
    160 
    161         view.addOnAttachStateChangeListener(this);
    162         onExtractedColorsChanged(mWallpaperColorInfo);
    163     }
    164 
    165     public void setWorkspace(Workspace workspace)  {
    166         mWorkspace = workspace;
    167     }
    168 
    169     public void draw(Canvas canvas) {
    170         // Draw the background below children.
    171         if (mScrimAlpha > 0) {
    172             // Update the scroll position first to ensure scrim cutout is in the right place.
    173             mWorkspace.computeScrollWithoutInvalidation();
    174             CellLayout currCellLayout = mWorkspace.getCurrentDragOverlappingLayout();
    175             canvas.save();
    176             if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
    177                 // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
    178                 mLauncher.getDragLayer()
    179                         .getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
    180                 canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
    181             }
    182 
    183             canvas.drawColor(ColorUtils.setAlphaComponent(mFullScrimColor, mScrimAlpha));
    184             canvas.restore();
    185         }
    186 
    187         if (!mHideSysUiScrim && mHasSysUiScrim) {
    188             if (mSysUiProgress <= 0) {
    189                 mAnimateScrimOnNextDraw = false;
    190                 return;
    191             }
    192 
    193             if (mAnimateScrimOnNextDraw) {
    194                 mSysUiAnimMultiplier = 0;
    195                 reapplySysUiAlphaNoInvalidate();
    196 
    197                 ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, 1);
    198                 anim.setAutoCancel(true);
    199                 anim.setDuration(600);
    200                 anim.setStartDelay(mLauncher.getWindow().getTransitionBackgroundFadeDuration());
    201                 anim.start();
    202                 mAnimateScrimOnNextDraw = false;
    203             }
    204 
    205             if (mDrawTopScrim) {
    206                 mTopScrim.draw(canvas);
    207             }
    208             if (mDrawBottomScrim) {
    209                 canvas.drawBitmap(mBottomMask, null, mFinalMaskRect, mBottomMaskPaint);
    210             }
    211         }
    212     }
    213 
    214     public void onInsetsChanged(Rect insets) {
    215         mDrawTopScrim = insets.top > 0;
    216         mDrawBottomScrim = !mLauncher.getDeviceProfile().isVerticalBarLayout();
    217     }
    218 
    219     private void setScrimProgress(float progress) {
    220         if (mScrimProgress != progress) {
    221             mScrimProgress = progress;
    222             mScrimAlpha = Math.round(255 * mScrimProgress);
    223             invalidate();
    224         }
    225     }
    226 
    227     @Override
    228     public void onViewAttachedToWindow(View view) {
    229         mWallpaperColorInfo.addOnChangeListener(this);
    230         onExtractedColorsChanged(mWallpaperColorInfo);
    231 
    232         if (mHasSysUiScrim) {
    233             IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF);
    234             filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
    235             mRoot.getContext().registerReceiver(mReceiver, filter);
    236         }
    237     }
    238 
    239     @Override
    240     public void onViewDetachedFromWindow(View view) {
    241         mWallpaperColorInfo.removeOnChangeListener(this);
    242         if (mHasSysUiScrim) {
    243             mRoot.getContext().unregisterReceiver(mReceiver);
    244         }
    245     }
    246 
    247     @Override
    248     public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
    249         // for super light wallpaper it needs to be darken for contrast to workspace
    250         // for dark wallpapers the text is white so darkening works as well
    251         mBottomMaskPaint.setColor(ColorUtils.compositeColors(DARK_SCRIM_COLOR,
    252                 wallpaperColorInfo.getMainColor()));
    253         reapplySysUiAlpha();
    254         mFullScrimColor = wallpaperColorInfo.getMainColor();
    255         if (mScrimAlpha > 0) {
    256             invalidate();
    257         }
    258     }
    259 
    260     public void setSize(int w, int h) {
    261         if (mHasSysUiScrim) {
    262             mTopScrim.setBounds(0, 0, w, h);
    263             mFinalMaskRect.set(0, h - mMaskHeight, w, h);
    264         }
    265     }
    266 
    267     public void hideSysUiScrim(boolean hideSysUiScrim) {
    268         mHideSysUiScrim = hideSysUiScrim;
    269         if (!hideSysUiScrim) {
    270             mAnimateScrimOnNextDraw = true;
    271         }
    272         invalidate();
    273     }
    274 
    275     private void setSysUiProgress(float progress) {
    276         if (progress != mSysUiProgress) {
    277             mSysUiProgress = progress;
    278             reapplySysUiAlpha();
    279         }
    280     }
    281 
    282     private void reapplySysUiAlpha() {
    283         if (mHasSysUiScrim) {
    284             reapplySysUiAlphaNoInvalidate();
    285             if (!mHideSysUiScrim) {
    286                 invalidate();
    287             }
    288         }
    289     }
    290 
    291     private void reapplySysUiAlphaNoInvalidate() {
    292         float factor = mSysUiProgress * mSysUiAnimMultiplier;
    293         mBottomMaskPaint.setAlpha(Math.round(MAX_HOTSEAT_SCRIM_ALPHA * factor));
    294         mTopScrim.setAlpha(Math.round(255 * factor));
    295     }
    296 
    297     public void invalidate() {
    298         mRoot.invalidate();
    299     }
    300 
    301     public Bitmap createDitheredAlphaMask() {
    302         DisplayMetrics dm = mLauncher.getResources().getDisplayMetrics();
    303         int width = Utilities.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
    304         int gradientHeight = Utilities.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm);
    305         Bitmap dst = Bitmap.createBitmap(width, mMaskHeight, Bitmap.Config.ALPHA_8);
    306         Canvas c = new Canvas(dst);
    307         Paint paint = new Paint(Paint.DITHER_FLAG);
    308         LinearGradient lg = new LinearGradient(0, 0, 0, gradientHeight,
    309                 new int[]{
    310                         0x00FFFFFF,
    311                         ColorUtils.setAlphaComponent(Color.WHITE, (int) (0xFF * 0.95)),
    312                         0xFFFFFFFF},
    313                 new float[]{0f, 0.8f, 1f},
    314                 Shader.TileMode.CLAMP);
    315         paint.setShader(lg);
    316         c.drawRect(0, 0, width, gradientHeight, paint);
    317         return dst;
    318     }
    319 }
    320