diff options
| author | 2021-02-25 14:32:10 +0800 | |
|---|---|---|
| committer | 2021-03-19 11:23:56 +0800 | |
| commit | a4c6d8f82897cc03cc71a5301874d3e6fee63eab (patch) | |
| tree | 42337e0132aa8d91393ce64e61398d0fa8fa031e /libs | |
| parent | df8f1012446cb8f34731b944745c93fd69f392b8 (diff) | |
Add API windowSplashScreenIconBackground(12/N)
Add new API for developer to customize starting window, set a
background color behind the icon.
Also masking the AVD and icon background like AdaptiveIconDrawable.
Ref doc: go/improved_app_launch_animations
Bug: 73289295
Test: atest SplashscreenTests
Change-Id: Ibba49ecc3ca020022cf775fed302e383908be327
Diffstat (limited to 'libs')
3 files changed, 248 insertions, 8 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 3f9c2717731a..3f46fee26510 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -16,6 +16,7 @@ package com.android.wm.shell.startingsurface; +import android.annotation.ColorInt; import android.annotation.NonNull; import android.app.ActivityThread; import android.content.Context; @@ -149,14 +150,16 @@ public class SplashscreenContentDrawer { .setThemeDrawable(themeBGDrawable) .setIconDrawable(iconDrawable) .setIconAnimationDuration(animationDuration) - .setBrandingDrawable(attrs.mBrandingImage).build(); + .setBrandingDrawable(attrs.mBrandingImage) + .setIconBackground(attrs.mIconBgColor).build(); } - private static class SplashScreenWindowAttrs { + static class SplashScreenWindowAttrs { private int mWindowBgResId = 0; private int mWindowBgColor = Color.TRANSPARENT; private Drawable mReplaceIcon = null; private Drawable mBrandingImage = null; + private int mIconBgColor = Color.TRANSPARENT; private int mAnimationDuration = 0; static SplashScreenWindowAttrs createWindowAttrs(Context context) { @@ -172,6 +175,8 @@ public class SplashscreenContentDrawer { R.styleable.Window_windowSplashScreenAnimationDuration, 0); attrs.mBrandingImage = typedArray.getDrawable( R.styleable.Window_windowSplashScreenBrandingImage); + attrs.mIconBgColor = typedArray.getColor( + R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT); typedArray.recycle(); if (DEBUG) { Slog.d(TAG, "window attributes color: " @@ -189,6 +194,7 @@ public class SplashscreenContentDrawer { private int mIconAnimationDuration; private Context mContext; private Drawable mBrandingDrawable; + private @ColorInt int mIconBackground; // result private boolean mBuildComplete = false; @@ -221,6 +227,12 @@ public class SplashscreenContentDrawer { return this; } + StartingWindowViewBuilder setIconBackground(int color) { + mIconBackground = color; + mBuildComplete = false; + return this; + } + StartingWindowViewBuilder setContext(Context context) { mContext = context; mBuildComplete = false; @@ -244,7 +256,9 @@ public class SplashscreenContentDrawer { if (DEBUG) { Slog.d(TAG, "The icon is not an AdaptiveIconDrawable"); } - mFinalIconDrawable = mIconDrawable; + mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable( + mIconBackground != Color.TRANSPARENT + ? mIconBackground : mThemeColor, mIconDrawable); } final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0; mCachedResult = fillViewWithIcon(mContext, iconSize, mFinalIconDrawable); @@ -252,6 +266,12 @@ public class SplashscreenContentDrawer { return mCachedResult; } + private void createIconDrawable(Drawable iconDrawable) { + mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable( + mIconBackground != Color.TRANSPARENT + ? mIconBackground : mThemeColor, iconDrawable); + } + private void processThemeColor() { final DrawableColorTester themeBGTester = new DrawableColorTester(mThemeBGDrawable, true /* filterTransparent */); @@ -307,8 +327,7 @@ public class SplashscreenContentDrawer { } // Using AdaptiveIconDrawable here can help keep the shape consistent with the // current settings. - mFinalIconDrawable = new AdaptiveIconDrawable( - new ColorDrawable(mThemeColor), iconForeground); + createIconDrawable(iconForeground); // Reference AdaptiveIcon description, outer is 108 and inner is 72, so we // should enlarge the size 108/72 if we only draw adaptiveIcon's foreground. if (foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD) { @@ -318,7 +337,11 @@ public class SplashscreenContentDrawer { if (DEBUG) { Slog.d(TAG, "makeSplashScreenContentView: draw whole icon"); } - mFinalIconDrawable = adaptiveIconDrawable; + if (mIconBackground != Color.TRANSPARENT) { + createIconDrawable(adaptiveIconDrawable); + } else { + mFinalIconDrawable = adaptiveIconDrawable; + } } return true; } @@ -326,7 +349,8 @@ public class SplashscreenContentDrawer { private SplashScreenView fillViewWithIcon(Context context, int iconSize, Drawable iconDrawable) { final SplashScreenView.Builder builder = new SplashScreenView.Builder(context); - builder.setIconSize(iconSize).setBackgroundColor(mThemeColor); + builder.setIconSize(iconSize).setBackgroundColor(mThemeColor) + .setIconBackground(mIconBackground); if (iconDrawable != null) { builder.setCenterViewDrawable(iconDrawable); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java new file mode 100644 index 000000000000..8626dbc6d724 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.startingsurface; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.annotation.ColorInt; +import android.annotation.NonNull; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.SystemClock; +import android.util.PathParser; +import android.window.SplashScreenView; + +import com.android.internal.R; + +import java.util.function.Consumer; + +/** + * Creating a lightweight Drawable object used for splash screen. + * @hide + */ +public class SplashscreenIconDrawableFactory { + + static Drawable makeIconDrawable(@ColorInt int backgroundColor, + @NonNull Drawable foregroundDrawable) { + if (foregroundDrawable instanceof Animatable) { + return new AnimatableIconDrawable(backgroundColor, foregroundDrawable); + } else { + // TODO make a light weight drawable instead of AdaptiveIconDrawable + return new AdaptiveIconDrawable(new ColorDrawable(backgroundColor), foregroundDrawable); + } + } + + /** + * A lightweight AdaptiveIconDrawable which support foreground to be Animatable, and keep this + * drawable masked by config_icon_mask. + * @hide + */ + private static class AnimatableIconDrawable extends SplashScreenView.SplashscreenIconDrawable { + private static final float MASK_SIZE = AdaptiveIconDrawable.MASK_SIZE; + private static final float EXTRA_INSET_PERCENTAGE = 1 / 4f; + private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE); + private final Rect mTmpOutRect = new Rect(); + /** + * Clip path defined in R.string.config_icon_mask. + */ + private static Path sMask; + + /** + * Scaled mask based on the view bounds. + */ + private final Path mMask; + private final Path mMaskScaleOnly; + private final Matrix mMaskMatrix; + private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Drawable mForegroundDrawable; + private Animatable mAnimatableIcon; + private Animator mIconAnimator; + private boolean mAnimationTriggered; + private long mIconAnimationStart; + + AnimatableIconDrawable(@ColorInt int backgroundColor, Drawable foregroundDrawable) { + mForegroundDrawable = foregroundDrawable; + final Resources r = Resources.getSystem(); + sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask)); + mMask = new Path(sMask); + mMaskScaleOnly = new Path(mMask); + mMaskMatrix = new Matrix(); + mPaint.setColor(backgroundColor); + mPaint.setStyle(Paint.Style.FILL); + if (mForegroundDrawable != null) { + mForegroundDrawable.setCallback(mCallback); + } + } + + @Override + protected boolean prepareAnimate(long duration, Consumer<Long> startListener) { + mAnimatableIcon = (Animatable) mForegroundDrawable; + mIconAnimator = ValueAnimator.ofInt(0, 1); + mIconAnimator.setDuration(duration); + mIconAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + mIconAnimationStart = SystemClock.uptimeMillis(); + if (startListener != null) { + startListener.accept(mIconAnimationStart); + } + mAnimatableIcon.start(); + } + + @Override + public void onAnimationEnd(Animator animation) { + mAnimatableIcon.stop(); + } + + @Override + public void onAnimationCancel(Animator animation) { + mAnimatableIcon.stop(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + // do not repeat + mAnimatableIcon.stop(); + } + }); + return true; + } + + @Override + protected void onBoundsChange(Rect bounds) { + if (bounds.isEmpty()) { + return; + } + updateLayerBounds(bounds); + } + + private final Callback mCallback = new Callback() { + @Override + public void invalidateDrawable(@NonNull Drawable who) { + invalidateSelf(); + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + unscheduleSelf(what); + } + }; + + private void updateLayerBounds(Rect bounds) { + int cX = bounds.width() / 2; + int cY = bounds.height() / 2; + + int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2)); + int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2)); + final Rect outRect = mTmpOutRect; + outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight); + + if (mForegroundDrawable != null) { + mForegroundDrawable.setBounds(outRect); + } + // reset everything that depends on the view bounds + mMaskMatrix.setScale(bounds.width() / MASK_SIZE, bounds.height() / MASK_SIZE); + sMask.transform(mMaskMatrix, mMaskScaleOnly); + invalidateSelf(); + } + + private void ensureAnimationStarted() { + if (mAnimationTriggered) { + return; + } + if (mIconAnimator != null && !mIconAnimator.isRunning()) { + mIconAnimator.start(); + } + mAnimationTriggered = true; + } + + @Override + public void draw(Canvas canvas) { + if (mMaskScaleOnly != null) { + canvas.drawPath(mMaskScaleOnly, mPaint); + } + if (mForegroundDrawable != null) { + ensureAnimationStarted(); + mForegroundDrawable.draw(canvas); + } + } + + @Override + public void setAlpha(int alpha) { + mPaint.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + if (mForegroundDrawable != null) { + mForegroundDrawable.setColorFilter(colorFilter); + } + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 9212c4b86105..b592121e98c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -362,7 +362,6 @@ public class StartingSurfaceDrawer { final StartingWindowRecord record = mStartingWindowRecords.get(taskId); if (record != null) { record.setSplashScreenView(splashScreenView); - splashScreenView.startIntroAnimation(); } } |