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 ScrimView(Context context, AttributeSet attrs)50 public ScrimView(Context context, AttributeSet attrs) { 51 super(context, attrs); 52 setFocusable(false); 53 } 54 55 @Override setInsets(Rect insets)56 public void setInsets(Rect insets) { 57 } 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 79 @Override onVisibilityAggregated(boolean isVisible)80 public void onVisibilityAggregated(boolean isVisible) { 81 super.onVisibilityAggregated(isVisible); 82 mIsVisible = isVisible; 83 dispatchVisibilityListenersIfNeeded(); 84 } 85 isFullyOpaque()86 public boolean isFullyOpaque() { 87 return mIsVisible && getAlpha() == 1 && Color.alpha(mBackgroundColor) == 255; 88 } 89 90 @Override onDraw(Canvas canvas)91 protected void onDraw(Canvas canvas) { 92 super.onDraw(canvas); 93 if (mDrawingController != null) { 94 mDrawingController.drawOnScrim(canvas); 95 } 96 } 97 98 @Override onVisibilityChanged(View changedView, int visibility)99 protected void onVisibilityChanged(View changedView, int visibility) { 100 super.onVisibilityChanged(changedView, visibility); 101 updateSysUiColors(); 102 } 103 updateSysUiColors()104 private void updateSysUiColors() { 105 // Use a light system UI (dark icons) if all apps is behind at least half of the 106 // status bar. 107 final float threshold = STATUS_BAR_COLOR_FORCE_UPDATE_THRESHOLD; 108 boolean forceChange = getVisibility() == VISIBLE 109 && getAlpha() > threshold 110 && (Color.alpha(mBackgroundColor) / 255f) > threshold; 111 if (forceChange) { 112 getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !isScrimDark()); 113 } else { 114 getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0); 115 } 116 } 117 dispatchVisibilityListenersIfNeeded()118 private void dispatchVisibilityListenersIfNeeded() { 119 boolean fullyOpaque = isFullyOpaque(); 120 if (mLastDispatchedOpaqueness == fullyOpaque) { 121 return; 122 } 123 mLastDispatchedOpaqueness = fullyOpaque; 124 for (int i = 0; i < mOpaquenessListeners.size(); i++) { 125 mOpaquenessListeners.get(i).run(); 126 } 127 } 128 getSystemUiController()129 private SystemUiController getSystemUiController() { 130 if (mSystemUiController == null) { 131 mSystemUiController = BaseActivity.fromContext(getContext()).getSystemUiController(); 132 } 133 return mSystemUiController; 134 } 135 isScrimDark()136 private boolean isScrimDark() { 137 if (!(getBackground() instanceof ColorDrawable)) { 138 throw new IllegalStateException( 139 "ScrimView must have a ColorDrawable background, this one has: " 140 + getBackground()); 141 } 142 return ColorUtils.calculateLuminance( 143 ((ColorDrawable) getBackground()).getColor()) < 0.5f; 144 } 145 146 /** 147 * Sets drawing controller. Invalidates ScrimView if drawerController has changed. 148 */ setDrawingController(ScrimDrawingController drawingController)149 public void setDrawingController(ScrimDrawingController drawingController) { 150 if (mDrawingController != drawingController) { 151 mDrawingController = drawingController; 152 invalidate(); 153 } 154 } 155 156 /** 157 * Registers a listener to be notified of whether the scrim is occluding other UI elements. 158 * @see #isFullyOpaque() 159 */ addOpaquenessListener(@onNull Runnable listener)160 public void addOpaquenessListener(@NonNull Runnable listener) { 161 mOpaquenessListeners.add(listener); 162 } 163 164 /** 165 * Removes previously registered listener. 166 * @see #addOpaquenessListener(Runnable) 167 */ removeOpaquenessListener(@onNull Runnable listener)168 public void removeOpaquenessListener(@NonNull Runnable listener) { 169 mOpaquenessListeners.remove(listener); 170 } 171 172 /** 173 * A Utility interface allowing for other surfaces to draw on ScrimView 174 */ 175 public interface ScrimDrawingController { 176 /** 177 * Called inside ScrimView#OnDraw 178 */ drawOnScrim(Canvas canvas)179 void drawOnScrim(Canvas canvas); 180 } 181 } 182