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