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 package com.android.launcher3.views; 17 18 import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW; 19 20 import android.content.Context; 21 import android.graphics.Canvas; 22 import android.graphics.Color; 23 import android.graphics.Rect; 24 import android.graphics.drawable.ColorDrawable; 25 import android.util.AttributeSet; 26 import android.view.View; 27 28 import androidx.annotation.NonNull; 29 import androidx.annotation.Px; 30 import androidx.core.graphics.ColorUtils; 31 32 import com.android.launcher3.BaseActivity; 33 import com.android.launcher3.Insettable; 34 import com.android.launcher3.util.SystemUiController; 35 36 import java.util.ArrayList; 37 38 /** 39 * Simple scrim which draws a flat color 40 */ 41 public class ScrimView extends View implements Insettable { 42 private static final float STATUS_BAR_COLOR_FORCE_UPDATE_THRESHOLD = 0.9f; 43 44 private final ArrayList<Runnable> mOpaquenessListeners = new ArrayList<>(1); 45 private SystemUiController mSystemUiController; 46 private ScrimDrawingController mDrawingController; 47 private int mBackgroundColor; 48 private boolean mIsVisible = true; 49 private boolean mLastDispatchedOpaqueness; 50 private float mHeaderScale = 1f; 51 ScrimView(Context context, AttributeSet attrs)52 public ScrimView(Context context, AttributeSet attrs) { 53 super(context, attrs); 54 setFocusable(false); 55 } 56 57 @Override setInsets(Rect insets)58 public void setInsets(Rect insets) { 59 } 60 61 @Override hasOverlappingRendering()62 public boolean hasOverlappingRendering() { 63 return false; 64 } 65 66 @Override onSetAlpha(int alpha)67 protected boolean onSetAlpha(int alpha) { 68 updateSysUiColors(); 69 dispatchVisibilityListenersIfNeeded(); 70 return super.onSetAlpha(alpha); 71 } 72 73 @Override setBackgroundColor(int color)74 public void setBackgroundColor(int color) { 75 mBackgroundColor = color; 76 updateSysUiColors(); 77 dispatchVisibilityListenersIfNeeded(); 78 super.setBackgroundColor(color); 79 } 80 getBackgroundColor()81 public int getBackgroundColor() { 82 return mBackgroundColor; 83 } 84 85 @Override onVisibilityAggregated(boolean isVisible)86 public void onVisibilityAggregated(boolean isVisible) { 87 super.onVisibilityAggregated(isVisible); 88 mIsVisible = isVisible; 89 dispatchVisibilityListenersIfNeeded(); 90 } 91 isFullyOpaque()92 public boolean isFullyOpaque() { 93 return mIsVisible && getAlpha() == 1 && Color.alpha(mBackgroundColor) == 255; 94 } 95 96 @Override onDraw(Canvas canvas)97 protected void onDraw(Canvas canvas) { 98 super.onDraw(canvas); 99 if (mDrawingController != null) { 100 mDrawingController.drawOnScrimWithScale(canvas, mHeaderScale); 101 } 102 } 103 104 /** Set scrim header's scale and bottom offset. */ setScrimHeaderScale(float scale)105 public void setScrimHeaderScale(float scale) { 106 boolean hasChanged = mHeaderScale != scale; 107 mHeaderScale = scale; 108 if (hasChanged) { 109 invalidate(); 110 } 111 } 112 113 @Override onVisibilityChanged(View changedView, int visibility)114 protected void onVisibilityChanged(View changedView, int visibility) { 115 super.onVisibilityChanged(changedView, visibility); 116 updateSysUiColors(); 117 } 118 updateSysUiColors()119 private void updateSysUiColors() { 120 // Use a light system UI (dark icons) if all apps is behind at least half of the 121 // status bar. 122 final float threshold = STATUS_BAR_COLOR_FORCE_UPDATE_THRESHOLD; 123 boolean forceChange = getVisibility() == VISIBLE 124 && getAlpha() > threshold 125 && (Color.alpha(mBackgroundColor) / 255f) > threshold; 126 if (forceChange) { 127 getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !isScrimDark()); 128 } else { 129 getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0); 130 } 131 } 132 dispatchVisibilityListenersIfNeeded()133 private void dispatchVisibilityListenersIfNeeded() { 134 boolean fullyOpaque = isFullyOpaque(); 135 if (mLastDispatchedOpaqueness == fullyOpaque) { 136 return; 137 } 138 mLastDispatchedOpaqueness = fullyOpaque; 139 for (int i = 0; i < mOpaquenessListeners.size(); i++) { 140 mOpaquenessListeners.get(i).run(); 141 } 142 } 143 getSystemUiController()144 private SystemUiController getSystemUiController() { 145 if (mSystemUiController == null) { 146 mSystemUiController = BaseActivity.fromContext(getContext()).getSystemUiController(); 147 } 148 return mSystemUiController; 149 } 150 isScrimDark()151 private boolean isScrimDark() { 152 if (!(getBackground() instanceof ColorDrawable)) { 153 throw new IllegalStateException( 154 "ScrimView must have a ColorDrawable background, this one has: " 155 + getBackground()); 156 } 157 return ColorUtils.calculateLuminance( 158 ((ColorDrawable) getBackground()).getColor()) < 0.5f; 159 } 160 161 /** 162 * Sets drawing controller. Invalidates ScrimView if drawerController has changed. 163 */ setDrawingController(ScrimDrawingController drawingController)164 public void setDrawingController(ScrimDrawingController drawingController) { 165 if (mDrawingController != drawingController) { 166 mDrawingController = drawingController; 167 invalidate(); 168 } 169 } 170 171 /** 172 * Registers a listener to be notified of whether the scrim is occluding other UI elements. 173 * @see #isFullyOpaque() 174 */ addOpaquenessListener(@onNull Runnable listener)175 public void addOpaquenessListener(@NonNull Runnable listener) { 176 mOpaquenessListeners.add(listener); 177 } 178 179 /** 180 * Removes previously registered listener. 181 * @see #addOpaquenessListener(Runnable) 182 */ removeOpaquenessListener(@onNull Runnable listener)183 public void removeOpaquenessListener(@NonNull Runnable listener) { 184 mOpaquenessListeners.remove(listener); 185 } 186 187 /** 188 * A Utility interface allowing for other surfaces to draw on ScrimView 189 */ 190 public interface ScrimDrawingController { 191 192 /** Draw scrim view on canvas with scale. */ drawOnScrimWithScale(Canvas canvas, float scale)193 default void drawOnScrimWithScale(Canvas canvas, float scale) { 194 drawOnScrimWithScaleAndBottomOffset(canvas, scale, 0); 195 } 196 197 /** Draw scrim view on canvas with bottomOffset. */ drawOnScrimWithBottomOffset(Canvas canvas, @Px int bottomOffsetPx)198 default void drawOnScrimWithBottomOffset(Canvas canvas, @Px int bottomOffsetPx) { 199 drawOnScrimWithScaleAndBottomOffset(canvas, 1f, bottomOffsetPx); 200 } 201 202 /** Draw scrim view on canvas with scale and bottomOffset. */ drawOnScrimWithScaleAndBottomOffset( Canvas canvas, float scale, @Px int bottomOffsetPx)203 void drawOnScrimWithScaleAndBottomOffset( 204 Canvas canvas, float scale, @Px int bottomOffsetPx); 205 } 206 } 207