From cb81f1716da2b37f25b38d3bb76ef1a6a3967c1e Mon Sep 17 00:00:00 2001 From: Vadim Caen Date: Fri, 5 Mar 2021 23:01:25 +0100 Subject: [PATCH 1/2] Display Splashscreen AVD in a SurfaceView If the splashscreen uses an animated icon, we need to display it in a surface view to avoid any jank if the view is passed to the client. Test: CtsWindowManagerDeviceTestCases:SplashscreenTests#testHandleExitIconAnimatingActivity Bug: 181757194 Change-Id: I7e2745a68626849374f5ebb9011c2e8c3b3b92c3 --- core/java/android/app/ActivityThread.java | 4 +- core/java/android/window/SplashScreen.java | 12 +- .../java/android/window/SplashScreenView.java | 173 +++++++++++++++--- .../StartingSurfaceDrawer.java | 1 + 4 files changed, 164 insertions(+), 26 deletions(-) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 92bdda3308e77..eb0a7b4076ee0 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -73,8 +73,8 @@ import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.ApplicationInfoFlags; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; @@ -4108,7 +4108,7 @@ public final class ActivityThread extends ClientTransactionHandler } synchronized (this) { if (mSplashScreenGlobal != null) { - mSplashScreenGlobal.dispatchOnExitAnimation(r.token, v); + mSplashScreenGlobal.handOverSplashScreenView(r.token, v); } } } diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java index 7d222dbe7bd22..42a58fb65e391 100644 --- a/core/java/android/window/SplashScreen.java +++ b/core/java/android/window/SplashScreen.java @@ -220,7 +220,13 @@ public interface SplashScreen { } } - public void dispatchOnExitAnimation(IBinder token, SplashScreenView view) { + public void handOverSplashScreenView(@NonNull IBinder token, + @NonNull SplashScreenView splashScreenView) { + transferSurface(splashScreenView); + dispatchOnExitAnimation(token, splashScreenView); + } + + private void dispatchOnExitAnimation(IBinder token, SplashScreenView view) { synchronized (mGlobalLock) { final SplashScreenImpl impl = findImpl(token); if (impl == null) { @@ -240,5 +246,9 @@ public interface SplashScreen { return impl != null && impl.mExitAnimationListener != null; } } + + private void transferSurface(@NonNull SplashScreenView splashScreenView) { + splashScreenView.transferSurface(); + } } } diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 6d7163909492c..000dfb2bfa594 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -30,6 +30,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -38,13 +39,17 @@ import android.os.Parcelable; import android.os.Trace; import android.util.AttributeSet; import android.util.Log; +import android.view.Gravity; import android.view.LayoutInflater; +import android.view.SurfaceControlViewHost; +import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowInsetsController; import android.view.WindowManager; import android.widget.FrameLayout; +import android.widget.ImageView; import com.android.internal.R; import com.android.internal.policy.DecorView; @@ -92,6 +97,14 @@ public final class SplashScreenView extends FrameLayout { // The host activity when transfer view to it. private Activity mHostActivity; + + @Nullable + private SurfaceControlViewHost.SurfacePackage mSurfacePackageCopy; + @Nullable + private SurfaceControlViewHost.SurfacePackage mSurfacePackage; + @Nullable + private SurfaceView mSurfaceView; + // cache original window and status private Window mWindow; private int mAppWindowFlags; @@ -114,6 +127,7 @@ public final class SplashScreenView extends FrameLayout { private @ColorInt int mIconBackground; private Bitmap mParceledIconBitmap; private Drawable mIconDrawable; + private SurfaceControlViewHost.SurfacePackage mSurfacePackage; private int mBrandingImageWidth; private int mBrandingImageHeight; private Drawable mBrandingDrawable; @@ -133,7 +147,10 @@ public final class SplashScreenView extends FrameLayout { mIconSize = parcelable.getIconSize(); mBackgroundColor = parcelable.getBackgroundColor(); mIconBackground = parcelable.getIconBackground(); - if (parcelable.mIconBitmap != null) { + mSurfacePackage = parcelable.mSurfacePackage; + if (mSurfacePackage == null && parcelable.mIconBitmap != null) { + // We only create a Bitmap copies of immobile icons since animated icon are using + // a surface view mIconDrawable = new BitmapDrawable(mContext.getResources(), parcelable.mIconBitmap); mParceledIconBitmap = parcelable.mIconBitmap; } @@ -145,6 +162,9 @@ public final class SplashScreenView extends FrameLayout { } mIconAnimationStart = Instant.ofEpochMilli(parcelable.mIconAnimationStartMillis); mIconAnimationDuration = Duration.ofMillis(parcelable.mIconAnimationDurationMillis); + if (DEBUG) { + Log.d(TAG, String.format("Building from parcel drawable: %s", mIconDrawable)); + } return this; } @@ -167,7 +187,7 @@ public final class SplashScreenView extends FrameLayout { /** * Set the Drawable object to fill the center view. */ - public Builder setCenterViewDrawable(Drawable drawable) { + public Builder setCenterViewDrawable(@Nullable Drawable drawable) { mIconDrawable = drawable; return this; } @@ -191,7 +211,7 @@ public final class SplashScreenView extends FrameLayout { /** * Set the Drawable object and size for the branding view. */ - public Builder setBrandingDrawable(Drawable branding, int width, int height) { + public Builder setBrandingDrawable(@Nullable Drawable branding, int width, int height) { mBrandingDrawable = branding; mBrandingImageWidth = width; mBrandingImageHeight = height; @@ -209,22 +229,30 @@ public final class SplashScreenView extends FrameLayout { view.mInitBackgroundColor = mBackgroundColor; view.mInitIconBackgroundColor = mIconBackground; view.setBackgroundColor(mBackgroundColor); - view.mIconView = view.findViewById(R.id.splashscreen_icon_view); + view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view); + // center icon - if (mIconSize != 0) { - final ViewGroup.LayoutParams params = view.mIconView.getLayoutParams(); - params.width = mIconSize; - params.height = mIconSize; - view.mIconView.setLayoutParams(params); - } - if (mIconDrawable != null) { - view.mIconView.setBackground(mIconDrawable); + if (mIconDrawable instanceof SplashScreenView.IconAnimateListener + || mSurfacePackage != null) { + view.mIconView = createSurfaceView(view); view.initIconAnimation(mIconDrawable, mIconAnimationDuration != null ? mIconAnimationDuration.toMillis() : 0); + view.mIconAnimationStart = mIconAnimationStart; + view.mIconAnimationDuration = mIconAnimationDuration; + } else { + view.mIconView = view.findViewById(R.id.splashscreen_icon_view); + if (mIconSize != 0) { + final ViewGroup.LayoutParams params = view.mIconView.getLayoutParams(); + params.width = mIconSize; + params.height = mIconSize; + view.mIconView.setLayoutParams(params); + if (mIconDrawable != null) { + view.mIconView.setBackground(mIconDrawable); + } + } } - view.mIconAnimationStart = mIconAnimationStart; - view.mIconAnimationDuration = mIconAnimationDuration; + if (mParceledIconBitmap != null) { view.mParceledIconBitmap = mParceledIconBitmap; } @@ -242,14 +270,60 @@ public final class SplashScreenView extends FrameLayout { view.mParceledBrandingBitmap = mParceledBrandingBitmap; } if (DEBUG) { - Log.d(TAG, " build " + view + " Icon: view: " + view.mIconView + " drawable: " - + mIconDrawable + " size: " + mIconSize + "\n Branding: view: " - + view.mBrandingImageView + " drawable: " + mBrandingDrawable - + " size w: " + mBrandingImageWidth + " h: " + mBrandingImageHeight); + Log.d(TAG, "Build " + view + + "\nIcon: view: " + view.mIconView + " drawable: " + + mIconDrawable + " size: " + mIconSize + + "\nBranding: view: " + view.mBrandingImageView + " drawable: " + + mBrandingDrawable + " size w: " + mBrandingImageWidth + " h: " + + mBrandingImageHeight); } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return view; } + + private SurfaceView createSurfaceView(@NonNull SplashScreenView view) { + final SurfaceView surfaceView = new SurfaceView(view.getContext()); + if (mSurfacePackage == null) { + if (DEBUG) { + Log.d(TAG, + "Creating Original SurfacePackage. SurfaceView: " + surfaceView); + } + + SurfaceControlViewHost viewHost = new SurfaceControlViewHost(mContext, + mContext.getDisplay(), + surfaceView.getHostToken()); + ImageView imageView = new ImageView(mContext); + imageView.setBackground(mIconDrawable); + viewHost.setView(imageView, mIconSize, mIconSize); + SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage(); + surfaceView.setChildSurfacePackage(surfacePackage); + view.mSurfacePackage = surfacePackage; + view.mSurfacePackageCopy = new SurfaceControlViewHost.SurfacePackage( + surfacePackage); + } else { + if (DEBUG) { + Log.d(TAG, "Using copy of SurfacePackage in the client"); + } + view.mSurfacePackage = mSurfacePackage; + } + if (mIconSize != 0) { + LayoutParams lp = new FrameLayout.LayoutParams(mIconSize, mIconSize); + lp.gravity = Gravity.CENTER; + surfaceView.setLayoutParams(lp); + if (DEBUG) { + Log.d(TAG, "Icon size " + mIconSize); + } + } + + // We ensure that we can blend the alpha of the surface view with the SplashScreenView + surfaceView.setUseAlpha(); + surfaceView.setZOrderOnTop(true); + surfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); + + view.addView(surfaceView); + view.mSurfaceView = surfaceView; + return surfaceView; + } } /** @hide */ @@ -297,6 +371,35 @@ public final class SplashScreenView extends FrameLayout { return mRevealAnimationSupported; } + /** + * Called when this {@link SplashScreenView} has been copied to be transferred to the client. + * + * @hide + */ + public void onCopied() { + if (mSurfaceView == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "Setting SurfaceView's SurfacePackage to null."); + } + // If we don't release the surface package, the surface will be reparented to this + // surface view. So once it's copied into the client process, we release it. + mSurfacePackage.release(); + mSurfacePackage = null; + } + + @Override + public void setAlpha(float alpha) { + super.setAlpha(alpha); + + // The surface view's alpha is not multiplied with the containing view's alpha, so we + // manually do it here + if (mSurfaceView != null) { + mSurfaceView.setAlpha(mSurfaceView.getAlpha() * alpha); + } + } + /** * Returns the duration of the icon animation if icon is animatable. * @@ -316,6 +419,22 @@ public final class SplashScreenView extends FrameLayout { return mIconAnimationStart; } + + void transferSurface() { + if (mSurfacePackage == null) { + return; + } + if (DEBUG) { + mSurfacePackage.getSurfaceControl().addOnReparentListener( + (transaction, parent) -> Log.e(TAG, + String.format("SurfacePackage'surface reparented.\n Parent: %s", + parent), new Throwable())); + Log.d(TAG, "Transferring surface " + mSurfaceView.toString()); + } + mSurfaceView.setChildSurfacePackage(mSurfacePackage); + + } + void initIconAnimation(Drawable iconDrawable, long duration) { if (!(iconDrawable instanceof IconAnimateListener)) { return; @@ -477,7 +596,7 @@ public final class SplashScreenView extends FrameLayout { private int mBackgroundColor; private int mIconBackground; - private Bitmap mIconBitmap; + private Bitmap mIconBitmap = null; private int mBrandingWidth; private int mBrandingHeight; private Bitmap mBrandingBitmap; @@ -485,14 +604,20 @@ public final class SplashScreenView extends FrameLayout { private long mIconAnimationStartMillis; private long mIconAnimationDurationMillis; + private SurfaceControlViewHost.SurfacePackage mSurfacePackage; + public SplashScreenViewParcelable(SplashScreenView view) { - ViewGroup.LayoutParams params = view.getIconView().getLayoutParams(); - mIconSize = params.height; + mIconSize = view.mIconView.getWidth(); mBackgroundColor = view.getInitBackgroundColor(); mIconBackground = view.getIconBackgroundColor(); - mIconBitmap = copyDrawable(view.getIconView().getBackground()); + mSurfacePackage = view.mSurfacePackageCopy; + if (mSurfacePackage == null) { + // We only need to copy the drawable if we are not using a SurfaceView + mIconBitmap = copyDrawable(view.getIconView().getBackground()); + } mBrandingBitmap = copyDrawable(view.getBrandingView().getBackground()); - params = view.getBrandingView().getLayoutParams(); + + ViewGroup.LayoutParams params = view.getBrandingView().getLayoutParams(); mBrandingWidth = params.width; mBrandingHeight = params.height; @@ -535,6 +660,7 @@ public final class SplashScreenView extends FrameLayout { mIconAnimationStartMillis = source.readLong(); mIconAnimationDurationMillis = source.readLong(); mIconBackground = source.readInt(); + mSurfacePackage = source.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR); } @Override @@ -553,6 +679,7 @@ public final class SplashScreenView extends FrameLayout { dest.writeLong(mIconAnimationStartMillis); dest.writeLong(mIconAnimationDurationMillis); dest.writeInt(mIconBackground); + dest.writeTypedObject(mSurfacePackage, flags); } public static final @NonNull Parcelable.Creator CREATOR = 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 187e104c9bfb6..fbb849128f957 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 @@ -372,6 +372,7 @@ public class StartingSurfaceDrawer { if (preView != null && preView.mContentView != null && preView.mContentView.isCopyable()) { parcelable = new SplashScreenViewParcelable(preView.mContentView); + preView.mContentView.onCopied(); } else { parcelable = null; } From dd3687333472d862432210da08d980b331721d0c Mon Sep 17 00:00:00 2001 From: Vadim Caen Date: Thu, 15 Apr 2021 14:28:55 +0200 Subject: [PATCH 2/2] Do not reparent the SC when package is already realeased If we pass a copy of the surface package, we can release the original, but we need to check that in this case the surface control doesn't get reparented to null since the SurfaceView using the copy might already use the surface. Test: atest CtsWindowManagerDeviceTestCases:SurfaceControlViewHostTests#testTransferSurfacePackage Bug: 181757194 Change-Id: Ibf084aa70f3821e7dc82fe45b194c07f307ebe1e --- core/java/android/view/SurfaceView.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 2fce4348cbb0f..6b0bb9df54683 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -571,7 +571,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall // recreate this Surface, so only release it when we are fully // detached. if (mSurfacePackage != null) { - mTmpTransaction.reparent(mSurfacePackage.getSurfaceControl(), null).apply(); + final SurfaceControl sc = mSurfacePackage.getSurfaceControl(); + if (sc != null && sc.isValid()) { + mTmpTransaction.reparent(sc, null).apply(); + } mSurfacePackage.release(); mSurfacePackage = null; } @@ -1826,7 +1829,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall */ public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) { final SurfaceControl lastSc = mSurfacePackage != null ? - mSurfacePackage.getSurfaceControl() : null; + mSurfacePackage.getSurfaceControl() : null; if (mSurfaceControl != null && lastSc != null) { mTmpTransaction.reparent(lastSc, null).apply(); mSurfacePackage.release(); @@ -1839,8 +1842,11 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void reparentSurfacePackage(SurfaceControl.Transaction t, SurfaceControlViewHost.SurfacePackage p) { - initEmbeddedHierarchyForAccessibility(p); final SurfaceControl sc = p.getSurfaceControl(); + if (sc == null || !sc.isValid()) { + return; + } + initEmbeddedHierarchyForAccessibility(p); final SurfaceControl parent; if (mUseBlastAdapter) { parent = mBlastSurfaceControl;