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