• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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