• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.wm.shell.startingsurface;
18 
19 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
20 
21 import android.animation.Animator;
22 import android.animation.ValueAnimator;
23 import android.annotation.ColorInt;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.content.res.Resources;
27 import android.graphics.Bitmap;
28 import android.graphics.Canvas;
29 import android.graphics.Color;
30 import android.graphics.ColorFilter;
31 import android.graphics.Matrix;
32 import android.graphics.Paint;
33 import android.graphics.Path;
34 import android.graphics.PixelFormat;
35 import android.graphics.Rect;
36 import android.graphics.drawable.AdaptiveIconDrawable;
37 import android.graphics.drawable.Animatable;
38 import android.graphics.drawable.Drawable;
39 import android.os.Handler;
40 import android.os.Trace;
41 import android.util.PathParser;
42 import android.window.SplashScreenView;
43 
44 import com.android.internal.R;
45 
46 /**
47  * Creating a lightweight Drawable object used for splash screen.
48  *
49  * @hide
50  */
51 public class SplashscreenIconDrawableFactory {
52 
53     /**
54      * @return An array containing the foreground drawable at index 0 and if needed a background
55      * drawable at index 1.
56      */
makeIconDrawable(@olorInt int backgroundColor, @ColorInt int themeColor, @NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize, Handler splashscreenWorkerHandler)57     static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor,
58             @NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize,
59             Handler splashscreenWorkerHandler) {
60         Drawable foreground;
61         Drawable background = null;
62         boolean drawBackground =
63                 backgroundColor != Color.TRANSPARENT && backgroundColor != themeColor;
64 
65         if (foregroundDrawable instanceof Animatable) {
66             foreground = new AnimatableIconAnimateListener(foregroundDrawable);
67         } else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
68             // If the icon is Adaptive, we already use the icon background.
69             drawBackground = false;
70             foreground = new ImmobileIconDrawable(foregroundDrawable,
71                     srcIconSize, iconSize, splashscreenWorkerHandler);
72         } else {
73             // Adaptive icon don't handle transparency so we draw the background of the adaptive
74             // icon with the same color as the window background color instead of using two layers
75             foreground = new ImmobileIconDrawable(
76                     new AdaptiveForegroundDrawable(foregroundDrawable),
77                     srcIconSize, iconSize, splashscreenWorkerHandler);
78         }
79 
80         if (drawBackground) {
81             background = new MaskBackgroundDrawable(backgroundColor);
82         }
83 
84         return new Drawable[]{foreground, background};
85     }
86 
makeLegacyIconDrawable(@onNull Drawable iconDrawable, int srcIconSize, int iconSize, Handler splashscreenWorkerHandler)87     static Drawable[] makeLegacyIconDrawable(@NonNull Drawable iconDrawable, int srcIconSize,
88             int iconSize, Handler splashscreenWorkerHandler) {
89         return new Drawable[]{new ImmobileIconDrawable(iconDrawable, srcIconSize, iconSize,
90                 splashscreenWorkerHandler)};
91     }
92 
93     /**
94      * Drawable pre-drawing the scaled icon in a separate thread to increase the speed of the
95      * final drawing.
96      */
97     private static class ImmobileIconDrawable extends Drawable {
98         private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG
99                 | Paint.FILTER_BITMAP_FLAG);
100         private final Matrix mMatrix = new Matrix();
101         private Bitmap mIconBitmap;
102 
ImmobileIconDrawable(Drawable drawable, int srcIconSize, int iconSize, Handler splashscreenWorkerHandler)103         ImmobileIconDrawable(Drawable drawable, int srcIconSize, int iconSize,
104                 Handler splashscreenWorkerHandler) {
105             final float scale = (float) iconSize / srcIconSize;
106             mMatrix.setScale(scale, scale);
107             splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, srcIconSize));
108         }
109 
preDrawIcon(Drawable drawable, int size)110         private void preDrawIcon(Drawable drawable, int size) {
111             synchronized (mPaint) {
112                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "preDrawIcon");
113                 mIconBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
114                 final Canvas canvas = new Canvas(mIconBitmap);
115                 drawable.setBounds(0, 0, size, size);
116                 drawable.draw(canvas);
117                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
118             }
119         }
120 
121         @Override
draw(Canvas canvas)122         public void draw(Canvas canvas) {
123             synchronized (mPaint) {
124                 if (mIconBitmap != null) {
125                     canvas.drawBitmap(mIconBitmap, mMatrix, mPaint);
126                 } else {
127                     // this shouldn't happen, but if it really happen, invalidate self to wait
128                     // for bitmap to be ready.
129                     invalidateSelf();
130                 }
131             }
132         }
133 
134         @Override
setAlpha(int alpha)135         public void setAlpha(int alpha) {
136         }
137 
138         @Override
setColorFilter(ColorFilter colorFilter)139         public void setColorFilter(ColorFilter colorFilter) {
140         }
141 
142         @Override
getOpacity()143         public int getOpacity() {
144             return 1;
145         }
146     }
147 
148     /**
149      * Base class the draw a background clipped by the system mask.
150      */
151     public static class MaskBackgroundDrawable extends Drawable {
152         private static final float MASK_SIZE = AdaptiveIconDrawable.MASK_SIZE;
153         private static final float EXTRA_INSET_PERCENTAGE = 1 / 4f;
154         static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);
155         /**
156          * Clip path defined in R.string.config_icon_mask.
157          */
158         private static Path sMask;
159         private final Path mMaskScaleOnly;
160         private final Matrix mMaskMatrix;
161 
162         @Nullable
163         private final Paint mBackgroundPaint;
164 
MaskBackgroundDrawable(@olorInt int backgroundColor)165         public MaskBackgroundDrawable(@ColorInt int backgroundColor) {
166             final Resources r = Resources.getSystem();
167             sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
168             Path mask = new Path(sMask);
169             mMaskScaleOnly = new Path(mask);
170             mMaskMatrix = new Matrix();
171             if (backgroundColor != Color.TRANSPARENT) {
172                 mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG
173                         | Paint.FILTER_BITMAP_FLAG);
174                 mBackgroundPaint.setColor(backgroundColor);
175                 mBackgroundPaint.setStyle(Paint.Style.FILL);
176             } else {
177                 mBackgroundPaint = null;
178             }
179         }
180 
181         @Override
onBoundsChange(Rect bounds)182         protected void onBoundsChange(Rect bounds) {
183             if (bounds.isEmpty()) {
184                 return;
185             }
186             updateLayerBounds(bounds);
187         }
188 
updateLayerBounds(Rect bounds)189         protected void updateLayerBounds(Rect bounds) {
190             // reset everything that depends on the view bounds
191             mMaskMatrix.setScale(bounds.width() / MASK_SIZE, bounds.height() / MASK_SIZE);
192             sMask.transform(mMaskMatrix, mMaskScaleOnly);
193         }
194 
195         @Override
draw(Canvas canvas)196         public void draw(Canvas canvas) {
197             canvas.clipPath(mMaskScaleOnly);
198             if (mBackgroundPaint != null) {
199                 canvas.drawPath(mMaskScaleOnly, mBackgroundPaint);
200             }
201         }
202 
203         @Override
setAlpha(int alpha)204         public void setAlpha(int alpha) {
205             if (mBackgroundPaint != null) {
206                 mBackgroundPaint.setAlpha(alpha);
207             }
208         }
209 
210         @Override
getOpacity()211         public int getOpacity() {
212             return PixelFormat.RGBA_8888;
213         }
214 
215         @Override
setColorFilter(ColorFilter colorFilter)216         public void setColorFilter(ColorFilter colorFilter) {
217         }
218     }
219 
220     private static class AdaptiveForegroundDrawable extends MaskBackgroundDrawable {
221 
222         @NonNull
223         protected final Drawable mForegroundDrawable;
224         private final Rect mTmpOutRect = new Rect();
225 
AdaptiveForegroundDrawable(@onNull Drawable foregroundDrawable)226         AdaptiveForegroundDrawable(@NonNull Drawable foregroundDrawable) {
227             super(Color.TRANSPARENT);
228             mForegroundDrawable = foregroundDrawable;
229         }
230 
231         @Override
updateLayerBounds(Rect bounds)232         protected void updateLayerBounds(Rect bounds) {
233             super.updateLayerBounds(bounds);
234             int cX = bounds.width() / 2;
235             int cY = bounds.height() / 2;
236 
237             int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
238             int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
239             final Rect outRect = mTmpOutRect;
240             outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);
241             if (mForegroundDrawable != null) {
242                 mForegroundDrawable.setBounds(outRect);
243             }
244             invalidateSelf();
245         }
246 
247         @Override
draw(Canvas canvas)248         public void draw(Canvas canvas) {
249             super.draw(canvas);
250             mForegroundDrawable.draw(canvas);
251         }
252 
253         @Override
setColorFilter(ColorFilter colorFilter)254         public void setColorFilter(ColorFilter colorFilter) {
255             mForegroundDrawable.setColorFilter(colorFilter);
256         }
257     }
258 
259     /**
260      * A lightweight AdaptiveIconDrawable which support foreground to be Animatable, and keep this
261      * drawable masked by config_icon_mask.
262      */
263     private static class AnimatableIconAnimateListener extends AdaptiveForegroundDrawable
264             implements SplashScreenView.IconAnimateListener {
265         private Animatable mAnimatableIcon;
266         private Animator mIconAnimator;
267         private boolean mAnimationTriggered;
268 
AnimatableIconAnimateListener(@onNull Drawable foregroundDrawable)269         AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable) {
270             super(foregroundDrawable);
271             mForegroundDrawable.setCallback(mCallback);
272         }
273 
274         @Override
prepareAnimate(long duration, Runnable startListener)275         public boolean prepareAnimate(long duration, Runnable startListener) {
276             mAnimatableIcon = (Animatable) mForegroundDrawable;
277             mIconAnimator = ValueAnimator.ofInt(0, 1);
278             mIconAnimator.setDuration(duration);
279             mIconAnimator.addListener(new Animator.AnimatorListener() {
280                 @Override
281                 public void onAnimationStart(Animator animation) {
282                     if (startListener != null) {
283                         startListener.run();
284                     }
285                     mAnimatableIcon.start();
286                 }
287 
288                 @Override
289                 public void onAnimationEnd(Animator animation) {
290                     mAnimatableIcon.stop();
291                 }
292 
293                 @Override
294                 public void onAnimationCancel(Animator animation) {
295                     mAnimatableIcon.stop();
296                 }
297 
298                 @Override
299                 public void onAnimationRepeat(Animator animation) {
300                     // do not repeat
301                     mAnimatableIcon.stop();
302                 }
303             });
304             return true;
305         }
306 
307         private final Callback mCallback = new Callback() {
308             @Override
309             public void invalidateDrawable(@NonNull Drawable who) {
310                 invalidateSelf();
311             }
312 
313             @Override
314             public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
315                 scheduleSelf(what, when);
316             }
317 
318             @Override
319             public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
320                 unscheduleSelf(what);
321             }
322         };
323 
ensureAnimationStarted()324         private void ensureAnimationStarted() {
325             if (mAnimationTriggered) {
326                 return;
327             }
328             if (mIconAnimator != null && !mIconAnimator.isRunning()) {
329                 mIconAnimator.start();
330             }
331             mAnimationTriggered = true;
332         }
333 
334         @Override
draw(Canvas canvas)335         public void draw(Canvas canvas) {
336             ensureAnimationStarted();
337             super.draw(canvas);
338         }
339     }
340 }
341