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