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