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.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 @Override hasOverlappingRendering()60 public boolean hasOverlappingRendering() { 61 return false; 62 } 63 64 @Override onSetAlpha(int alpha)65 protected boolean onSetAlpha(int alpha) { 66 updateSysUiColors(); 67 dispatchVisibilityListenersIfNeeded(); 68 return super.onSetAlpha(alpha); 69 } 70 71 @Override setBackgroundColor(int color)72 public void setBackgroundColor(int color) { 73 mBackgroundColor = color; 74 updateSysUiColors(); 75 dispatchVisibilityListenersIfNeeded(); 76 super.setBackgroundColor(color); 77 } 78 getBackgroundColor()79 public int getBackgroundColor() { 80 return mBackgroundColor; 81 } 82 83 @Override onVisibilityAggregated(boolean isVisible)84 public void onVisibilityAggregated(boolean isVisible) { 85 super.onVisibilityAggregated(isVisible); 86 mIsVisible = isVisible; 87 dispatchVisibilityListenersIfNeeded(); 88 } 89 isFullyOpaque()90 public boolean isFullyOpaque() { 91 return mIsVisible && getAlpha() == 1 && Color.alpha(mBackgroundColor) == 255; 92 } 93 94 @Override onDraw(Canvas canvas)95 protected void onDraw(Canvas canvas) { 96 super.onDraw(canvas); 97 if (mDrawingController != null) { 98 mDrawingController.drawOnScrimWithScale(canvas, mHeaderScale); 99 } 100 } 101 102 /** Set scrim header's scale and bottom offset. */ setScrimHeaderScale(float scale)103 public void setScrimHeaderScale(float scale) { 104 boolean hasChanged = mHeaderScale != scale; 105 mHeaderScale = scale; 106 if (hasChanged) { 107 invalidate(); 108 } 109 } 110 111 @Override onVisibilityChanged(View changedView, int visibility)112 protected void onVisibilityChanged(View changedView, int visibility) { 113 super.onVisibilityChanged(changedView, visibility); 114 updateSysUiColors(); 115 } 116 updateSysUiColors()117 private void updateSysUiColors() { 118 // Use a light system UI (dark icons) if all apps is behind at least half of the 119 // status bar. 120 final float threshold = STATUS_BAR_COLOR_FORCE_UPDATE_THRESHOLD; 121 boolean forceChange = getVisibility() == VISIBLE 122 && getAlpha() > threshold 123 && (Color.alpha(mBackgroundColor) / 255f) > threshold; 124 if (forceChange) { 125 getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !isScrimDark()); 126 } else { 127 getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0); 128 } 129 } 130 dispatchVisibilityListenersIfNeeded()131 private void dispatchVisibilityListenersIfNeeded() { 132 boolean fullyOpaque = isFullyOpaque(); 133 if (mLastDispatchedOpaqueness == fullyOpaque) { 134 return; 135 } 136 mLastDispatchedOpaqueness = fullyOpaque; 137 for (int i = 0; i < mOpaquenessListeners.size(); i++) { 138 mOpaquenessListeners.get(i).run(); 139 } 140 } 141 getSystemUiController()142 private SystemUiController getSystemUiController() { 143 if (mSystemUiController == null) { 144 mSystemUiController = 145 ActivityContext.lookupContext(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 /** Draw scrim view on canvas with scale. */ drawOnScrimWithScale(Canvas canvas, float scale)192 default void drawOnScrimWithScale(Canvas canvas, float scale) { 193 drawOnScrimWithScaleAndBottomOffset(canvas, scale, 0); 194 } 195 196 /** Draw scrim view on canvas with bottomOffset. */ drawOnScrimWithBottomOffset(Canvas canvas, @Px int bottomOffsetPx)197 default void drawOnScrimWithBottomOffset(Canvas canvas, @Px int bottomOffsetPx) { 198 drawOnScrimWithScaleAndBottomOffset(canvas, 1f, bottomOffsetPx); 199 } 200 201 /** Draw scrim view on canvas with scale and bottomOffset. */ drawOnScrimWithScaleAndBottomOffset( Canvas canvas, float scale, @Px int bottomOffsetPx)202 void drawOnScrimWithScaleAndBottomOffset( 203 Canvas canvas, float scale, @Px int bottomOffsetPx); 204 } 205 } 206