Merge "Synchronize screen turning on and unfold overlay" into sc-v2-dev

This commit is contained in:
TreeHugger Robot
2021-10-18 12:28:26 +00:00
committed by Android (Google) Code Review
18 changed files with 810 additions and 106 deletions

View File

@@ -292,11 +292,18 @@ public class SurfaceControlViewHost {
*/
@TestApi
public void relayout(WindowManager.LayoutParams attrs) {
relayout(attrs, SurfaceControl.Transaction::apply);
}
/**
* Forces relayout and draw and allows to set a custom callback when it is finished
* @hide
*/
public void relayout(WindowManager.LayoutParams attrs,
WindowlessWindowManager.ResizeCompleteCallback callback) {
mViewRoot.setLayoutParams(attrs, false);
mViewRoot.setReportNextDraw();
mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), (SurfaceControl.Transaction t) -> {
t.apply();
});
mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), callback);
}
/**

View File

@@ -0,0 +1,114 @@
/*
* Copyright (C) 2020 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;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
import androidx.annotation.NonNull;
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.Executor;
/** Display area organizer for the root display areas */
public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer {
private static final String TAG = RootDisplayAreaOrganizer.class.getSimpleName();
/** {@link DisplayAreaInfo} list, which is mapped by display IDs. */
private final SparseArray<DisplayAreaInfo> mDisplayAreasInfo = new SparseArray<>();
/** Display area leashes, which is mapped by display IDs. */
private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>();
public RootDisplayAreaOrganizer(Executor executor) {
super(executor);
List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT);
for (int i = infos.size() - 1; i >= 0; --i) {
onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash());
}
}
public void attachToDisplayArea(int displayId, SurfaceControl.Builder b) {
final SurfaceControl sc = mLeashes.get(displayId);
if (sc != null) {
b.setParent(sc);
}
}
@Override
public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
@NonNull SurfaceControl leash) {
if (displayAreaInfo.featureId != FEATURE_ROOT) {
throw new IllegalArgumentException(
"Unknown feature: " + displayAreaInfo.featureId
+ "displayAreaInfo:" + displayAreaInfo);
}
final int displayId = displayAreaInfo.displayId;
if (mDisplayAreasInfo.get(displayId) != null) {
throw new IllegalArgumentException(
"Duplicate DA for displayId: " + displayId
+ " displayAreaInfo:" + displayAreaInfo
+ " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
}
mDisplayAreasInfo.put(displayId, displayAreaInfo);
mLeashes.put(displayId, leash);
}
@Override
public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
final int displayId = displayAreaInfo.displayId;
if (mDisplayAreasInfo.get(displayId) == null) {
throw new IllegalArgumentException(
"onDisplayAreaVanished() Unknown DA displayId: " + displayId
+ " displayAreaInfo:" + displayAreaInfo
+ " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
}
mDisplayAreasInfo.remove(displayId);
}
@Override
public void onDisplayAreaInfoChanged(@NonNull DisplayAreaInfo displayAreaInfo) {
final int displayId = displayAreaInfo.displayId;
if (mDisplayAreasInfo.get(displayId) == null) {
throw new IllegalArgumentException(
"onDisplayAreaInfoChanged() Unknown DA displayId: " + displayId
+ " displayAreaInfo:" + displayAreaInfo
+ " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
}
mDisplayAreasInfo.put(displayId, displayAreaInfo);
}
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
}
@Override
public String toString() {
return TAG + "#" + mDisplayAreasInfo.size();
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.displayareahelper;
import android.view.SurfaceControl;
import java.util.function.Consumer;
/**
* Interface that allows to perform various display area related actions
*/
public interface DisplayAreaHelper {
/**
* Updates SurfaceControl builder to reparent it to the root display area
* @param displayId id of the display to which root display area it should be reparented to
* @param builder surface control builder that should be updated
* @param onUpdated callback that is invoked after updating the builder, called on
* the shell main thread
*/
default void attachToRootDisplayArea(int displayId, SurfaceControl.Builder builder,
Consumer<SurfaceControl.Builder> onUpdated) {
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.displayareahelper;
import android.view.SurfaceControl;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
public class DisplayAreaHelperController implements DisplayAreaHelper {
private final Executor mExecutor;
private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
public DisplayAreaHelperController(Executor executor,
RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
mExecutor = executor;
mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
}
@Override
public void attachToRootDisplayArea(int displayId, SurfaceControl.Builder builder,
Consumer<SurfaceControl.Builder> onUpdated) {
mExecutor.execute(() -> {
mRootDisplayAreaOrganizer.attachToDisplayArea(displayId, builder);
onUpdated.accept(builder);
});
}
}

View File

@@ -16,6 +16,7 @@
package com.android.systemui.unfold.progress
import android.os.Handler
import android.util.Log
import android.util.MathUtils.saturate
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -92,7 +93,14 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
}
}
FOLD_UPDATE_FINISH_FULL_OPEN -> {
cancelTransition(endValue = 1f, animate = true)
// Do not cancel if we haven't started the transition yet.
// This could happen when we fully unfolded the device before the screen
// became available. In this case we start and immediately cancel the animation
// in FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE event handler, so we don't need to
// cancel it here.
if (isTransitionRunning) {
cancelTransition(endValue = 1f, animate = true)
}
}
FOLD_UPDATE_FINISH_CLOSED -> {
cancelTransition(endValue = 0f, animate = false)
@@ -101,6 +109,10 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
startTransition(startValue = 1f)
}
}
if (DEBUG) {
Log.d(TAG, "onFoldUpdate = $update")
}
}
private fun cancelTransition(endValue: Float, animate: Boolean) {
@@ -118,6 +130,10 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
listeners.forEach {
it.onTransitionFinished()
}
if (DEBUG) {
Log.d(TAG, "onTransitionFinished")
}
}
}
@@ -137,6 +153,10 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
it.onTransitionStarted()
}
isTransitionRunning = true
if (DEBUG) {
Log.d(TAG, "onTransitionStarted")
}
}
private fun startTransition(startValue: Float) {
@@ -189,6 +209,9 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
}
}
private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
private const val DEBUG = true
private const val TRANSITION_TIMEOUT_MILLIS = 2000L
private const val SPRING_STIFFNESS = 200.0f
private const val MINIMAL_VISIBLE_CHANGE = 0.001f

View File

@@ -25,7 +25,7 @@ import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import java.util.concurrent.Executor
internal class DeviceFoldStateProvider(
class DeviceFoldStateProvider(
context: Context,
private val hingeAngleProvider: HingeAngleProvider,
private val screenStatusProvider: ScreenStatusProvider,
@@ -43,6 +43,7 @@ internal class DeviceFoldStateProvider(
private val foldStateListener = FoldStateListener(context)
private var isFolded = false
private var isUnfoldHandled = true
override fun start() {
deviceStateManager.registerCallback(
@@ -104,6 +105,7 @@ internal class DeviceFoldStateProvider(
lastFoldUpdate = FOLD_UPDATE_FINISH_CLOSED
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) }
hingeAngleProvider.stop()
isUnfoldHandled = false
} else {
lastFoldUpdate = FOLD_UPDATE_START_OPENING
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_OPENING) }
@@ -115,8 +117,15 @@ internal class DeviceFoldStateProvider(
ScreenStatusProvider.ScreenListener {
override fun onScreenTurnedOn() {
if (!isFolded) {
// Trigger this event only if we are unfolded and this is the first screen
// turned on event since unfold started. This prevents running the animation when
// turning on the internal display using the power button.
// Initially isUnfoldHandled is true so it will be reset to false *only* when we
// receive 'folded' event. If SystemUI started when device is already folded it will
// still receive 'folded' event on startup.
if (!isFolded && !isUnfoldHandled) {
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }
isUnfoldHandled = true
}
}
}

View File

@@ -24,7 +24,7 @@ import com.android.systemui.statusbar.policy.CallbackController
* Allows to subscribe to main events related to fold/unfold process such as hinge angle update,
* start folding/unfolding, screen availability
*/
internal interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
fun start()
fun stop()

View File

@@ -9,7 +9,7 @@ import com.android.systemui.statusbar.policy.CallbackController
* For foldable devices usually 0 corresponds to fully closed (folded) state and
* 180 degrees corresponds to fully open (flat) state
*/
internal interface HingeAngleProvider : CallbackController<Consumer<Float>> {
interface HingeAngleProvider : CallbackController<Consumer<Float>> {
fun start()
fun stop()
}

View File

@@ -118,6 +118,7 @@ public class SystemUIFactory {
.setTaskViewFactory(mWMComponent.getTaskViewFactory())
.setTransitions(mWMComponent.getTransitions())
.setStartingSurface(mWMComponent.getStartingSurface())
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper());
} else {
// TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
@@ -133,6 +134,7 @@ public class SystemUIFactory {
.setAppPairs(Optional.ofNullable(null))
.setTaskViewFactory(Optional.ofNullable(null))
.setTransitions(Transitions.createEmptyForTesting())
.setDisplayAreaHelper(Optional.ofNullable(null))
.setStartingSurface(Optional.ofNullable(null))
.setTaskSurfaceHelper(Optional.ofNullable(null));
}

View File

@@ -30,6 +30,7 @@ import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
@@ -95,6 +96,9 @@ public interface SysUIComponent {
@BindsInstance
Builder setStartingSurface(Optional<StartingSurface> s);
@BindsInstance
Builder setDisplayAreaHelper(Optional<DisplayAreaHelper> h);
@BindsInstance
Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t);

View File

@@ -27,6 +27,7 @@ import com.android.wm.shell.ShellInit;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
@@ -104,6 +105,9 @@ public interface WMComponent {
@WMSingleton
Optional<StartingSurface> getStartingSurface();
@WMSingleton
Optional<DisplayAreaHelper> getDisplayAreaHelper();
@WMSingleton
Optional<TaskSurfaceHelper> getTaskSurfaceHelper();
}

View File

@@ -119,12 +119,15 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.util.DeviceConfigProxy;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import dagger.Lazy;
@@ -815,6 +818,10 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
private DeviceConfigProxy mDeviceConfig;
private DozeParameters mDozeParameters;
private final UnfoldTransitionConfig mUnfoldTransitionConfig;
private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealAnimation;
private final AtomicInteger mPendingDrawnTasks = new AtomicInteger();
private final KeyguardStateController mKeyguardStateController;
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
private boolean mWallpaperSupportsAmbientMode;
@@ -837,6 +844,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
NavigationModeController navigationModeController,
KeyguardDisplayManager keyguardDisplayManager,
DozeParameters dozeParameters,
UnfoldTransitionConfig unfoldTransitionConfig,
Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
SysuiStatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy,
@@ -870,6 +879,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
}));
mDozeParameters = dozeParameters;
mUnfoldTransitionConfig = unfoldTransitionConfig;
mUnfoldLightRevealAnimation = unfoldLightRevealOverlayAnimation;
mStatusBarStateController = statusBarStateController;
statusBarStateController.addCallback(this);
@@ -2552,6 +2563,24 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurningOn");
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurningOn");
if (mUnfoldTransitionConfig.isEnabled()) {
mPendingDrawnTasks.set(2); // unfold overlay and keyguard drawn
mUnfoldLightRevealAnimation.get()
.onScreenTurningOn(() -> {
if (mPendingDrawnTasks.decrementAndGet() == 0) {
try {
callback.onDrawn();
} catch (RemoteException e) {
Slog.w(TAG, "Exception calling onDrawn():", e);
}
}
});
} else {
mPendingDrawnTasks.set(1); // only keyguard drawn
}
mKeyguardViewControllerLazy.get().onScreenTurningOn();
if (callback != null) {
if (mWakeAndUnlocking) {
@@ -2582,10 +2611,12 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
private void notifyDrawn(final IKeyguardDrawnCallback callback) {
Trace.beginSection("KeyguardViewMediator#notifyDrawn");
try {
callback.onDrawn();
} catch (RemoteException e) {
Slog.w(TAG, "Exception calling onDrawn():", e);
if (mPendingDrawnTasks.decrementAndGet() == 0) {
try {
callback.onDrawn();
} catch (RemoteException e) {
Slog.w(TAG, "Exception calling onDrawn():", e);
}
}
Trace.endSection();
}
@@ -2738,6 +2769,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
pw.print(" mHideAnimationRun: "); pw.println(mHideAnimationRun);
pw.print(" mPendingReset: "); pw.println(mPendingReset);
pw.print(" mPendingLock: "); pw.println(mPendingLock);
pw.print(" mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.get());
pw.print(" mWakeAndUnlocking: "); pw.println(mWakeAndUnlocking);
pw.print(" mDrawnCallback: "); pw.println(mDrawnCallback);
}

View File

@@ -55,6 +55,8 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.settings.GlobalSettings;
@@ -99,6 +101,8 @@ public class KeyguardModule {
NavigationModeController navigationModeController,
KeyguardDisplayManager keyguardDisplayManager,
DozeParameters dozeParameters,
UnfoldTransitionConfig unfoldTransitionConfig,
Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
SysuiStatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
@@ -121,6 +125,8 @@ public class KeyguardModule {
navigationModeController,
keyguardDisplayManager,
dozeParameters,
unfoldTransitionConfig,
unfoldLightRevealOverlayAnimation,
statusBarStateController,
keyguardStateController,
keyguardUnlockAnimationController,

View File

@@ -19,13 +19,27 @@ import android.content.Context
import android.graphics.PixelFormat
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
import android.hardware.display.DisplayManager
import android.os.Handler
import android.os.Trace
import android.view.Choreographer
import android.view.Display
import android.view.DisplayInfo
import android.view.Surface
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
import android.view.SurfaceSession
import android.view.WindowManager
import android.view.WindowlessWindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.LinearLightRevealEffect
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.wm.shell.displayareahelper.DisplayAreaHelper
import java.util.Optional
import java.util.concurrent.Executor
import java.util.function.Consumer
import javax.inject.Inject
@@ -34,53 +48,148 @@ import javax.inject.Inject
class UnfoldLightRevealOverlayAnimation @Inject constructor(
private val context: Context,
private val deviceStateManager: DeviceStateManager,
private val displayManager: DisplayManager,
private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
private val displayAreaHelper: Optional<DisplayAreaHelper>,
@Main private val executor: Executor,
private val windowManager: WindowManager
@Main private val handler: Handler,
@UiBackground private val backgroundExecutor: Executor
) {
private val transitionListener = TransitionListener()
private val displayListener = DisplayChangeListener()
private lateinit var wwm: WindowlessWindowManager
private lateinit var unfoldedDisplayInfo: DisplayInfo
private lateinit var overlayContainer: SurfaceControl
private var root: SurfaceControlViewHost? = null
private var scrimView: LightRevealScrim? = null
private var isFolded: Boolean = false
private var isUnfoldHandled: Boolean = true
private var currentRotation: Int = context.display!!.rotation
fun init() {
deviceStateManager.registerCallback(executor, FoldListener())
unfoldTransitionProgressProvider.addCallback(transitionListener)
}
private inner class TransitionListener : TransitionProgressListener {
val containerBuilder = SurfaceControl.Builder(SurfaceSession())
.setContainerLayer()
.setName("unfold-overlay-container")
override fun onTransitionProgress(progress: Float) {
scrimView?.revealAmount = progress
}
displayAreaHelper.get().attachToRootDisplayArea(Display.DEFAULT_DISPLAY,
containerBuilder) { builder ->
executor.execute {
overlayContainer = builder.build()
override fun onTransitionFinished() {
removeOverlayView()
}
SurfaceControl.Transaction()
.setLayer(overlayContainer, Integer.MAX_VALUE)
.show(overlayContainer)
.apply()
override fun onTransitionStarted() {
// When unfolding the view is added earlier, add view for folding case
if (scrimView == null) {
addOverlayView()
wwm = WindowlessWindowManager(context.resources.configuration,
overlayContainer, null)
}
}
displayManager.registerDisplayListener(displayListener, handler,
DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
// Get unfolded display size immediately as 'current display info' might be
// not up-to-date during unfolding
unfoldedDisplayInfo = getUnfoldedDisplayInfo()
}
private inner class FoldListener : FoldStateListener(context, Consumer { isFolded ->
if (isFolded) {
removeOverlayView()
} else {
// Add overlay view before starting the transition as soon as we unfolded the device
addOverlayView()
/**
* Called when screen starts turning on, the contents of the screen might not be visible yet.
* This method reports back that the overlay is ready in [onOverlayReady] callback.
*
* @param onOverlayReady callback when the overlay is drawn and visible on the screen
* @see [com.android.systemui.keyguard.KeyguardViewMediator]
*/
fun onScreenTurningOn(onOverlayReady: Runnable) {
Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn")
try {
// Add the view only if we are unfolding and this is the first screen on
if (!isFolded && !isUnfoldHandled) {
addView(onOverlayReady)
isUnfoldHandled = true
} else {
// No unfold transition, immediately report that overlay is ready
ensureOverlayRemoved()
onOverlayReady.run()
}
} finally {
Trace.endSection()
}
})
}
private fun addOverlayView() {
private fun addView(onOverlayReady: Runnable? = null) {
if (!::wwm.isInitialized) {
// Surface overlay is not created yet on the first SysUI launch
onOverlayReady?.run()
return
}
ensureOverlayRemoved()
val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false)
val newView = LightRevealScrim(context, null)
.apply {
revealEffect = createLightRevealEffect()
isScrimOpaqueChangedListener = Consumer {}
revealAmount = 0f
}
val params = getLayoutParams()
newRoot.setView(newView, params)
onOverlayReady?.let { callback ->
Trace.beginAsyncSection(
"UnfoldLightRevealOverlayAnimation#relayout", 0)
newRoot.relayout(params) { transaction ->
val vsyncId = Choreographer.getSfInstance().vsyncId
backgroundExecutor.execute {
// Apply the transaction that contains the first frame of the overlay
// synchronously and apply another empty transaction with
// 'vsyncId + 1' to make sure that it is actually displayed on
// the screen. The second transaction is necessary to remove the screen blocker
// (turn on the brightness) only when the content is actually visible as it
// might be presented only in the next frame.
// See b/197538198
transaction.setFrameTimelineVsync(vsyncId)
.apply(/* sync */true)
transaction
.setFrameTimelineVsync(vsyncId + 1)
.apply(/* sync */ true)
Trace.endAsyncSection(
"UnfoldLightRevealOverlayAnimation#relayout", 0)
callback.run()
}
}
}
scrimView = newView
root = newRoot
}
private fun getLayoutParams(): WindowManager.LayoutParams {
val params: WindowManager.LayoutParams = WindowManager.LayoutParams()
params.height = WindowManager.LayoutParams.MATCH_PARENT
params.width = WindowManager.LayoutParams.MATCH_PARENT
params.format = PixelFormat.TRANSLUCENT
// TODO(b/193801466): create a separate type for this overlay
val rotation = context.display!!.rotation
val isNatural = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
params.height = if (isNatural)
unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth
params.width = if (isNatural)
unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight
params.format = PixelFormat.TRANSLUCENT
params.type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
params.title = "Unfold Light Reveal Animation"
params.layoutInDisplayCutoutMode =
@@ -90,41 +199,72 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor(
or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
params.setTrustedOverlay()
val rotation = windowManager.defaultDisplay.rotation
val isVerticalFold = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
val newScrimView = LightRevealScrim(context, null)
.apply {
revealEffect = LinearLightRevealEffect(isVerticalFold)
isScrimOpaqueChangedListener = Consumer {}
revealAmount = 0f
}
val packageName: String = newScrimView.context.opPackageName
val packageName: String = context.opPackageName
params.packageName = packageName
params.hideTimeoutMilliseconds = OVERLAY_HIDE_TIMEOUT_MILLIS
if (scrimView?.parent != null) {
windowManager.removeView(scrimView)
}
this.scrimView = newScrimView
try {
windowManager.addView(scrimView, params)
} catch (e: WindowManager.BadTokenException) {
e.printStackTrace()
}
return params
}
private fun removeOverlayView() {
scrimView?.let {
if (it.parent != null) {
windowManager.removeViewImmediate(it)
private fun createLightRevealEffect(): LightRevealEffect {
val isVerticalFold = currentRotation == Surface.ROTATION_0 ||
currentRotation == Surface.ROTATION_180
return LinearLightRevealEffect(isVertical = isVerticalFold)
}
private fun ensureOverlayRemoved() {
root?.release()
root = null
scrimView = null
}
private fun getUnfoldedDisplayInfo(): DisplayInfo =
displayManager.displays
.asSequence()
.map { DisplayInfo().apply { it.getDisplayInfo(this) } }
.filter { it.type == Display.TYPE_INTERNAL }
.maxByOrNull { it.naturalWidth }!!
private inner class TransitionListener : TransitionProgressListener {
override fun onTransitionProgress(progress: Float) {
scrimView?.revealAmount = progress
}
override fun onTransitionFinished() {
ensureOverlayRemoved()
}
override fun onTransitionStarted() {
// Add view for folding case (when unfolding the view is added earlier)
if (scrimView == null) {
addView()
}
scrimView = null
}
}
}
private const val OVERLAY_HIDE_TIMEOUT_MILLIS = 10_000L
private inner class DisplayChangeListener : DisplayManager.DisplayListener {
override fun onDisplayChanged(displayId: Int) {
val newRotation: Int = context.display!!.rotation
if (currentRotation != newRotation) {
currentRotation = newRotation
scrimView?.revealEffect = createLightRevealEffect()
root?.relayout(getLayoutParams())
}
}
override fun onDisplayAdded(displayId: Int) {
}
override fun onDisplayRemoved(displayId: Int) {
}
}
private inner class FoldListener : FoldStateListener(context, Consumer { isFolded ->
if (isFolded) {
ensureOverlayRemoved()
isUnfoldHandled = false
}
this.isFolded = isFolded
})
}

View File

@@ -28,6 +28,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.ShellCommandHandlerImpl;
@@ -54,6 +55,8 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
@@ -342,13 +345,21 @@ public abstract class WMShellBaseModule {
return taskSurfaceController.map((controller) -> controller.asTaskSurfaceHelper());
}
@WMSingleton
@Provides
static Optional<TaskSurfaceHelperController> provideTaskSurfaceHelperController(
ShellTaskOrganizer taskOrganizer, @ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(new TaskSurfaceHelperController(taskOrganizer, mainExecutor));
}
@WMSingleton
@Provides
static Optional<DisplayAreaHelper> provideDisplayAreaHelper(
@ShellMainThread ShellExecutor mainExecutor,
RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
return Optional.ofNullable(new DisplayAreaHelperController(mainExecutor,
rootDisplayAreaOrganizer));
}
//
// Pip (optional feature)
//
@@ -422,6 +433,13 @@ public abstract class WMShellBaseModule {
return new RootTaskDisplayAreaOrganizer(mainExecutor, context);
}
@WMSingleton
@Provides
static RootDisplayAreaOrganizer provideRootDisplayAreaOrganizer(
@ShellMainThread ShellExecutor mainExecutor) {
return new RootDisplayAreaOrganizer(mainExecutor);
}
@WMSingleton
@Provides
static Optional<SplitScreen> provideSplitScreen(

View File

@@ -34,12 +34,14 @@ import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.RemoteException;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.internal.policy.IKeyguardDrawnCallback;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -55,6 +57,8 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -63,6 +67,7 @@ import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -85,11 +90,14 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock NavigationModeController mNavigationModeController;
private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
private @Mock DozeParameters mDozeParameters;
private @Mock UnfoldTransitionConfig mUnfoldTransitionConfig;
private @Mock UnfoldLightRevealOverlayAnimation mUnfoldAnimation;
private @Mock SysuiStatusBarStateController mStatusBarStateController;
private @Mock KeyguardStateController mKeyguardStateController;
private @Mock NotificationShadeDepthController mNotificationShadeDepthController;
private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -120,6 +128,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mNavigationModeController,
mKeyguardDisplayManager,
mDozeParameters,
mUnfoldTransitionConfig,
() -> mUnfoldAnimation,
mStatusBarStateController,
mKeyguardStateController,
() -> mKeyguardUnlockAnimationController,
@@ -147,6 +157,33 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
verify(mStatusBarKeyguardViewManager).setKeyguardGoingAwayState(eq(false));
}
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public void testUnfoldTransitionEnabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback()
throws RemoteException {
when(mUnfoldTransitionConfig.isEnabled()).thenReturn(true);
mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
TestableLooper.get(this).processAllMessages();
onUnfoldOverlayReady();
// Should be called when both unfold overlay and keyguard drawn ready
verify(mKeyguardDrawnCallback).onDrawn();
}
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public void testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback()
throws RemoteException {
when(mUnfoldTransitionConfig.isEnabled()).thenReturn(false);
mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
TestableLooper.get(this).processAllMessages();
// Should be called when only keyguard drawn
verify(mKeyguardDrawnCallback).onDrawn();
}
@Test
public void testIsAnimatingScreenOff() {
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
@@ -187,4 +224,11 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
// then make sure it comes back
verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null);
}
private void onUnfoldOverlayReady() {
ArgumentCaptor<Runnable> overlayReadyCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(mUnfoldAnimation).onScreenTurningOn(overlayReadyCaptor.capture());
overlayReadyCaptor.getValue().run();
TestableLooper.get(this).processAllMessages();
}
}

View File

@@ -0,0 +1,178 @@
/*
* 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.systemui.unfold.updates
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class DeviceFoldStateProviderTest : SysuiTestCase() {
@Mock
private lateinit var hingeAngleProvider: HingeAngleProvider
@Mock
private lateinit var screenStatusProvider: ScreenStatusProvider
@Mock
private lateinit var deviceStateManager: DeviceStateManager
private lateinit var foldStateProvider: FoldStateProvider
private val foldUpdates: MutableList<Int> = arrayListOf()
private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
private val foldStateListenerCaptor = ArgumentCaptor.forClass(FoldStateListener::class.java)
private var foldedDeviceState: Int = 0
private var unfoldedDeviceState: Int = 0
private val screenOnListenerCaptor = ArgumentCaptor.forClass(ScreenListener::class.java)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
val foldedDeviceStates: IntArray = context.resources.getIntArray(
com.android.internal.R.array.config_foldedDeviceStates)
assumeTrue("Test should be launched on a foldable device",
foldedDeviceStates.isNotEmpty())
foldedDeviceState = foldedDeviceStates.maxOrNull()!!
unfoldedDeviceState = foldedDeviceState + 1
foldStateProvider = DeviceFoldStateProvider(
context,
hingeAngleProvider,
screenStatusProvider,
deviceStateManager,
context.mainExecutor
)
foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener {
override fun onHingeAngleUpdate(angle: Float) {
hingeAngleUpdates.add(angle)
}
override fun onFoldUpdate(update: Int) {
foldUpdates.add(update)
}
})
foldStateProvider.start()
verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture())
}
@Test
fun testOnFolded_emitsFinishClosedEvent() {
setFoldState(folded = true)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED)
}
@Test
fun testOnUnfolded_emitsStartOpeningEvent() {
setFoldState(folded = false)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING)
}
@Test
fun testOnFolded_stopsHingeAngleProvider() {
setFoldState(folded = true)
verify(hingeAngleProvider).stop()
}
@Test
fun testOnUnfolded_startsHingeAngleProvider() {
setFoldState(folded = false)
verify(hingeAngleProvider).start()
}
@Test
fun testFirstScreenOnEventWhenFolded_doesNotEmitEvents() {
setFoldState(folded = true)
foldUpdates.clear()
fireScreenOnEvent()
// Power button turn on
assertThat(foldUpdates).isEmpty()
}
@Test
fun testFirstScreenOnEventWhenUnfolded_doesNotEmitEvents() {
setFoldState(folded = false)
foldUpdates.clear()
fireScreenOnEvent()
assertThat(foldUpdates).isEmpty()
}
@Test
fun testFirstScreenOnEventAfterFoldAndUnfold_emitsUnfoldedScreenAvailableEvent() {
setFoldState(folded = false)
setFoldState(folded = true)
fireScreenOnEvent()
setFoldState(folded = false)
foldUpdates.clear()
fireScreenOnEvent()
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE)
}
@Test
fun testSecondScreenOnEventWhenUnfolded_doesNotEmitEvents() {
setFoldState(folded = false)
fireScreenOnEvent()
foldUpdates.clear()
fireScreenOnEvent()
// No events as this is power button turn on
assertThat(foldUpdates).isEmpty()
}
private fun setFoldState(folded: Boolean) {
val state = if (folded) foldedDeviceState else unfoldedDeviceState
foldStateListenerCaptor.value.onStateChanged(state)
}
private fun fireScreenOnEvent() {
screenOnListenerCaptor.value.onScreenTurnedOn()
}
}

View File

@@ -297,7 +297,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* The direct child layer of the display to put all non-overlay windows. This is also used for
* screen rotation animation so that there is a parent layer to put the animation leash.
*/
private final SurfaceControl mWindowingLayer;
private SurfaceControl mWindowingLayer;
/**
* The window token of the layer of the hierarchy to mirror, or null if this DisplayContent
@@ -329,7 +329,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private final ImeContainer mImeWindowsContainer = new ImeContainer(mWmService);
@VisibleForTesting
final DisplayAreaPolicy mDisplayAreaPolicy;
DisplayAreaPolicy mDisplayAreaPolicy;
private WindowState mTmpWindow;
private boolean mUpdateImeTarget;
@@ -1104,41 +1104,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mDividerControllerLocked = new DockedTaskDividerController(this);
mPinnedTaskController = new PinnedTaskController(mWmService, this);
final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
.setOpaque(true)
.setContainerLayer()
.setCallsite("DisplayContent");
mSurfaceControl = b.setName("Root").setContainerLayer().build();
// Setup the policy and build the display area hierarchy.
mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate(
mWmService, this /* content */, this /* root */, mImeWindowsContainer);
final List<DisplayArea<? extends WindowContainer>> areas =
mDisplayAreaPolicy.getDisplayAreas(FEATURE_WINDOWED_MAGNIFICATION);
final DisplayArea<?> area = areas.size() == 1 ? areas.get(0) : null;
if (area != null && area.getParent() == this) {
// The windowed magnification area should contain all non-overlay windows, so just use
// it as the windowing layer.
mWindowingLayer = area.mSurfaceControl;
} else {
// Need an additional layer for screen level animation, so move the layer containing
// the windows to the new root.
mWindowingLayer = mSurfaceControl;
mSurfaceControl = b.setName("RootWrapper").build();
getPendingTransaction().reparent(mWindowingLayer, mSurfaceControl)
.show(mWindowingLayer);
}
mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build();
getPendingTransaction()
.setLayer(mSurfaceControl, 0)
.setLayerStack(mSurfaceControl, mDisplayId)
.show(mSurfaceControl)
.setLayer(mOverlayLayer, Integer.MAX_VALUE)
.show(mOverlayLayer);
getPendingTransaction().apply();
final Transaction pendingTransaction = getPendingTransaction();
configureSurfaces(pendingTransaction);
pendingTransaction.apply();
// Sets the display content for the children.
onDisplayChanged(this);
@@ -1152,6 +1120,77 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
}
@Override
void migrateToNewSurfaceControl(Transaction t) {
t.remove(mSurfaceControl);
mLastSurfacePosition.set(0, 0);
configureSurfaces(t);
for (int i = 0; i < mChildren.size(); i++) {
SurfaceControl sc = mChildren.get(i).getSurfaceControl();
if (sc != null) {
t.reparent(sc, mSurfaceControl);
}
}
scheduleAnimation();
}
/**
* Configures the surfaces hierarchy for DisplayContent
* This method always recreates the main surface control but reparents the children
* if they are already created.
* @param transaction as part of which to perform the configuration
*/
private void configureSurfaces(Transaction transaction) {
final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
.setOpaque(true)
.setContainerLayer()
.setCallsite("DisplayContent");
mSurfaceControl = b.setName(getName()).setContainerLayer().build();
if (mDisplayAreaPolicy == null) {
// Setup the policy and build the display area hierarchy.
// Build the hierarchy only after creating the surface so it is reparented correctly
mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate(
mWmService, this /* content */, this /* root */,
mImeWindowsContainer);
}
final List<DisplayArea<? extends WindowContainer>> areas =
mDisplayAreaPolicy.getDisplayAreas(FEATURE_WINDOWED_MAGNIFICATION);
final DisplayArea<?> area = areas.size() == 1 ? areas.get(0) : null;
if (area != null && area.getParent() == this) {
// The windowed magnification area should contain all non-overlay windows, so just use
// it as the windowing layer.
mWindowingLayer = area.mSurfaceControl;
transaction.reparent(mWindowingLayer, mSurfaceControl);
} else {
// Need an additional layer for screen level animation, so move the layer containing
// the windows to the new root.
mWindowingLayer = mSurfaceControl;
mSurfaceControl = b.setName("RootWrapper").build();
transaction.reparent(mWindowingLayer, mSurfaceControl)
.show(mWindowingLayer);
}
if (mOverlayLayer == null) {
mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build();
} else {
transaction.reparent(mOverlayLayer, mSurfaceControl);
}
transaction
.setLayer(mSurfaceControl, 0)
.setLayerStack(mSurfaceControl, mDisplayId)
.show(mSurfaceControl)
.setLayer(mOverlayLayer, Integer.MAX_VALUE)
.show(mOverlayLayer);
}
boolean isReady() {
// The display is ready when the system and the individual display are both ready.
return mWmService.mDisplayReady && mDisplayReady;