From 9ee5dff83c1cae93dff281735b60134dbd26dd8a Mon Sep 17 00:00:00 2001
From: Adrian Roos
Date: Wed, 22 Aug 2018 20:19:49 +0200
Subject: [PATCH] BootAnimation: Fix boot animation with hidden cutout
We do this by storing the masking inset in a persistent property.
The boot animation then animates itself to where it would be if that
masking inset were applied, then changes the viewport.
For this to work, we also need to make sure the DisplayManagerService
has the right overlay right at the start.
Bug: 112876936
Test: Hide cutout, then reboot. Verify boot animation is smooth.
Change-Id: I3e988b2340b2e0d2be3939bdc6878704c234ccc8
---
cmds/bootanimation/BootAnimation.cpp | 49 ++++++++++++++++++-
cmds/bootanimation/BootAnimation.h | 4 ++
core/java/android/app/ActivityThread.java | 11 +++++
.../server/display/DisplayManagerService.java | 45 ++++++++++++++++-
.../server/display/LogicalDisplay.java | 14 ++++++
.../server/om/OverlayManagerService.java | 23 ++++++++-
.../java/com/android/server/SystemServer.java | 11 ++++-
7 files changed, 152 insertions(+), 5 deletions(-)
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 8ffe5bf593157..ed6c25dc49c36 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -302,6 +302,7 @@ status_t BootAnimation::readyToRun() {
mHeight = h;
mFlingerSurfaceControl = control;
mFlingerSurface = s;
+ mTargetInset = -1;
// If the device has encryption turned on or is in process
// of being encrypted we show the encrypted boot animation.
@@ -942,6 +943,7 @@ bool BootAnimation::playAnimation(const Animation& animation)
if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
}
+ handleViewport(frameDuration);
eglSwapBuffers(mDisplay, mSurface);
@@ -966,7 +968,7 @@ bool BootAnimation::playAnimation(const Animation& animation)
usleep(part.pause * ns2us(frameDuration));
// For infinite parts, we've now played them at least once, so perhaps exit
- if(exitPending() && !part.count)
+ if(exitPending() && !part.count && mCurrentInset >= mTargetInset)
break;
}
@@ -986,6 +988,51 @@ bool BootAnimation::playAnimation(const Animation& animation)
return true;
}
+void BootAnimation::handleViewport(nsecs_t timestep) {
+ if (mShuttingDown || !mFlingerSurfaceControl || mTargetInset == 0) {
+ return;
+ }
+ if (mTargetInset < 0) {
+ // Poll the amount for the top display inset. This will return -1 until persistent properties
+ // have been loaded.
+ mTargetInset = android::base::GetIntProperty("persist.sys.displayinset.top",
+ -1 /* default */, -1 /* min */, mHeight / 2 /* max */);
+ }
+ if (mTargetInset <= 0) {
+ return;
+ }
+
+ if (mCurrentInset < mTargetInset) {
+ // After the device boots, the inset will effectively be cropped away. We animate this here.
+ float fraction = static_cast(mCurrentInset) / mTargetInset;
+ int interpolatedInset = (cosf((fraction + 1) * M_PI) / 2.0f + 0.5f) * mTargetInset;
+
+ SurfaceComposerClient::Transaction()
+ .setCrop(mFlingerSurfaceControl, Rect(0, interpolatedInset, mWidth, mHeight))
+ .apply();
+ } else {
+ // At the end of the animation, we switch to the viewport that DisplayManager will apply
+ // later. This changes the coordinate system, and means we must move the surface up by
+ // the inset amount.
+ sp dtoken(SurfaceComposerClient::getBuiltInDisplay(
+ ISurfaceComposer::eDisplayIdMain));
+
+ Rect layerStackRect(0, 0, mWidth, mHeight - mTargetInset);
+ Rect displayRect(0, mTargetInset, mWidth, mHeight);
+
+ SurfaceComposerClient::Transaction t;
+ t.setPosition(mFlingerSurfaceControl, 0, -mTargetInset)
+ .setCrop(mFlingerSurfaceControl, Rect(0, mTargetInset, mWidth, mHeight));
+ t.setDisplayProjection(dtoken, 0 /* orientation */, layerStackRect, displayRect);
+ t.apply();
+
+ mTargetInset = mCurrentInset = 0;
+ }
+
+ int delta = timestep * mTargetInset / ms2ns(200);
+ mCurrentInset += delta;
+}
+
void BootAnimation::releaseAnimation(Animation* animation) const
{
for (Vector::iterator it = animation->parts.begin(),
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 56e131523bcbb..b4699d8846815 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -157,11 +157,15 @@ private:
void checkExit();
+ void handleViewport(nsecs_t timestep);
+
sp mSession;
AssetManager mAssets;
Texture mAndroid[2];
int mWidth;
int mHeight;
+ int mCurrentInset;
+ int mTargetInset;
bool mUseNpotTextures = false;
EGLDisplay mDisplay;
EGLDisplay mContext;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 87b334992d2da..ab0b88c22d28c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -146,6 +146,7 @@ import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.org.conscrypt.OpenSSLSocketImpl;
import com.android.org.conscrypt.TrustedCertificateStore;
@@ -5144,6 +5145,16 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
+ /**
+ * Updates the application info.
+ *
+ * This only works in the system process. Must be called on the main thread.
+ */
+ public void handleSystemApplicationInfoChanged(@NonNull ApplicationInfo ai) {
+ Preconditions.checkState(mSystemThread, "Must only be called in the system process");
+ handleApplicationInfoChanged(ai);
+ }
+
void handleApplicationInfoChanged(@NonNull final ApplicationInfo ai) {
// Updates triggered by package installation go through a package update
// receiver. Here we try to capture ApplicationInfo changes that are
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ddd8855f31993..cc5a8271eb5fa 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -151,6 +151,8 @@ public final class DisplayManagerService extends SystemService {
// Otherwise WFD is enabled according to the value of config_enableWifiDisplay.
private static final String FORCE_WIFI_DISPLAY_ENABLE = "persist.debug.wfd.enable";
+ private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top";
+
private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
@@ -243,6 +245,15 @@ public final class DisplayManagerService extends SystemService {
// device).
private Point mStableDisplaySize = new Point();
+ // Whether the system has finished booting or not.
+ private boolean mSystemReady;
+
+ // The top inset of the default display.
+ // This gets persisted so that the boot animation knows how to transition from the display's
+ // full size to the size configured by the user. Right now we only persist and animate the top
+ // inset, but theoretically we could do it for all of them.
+ private int mDefaultDisplayTopInset;
+
// Viewports of the default display and the display that should receive touch
// input from an external source. Used by the input system.
private final DisplayViewport mDefaultViewport = new DisplayViewport();
@@ -301,6 +312,7 @@ public final class DisplayManagerService extends SystemService {
Resources resources = mContext.getResources();
mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger(
com.android.internal.R.integer.config_defaultDisplayDefaultColorMode);
+ mDefaultDisplayTopInset = SystemProperties.getInt(PROP_DEFAULT_DISPLAY_TOP_INSET, -1);
float[] lux = getFloatArray(resources.obtainTypedArray(
com.android.internal.R.array.config_minimumBrightnessCurveLux));
float[] nits = getFloatArray(resources.obtainTypedArray(
@@ -311,6 +323,8 @@ public final class DisplayManagerService extends SystemService {
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
mCurrentUserId = UserHandle.USER_SYSTEM;
+
+ mSystemReady = false;
}
public void setupSchedulerPolicies() {
@@ -400,6 +414,10 @@ public final class DisplayManagerService extends SystemService {
synchronized (mSyncRoot) {
mSafeMode = safeMode;
mOnlyCore = onlyCore;
+ mSystemReady = true;
+ // Just in case the top inset changed before the system was ready. At this point, any
+ // relevant configuration should be in place.
+ recordTopInsetLocked(mLogicalDisplays.get(Display.DEFAULT_DISPLAY));
}
mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
@@ -457,7 +475,7 @@ public final class DisplayManagerService extends SystemService {
LogicalDisplay display = mLogicalDisplays.get(displayId);
if (display != null) {
if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
- sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ handleLogicalDisplayChanged(displayId, display);
scheduleTraversalLocked(false);
}
}
@@ -938,6 +956,13 @@ public final class DisplayManagerService extends SystemService {
scheduleTraversalLocked(false);
}
+ private void handleLogicalDisplayChanged(int displayId, @NonNull LogicalDisplay display) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ recordTopInsetLocked(display);
+ }
+ sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ }
+
private void applyGlobalDisplayStateLocked(List workQueue) {
final int count = mDisplayDevices.size();
for (int i = 0; i < count; i++) {
@@ -991,6 +1016,7 @@ public final class DisplayManagerService extends SystemService {
configureColorModeLocked(display, device);
if (isDefault) {
recordStableDisplayStatsIfNeededLocked(display);
+ recordTopInsetLocked(display);
}
mLogicalDisplays.put(displayId, display);
@@ -1039,6 +1065,21 @@ public final class DisplayManagerService extends SystemService {
}
}
+ private void recordTopInsetLocked(@Nullable LogicalDisplay d) {
+ // We must only persist the inset after boot has completed, otherwise we will end up
+ // overwriting the persisted value before the masking flag has been loaded from the
+ // resource overlay.
+ if (!mSystemReady || d == null) {
+ return;
+ }
+ int topInset = d.getInsets().top;
+ if (topInset == mDefaultDisplayTopInset) {
+ return;
+ }
+ mDefaultDisplayTopInset = topInset;
+ SystemProperties.set(PROP_DEFAULT_DISPLAY_TOP_INSET, Integer.toString(topInset));
+ }
+
private void setStableDisplaySizeLocked(int width, int height) {
mStableDisplaySize = new Point(width, height);
try {
@@ -1118,7 +1159,7 @@ public final class DisplayManagerService extends SystemService {
sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
changed = true;
} else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
- sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ handleLogicalDisplayChanged(displayId, display);
changed = true;
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 373de63c0ec97..5b7c5205ce3a7 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -18,6 +18,7 @@ package com.android.server.display;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerInternal;
+import android.os.SystemProperties;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -57,6 +58,8 @@ import java.util.Objects;
*
*/
final class LogicalDisplay {
+ private static final String PROP_MASKING_INSET_TOP = "persist.sys.displayinset.top";
+
private final DisplayInfo mBaseDisplayInfo = new DisplayInfo();
// The layer stack we use when the display has been blanked to prevent any
@@ -296,6 +299,17 @@ final class LogicalDisplay {
}
}
+ /**
+ * Return the insets currently applied to the display.
+ *
+ * Note that the base DisplayInfo already takes these insets into account, so if you want to
+ * find out the true size of the display, you need to add them back to the logical
+ * dimensions.
+ */
+ public Rect getInsets() {
+ return getMaskingInsets(mPrimaryDisplayDeviceInfo);
+ }
+
/**
* Returns insets in ROTATION_0 for areas that are masked.
*/
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index f082271ab094f..c73870189002e 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -22,11 +22,14 @@ import static android.content.Intent.ACTION_PACKAGE_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.SIGNATURE_MATCH;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityThread;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -34,6 +37,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.om.IOverlayManager;
import android.content.om.OverlayInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManagerInternal;
@@ -269,13 +273,30 @@ public final class OverlayManagerService extends SystemService {
@Override
public void onBootPhase(int phase) {
- if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY && mInitCompleteSignal != null) {
ConcurrentUtils.waitForFutureNoInterrupt(mInitCompleteSignal,
"Wait for OverlayManagerService init");
mInitCompleteSignal = null;
}
}
+ public void updateSystemUiContext() {
+ if (mInitCompleteSignal != null) {
+ ConcurrentUtils.waitForFutureNoInterrupt(mInitCompleteSignal,
+ "Wait for OverlayManagerService init");
+ mInitCompleteSignal = null;
+ }
+
+ final ApplicationInfo ai;
+ try {
+ ai = mPackageManager.mPackageManager.getApplicationInfo("android",
+ GET_SHARED_LIBRARY_FILES, UserHandle.USER_SYSTEM);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ ActivityThread.currentActivityThread().handleSystemApplicationInfoChanged(ai);
+ }
+
private void initIfNeeded() {
final UserManager um = getContext().getSystemService(UserManager.class);
final List users = um.getUsers(true /*excludeDying*/);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3c833d862d9ca..a61970e063bb8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -29,6 +29,7 @@ import android.content.res.Configuration;
import android.content.res.Resources.Theme;
import android.database.sqlite.SQLiteCompatibilityWalFlags;
import android.database.sqlite.SQLiteGlobal;
+import android.hardware.display.DisplayManagerInternal;
import android.os.BaseBundle;
import android.os.Binder;
import android.os.Build;
@@ -677,9 +678,17 @@ public final class SystemServer {
// Manages Overlay packages
traceBeginAndSlog("StartOverlayManagerService");
- mSystemServiceManager.startService(new OverlayManagerService(mSystemContext, installer));
+ OverlayManagerService overlayManagerService = new OverlayManagerService(
+ mSystemContext, installer);
+ mSystemServiceManager.startService(overlayManagerService);
traceEnd();
+ if (SystemProperties.getInt("persist.sys.displayinset.top", 0) > 0) {
+ // DisplayManager needs the overlay immediately.
+ overlayManagerService.updateSystemUiContext();
+ LocalServices.getService(DisplayManagerInternal.class).onOverlayChanged();
+ }
+
// The sensor service needs access to package manager service, app ops
// service, and permissions service, therefore we start it after them.
// Start sensor service in a separate thread. Completion should be checked