1 /* 2 * Copyright (C) 2024 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.systemui.shared.statusbar.phone; 18 19 import android.annotation.ColorInt; 20 import android.annotation.IntDef; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.content.res.TypedArray; 24 import android.graphics.Canvas; 25 import android.graphics.Color; 26 import android.graphics.ColorFilter; 27 import android.graphics.Paint; 28 import android.graphics.PixelFormat; 29 import android.graphics.PorterDuff; 30 import android.graphics.PorterDuff.Mode; 31 import android.graphics.PorterDuffColorFilter; 32 import android.graphics.Rect; 33 import android.graphics.drawable.Drawable; 34 import android.os.SystemClock; 35 import android.util.Log; 36 import android.view.View; 37 38 import com.android.app.animation.Interpolators; 39 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 43 public class BarTransitions { 44 private static final boolean DEBUG = false; 45 private static final boolean DEBUG_COLORS = false; 46 47 @ColorInt 48 private static final int SYSTEM_BAR_BACKGROUND_OPAQUE = Color.BLACK; 49 @ColorInt 50 private static final int SYSTEM_BAR_BACKGROUND_TRANSPARENT = Color.TRANSPARENT; 51 52 public static final int MODE_TRANSPARENT = 0; 53 public static final int MODE_SEMI_TRANSPARENT = 1; 54 public static final int MODE_TRANSLUCENT = 2; 55 public static final int MODE_LIGHTS_OUT = 3; 56 public static final int MODE_OPAQUE = 4; 57 public static final int MODE_WARNING = 5; 58 public static final int MODE_LIGHTS_OUT_TRANSPARENT = 6; 59 60 @IntDef(flag = true, prefix = { "MODE_" }, value = { 61 MODE_OPAQUE, 62 MODE_SEMI_TRANSPARENT, 63 MODE_TRANSLUCENT, 64 MODE_LIGHTS_OUT, 65 MODE_TRANSPARENT, 66 MODE_WARNING, 67 MODE_LIGHTS_OUT_TRANSPARENT 68 }) 69 @Retention(RetentionPolicy.SOURCE) 70 public @interface TransitionMode {} 71 72 public static final int LIGHTS_IN_DURATION = 250; 73 public static final int LIGHTS_OUT_DURATION = 1500; 74 public static final int BACKGROUND_DURATION = 200; 75 76 private final String mTag; 77 private final View mView; 78 protected final BarBackgroundDrawable mBarBackground; 79 80 private @TransitionMode int mMode; 81 private boolean mAlwaysOpaque = false; 82 BarTransitions(View view, int gradientResourceId)83 public BarTransitions(View view, int gradientResourceId) { 84 mTag = "BarTransitions." + view.getClass().getSimpleName(); 85 mView = view; 86 mBarBackground = new BarBackgroundDrawable(mView.getContext(), gradientResourceId); 87 mView.setBackground(mBarBackground); 88 } 89 destroy()90 public void destroy() { 91 // To be overridden 92 } 93 getMode()94 public int getMode() { 95 return mMode; 96 } 97 setAutoDim(boolean autoDim)98 public void setAutoDim(boolean autoDim) { 99 // Default is don't care. 100 } 101 102 /** 103 * @param alwaysOpaque if {@code true}, the bar's background will always be opaque, regardless 104 * of what mode it is currently set to. 105 */ setAlwaysOpaque(boolean alwaysOpaque)106 public void setAlwaysOpaque(boolean alwaysOpaque) { 107 mAlwaysOpaque = alwaysOpaque; 108 } 109 isAlwaysOpaque()110 public boolean isAlwaysOpaque() { 111 // Low-end devices do not support translucent modes, fallback to opaque 112 return mAlwaysOpaque; 113 } 114 transitionTo(int mode, boolean animate)115 public void transitionTo(int mode, boolean animate) { 116 if (isAlwaysOpaque() && (mode == MODE_SEMI_TRANSPARENT || mode == MODE_TRANSLUCENT 117 || mode == MODE_TRANSPARENT)) { 118 mode = MODE_OPAQUE; 119 } 120 if (isAlwaysOpaque() && (mode == MODE_LIGHTS_OUT_TRANSPARENT)) { 121 mode = MODE_LIGHTS_OUT; 122 } 123 if (mMode == mode) return; 124 int oldMode = mMode; 125 mMode = mode; 126 if (DEBUG) Log.d(mTag, String.format("%s -> %s animate=%s", 127 modeToString(oldMode), modeToString(mode), animate)); 128 onTransition(oldMode, mMode, animate); 129 } 130 onTransition(int oldMode, int newMode, boolean animate)131 protected void onTransition(int oldMode, int newMode, boolean animate) { 132 applyModeBackground(oldMode, newMode, animate); 133 } 134 applyModeBackground(int oldMode, int newMode, boolean animate)135 protected void applyModeBackground(int oldMode, int newMode, boolean animate) { 136 if (DEBUG) Log.d(mTag, String.format("applyModeBackground oldMode=%s newMode=%s animate=%s", 137 modeToString(oldMode), modeToString(newMode), animate)); 138 mBarBackground.applyModeBackground(oldMode, newMode, animate); 139 } 140 modeToString(int mode)141 public static String modeToString(int mode) { 142 if (mode == MODE_OPAQUE) return "MODE_OPAQUE"; 143 if (mode == MODE_SEMI_TRANSPARENT) return "MODE_SEMI_TRANSPARENT"; 144 if (mode == MODE_TRANSLUCENT) return "MODE_TRANSLUCENT"; 145 if (mode == MODE_LIGHTS_OUT) return "MODE_LIGHTS_OUT"; 146 if (mode == MODE_TRANSPARENT) return "MODE_TRANSPARENT"; 147 if (mode == MODE_WARNING) return "MODE_WARNING"; 148 if (mode == MODE_LIGHTS_OUT_TRANSPARENT) return "MODE_LIGHTS_OUT_TRANSPARENT"; 149 throw new IllegalArgumentException("Unknown mode " + mode); 150 } 151 finishAnimations()152 public void finishAnimations() { 153 mBarBackground.finishAnimation(); 154 } 155 isLightsOut(int mode)156 protected boolean isLightsOut(int mode) { 157 return mode == MODE_LIGHTS_OUT || mode == MODE_LIGHTS_OUT_TRANSPARENT; 158 } 159 160 protected static class BarBackgroundDrawable extends Drawable { 161 private final int mOpaque; 162 private final int mSemiTransparent; 163 private final int mTransparent; 164 private final int mWarning; 165 private final Drawable mGradient; 166 167 private int mMode = -1; 168 private boolean mAnimating; 169 private long mStartTime; 170 private long mEndTime; 171 172 private int mGradientAlpha; 173 private int mColor; 174 private float mOverrideAlpha = 1f; 175 private PorterDuffColorFilter mTintFilter; 176 private Paint mPaint = new Paint(); 177 178 private int mGradientAlphaStart; 179 private int mColorStart; 180 private Rect mFrame; 181 182 BarBackgroundDrawable(Context context, int gradientResourceId)183 public BarBackgroundDrawable(Context context, int gradientResourceId) { 184 final Resources res = context.getResources(); 185 if (DEBUG_COLORS) { 186 mOpaque = 0xff0000ff; 187 mSemiTransparent = 0x7f0000ff; 188 mTransparent = 0x2f0000ff; 189 mWarning = 0xffff0000; 190 } else { 191 mOpaque = SYSTEM_BAR_BACKGROUND_OPAQUE; 192 mSemiTransparent = context.getColor( 193 com.android.internal.R.color.system_bar_background_semi_transparent); 194 mTransparent = SYSTEM_BAR_BACKGROUND_TRANSPARENT; 195 mWarning = getColorAttrDefaultColor(context, android.R.attr.colorError, 0); 196 } 197 mGradient = context.getDrawable(gradientResourceId); 198 } 199 setFrame(Rect frame)200 public void setFrame(Rect frame) { 201 mFrame = frame; 202 } 203 setOverrideAlpha(float overrideAlpha)204 public void setOverrideAlpha(float overrideAlpha) { 205 mOverrideAlpha = overrideAlpha; 206 invalidateSelf(); 207 } 208 getOverrideAlpha()209 public float getOverrideAlpha() { 210 return mOverrideAlpha; 211 } 212 getColor()213 public int getColor() { 214 return mColor; 215 } 216 getFrame()217 public Rect getFrame() { 218 return mFrame; 219 } 220 221 @Override setAlpha(int alpha)222 public void setAlpha(int alpha) { 223 // noop 224 } 225 226 @Override setColorFilter(ColorFilter colorFilter)227 public void setColorFilter(ColorFilter colorFilter) { 228 // noop 229 } 230 231 @Override setTint(int color)232 public void setTint(int color) { 233 PorterDuff.Mode targetMode = mTintFilter == null ? Mode.SRC_IN : 234 mTintFilter.getMode(); 235 if (mTintFilter == null || mTintFilter.getColor() != color) { 236 mTintFilter = new PorterDuffColorFilter(color, targetMode); 237 } 238 invalidateSelf(); 239 } 240 241 @Override setTintMode(Mode tintMode)242 public void setTintMode(Mode tintMode) { 243 int targetColor = mTintFilter == null ? 0 : mTintFilter.getColor(); 244 if (mTintFilter == null || mTintFilter.getMode() != tintMode) { 245 mTintFilter = new PorterDuffColorFilter(targetColor, tintMode); 246 } 247 invalidateSelf(); 248 } 249 250 @Override onBoundsChange(Rect bounds)251 protected void onBoundsChange(Rect bounds) { 252 super.onBoundsChange(bounds); 253 mGradient.setBounds(bounds); 254 } 255 applyModeBackground(int oldMode, int newMode, boolean animate)256 public void applyModeBackground(int oldMode, int newMode, boolean animate) { 257 if (mMode == newMode) return; 258 mMode = newMode; 259 mAnimating = animate; 260 if (animate) { 261 long now = SystemClock.elapsedRealtime(); 262 mStartTime = now; 263 mEndTime = now + BACKGROUND_DURATION; 264 mGradientAlphaStart = mGradientAlpha; 265 mColorStart = mColor; 266 } 267 invalidateSelf(); 268 } 269 270 @Override getOpacity()271 public int getOpacity() { 272 return PixelFormat.TRANSLUCENT; 273 } 274 finishAnimation()275 public void finishAnimation() { 276 if (mAnimating) { 277 mAnimating = false; 278 invalidateSelf(); 279 } 280 } 281 282 @Override draw(Canvas canvas)283 public void draw(Canvas canvas) { 284 int targetGradientAlpha = 0, targetColor = 0; 285 if (mMode == MODE_WARNING) { 286 targetColor = mWarning; 287 } else if (mMode == MODE_TRANSLUCENT) { 288 targetColor = mSemiTransparent; 289 } else if (mMode == MODE_SEMI_TRANSPARENT) { 290 targetColor = mSemiTransparent; 291 } else if (mMode == MODE_TRANSPARENT || mMode == MODE_LIGHTS_OUT_TRANSPARENT) { 292 targetColor = mTransparent; 293 } else { 294 targetColor = mOpaque; 295 } 296 297 if (!mAnimating) { 298 mColor = targetColor; 299 mGradientAlpha = targetGradientAlpha; 300 } else { 301 final long now = SystemClock.elapsedRealtime(); 302 if (now >= mEndTime) { 303 mAnimating = false; 304 mColor = targetColor; 305 mGradientAlpha = targetGradientAlpha; 306 } else { 307 final float t = (now - mStartTime) / (float)(mEndTime - mStartTime); 308 final float v = Math.max(0, Math.min( 309 Interpolators.LINEAR.getInterpolation(t), 1)); 310 mGradientAlpha = (int)(v * targetGradientAlpha + mGradientAlphaStart * (1 - v)); 311 mColor = Color.argb( 312 (int) (v * Color.alpha(targetColor) + Color.alpha(mColorStart) * (1 313 - v)), 314 (int) (v * Color.red(targetColor) + Color.red(mColorStart) * (1 - v)), 315 (int) (v * Color.green(targetColor) + Color.green(mColorStart) * (1 316 - v)), 317 (int) (v * Color.blue(targetColor) + Color.blue(mColorStart) * (1 318 - v))); 319 } 320 } 321 if (mGradientAlpha > 0) { 322 mGradient.setAlpha(mGradientAlpha); 323 mGradient.draw(canvas); 324 } 325 326 if (Color.alpha(mColor) > 0) { 327 mPaint.setColor(mColor); 328 if (mTintFilter != null) { 329 mPaint.setColorFilter(mTintFilter); 330 } 331 mPaint.setAlpha((int) (Color.alpha(mColor) * mOverrideAlpha)); 332 if (mFrame != null) { 333 canvas.drawRect(mFrame, mPaint); 334 } else { 335 canvas.drawPaint(mPaint); 336 } 337 } 338 if (mAnimating) { 339 invalidateSelf(); // keep going 340 } 341 } 342 } 343 344 /** Get color styled attribute {@code attr}, default to {@code defValue} if not found. */ 345 @ColorInt getColorAttrDefaultColor(Context context, int attr, @ColorInt int defValue)346 public static int getColorAttrDefaultColor(Context context, int attr, @ColorInt int defValue) { 347 TypedArray ta = context.obtainStyledAttributes(new int[] {attr}); 348 @ColorInt int colorAccent = ta.getColor(0, defValue); 349 ta.recycle(); 350 return colorAccent; 351 } 352 } 353