Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 548320ff authored by Riddle Hsu's avatar Riddle Hsu Committed by Nick Chameyev
Browse files

Skip sleep-token when switching display which will be on

To reduce unnecessary visibility or lifecycle changes.
That may improve the latency of display switch and avoid
showing intermediate content.

Note: mRequestedOrSleepingDefaultDisplay cannot be used for
this case because it is set to true by screenTurningOff but
it doesn't mean the device will sleep.

Maybe commit 0687f9d3 and 1dbb239b can be removed after
adoping this concept.

Bug: 303241079
Test: atest PhoneWindowManagerTests#testScreenTurnedOff
Test: Fold/unfold a foldable device.
      With Settings > Display > Continue using apps on fold
      - Keep screen on fold
        Home keeps resumed and won't receive invisible.
      - Turn off screen on fold
        Home can receive invisible and stopped.
Change-Id: If3848b4a6d67d71da7d735af56fcfd967152c1f6
(cherry picked from commit 20e51039)
parent 9860e4e2
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -37,6 +37,17 @@ flag {
  bug: "291870756"
}

flag {
  name: "skip_sleeping_when_switching_display"
  namespace: "windowing_frontend"
  description: "Reduce unnecessary visibility or lifecycle changes when changing fold state"
  bug: "303241079"
  is_fixed_read_only: true
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}

flag {
  name: "introduce_smoother_dimmer"
  namespace: "windowing_frontend"
+23 −2
Original line number Diff line number Diff line
@@ -530,6 +530,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
    // TODO(b/178103325): Track sleep/requested sleep for every display.
    volatile boolean mRequestedOrSleepingDefaultDisplay;

    /**
     * This is used to check whether to invoke {@link #updateScreenOffSleepToken} when screen is
     * turned off. E.g. if it is false when screen is turned off and the display is swapping, it
     * is expected that the screen will be on in a short time. Then it is unnecessary to acquire
     * screen-off-sleep-token, so it can avoid intermediate visibility or lifecycle changes.
     */
    volatile boolean mIsGoingToSleepDefaultDisplay;

    volatile boolean mRecentsVisible;
    volatile boolean mNavBarVirtualKeyHapticFeedbackEnabled = true;
    volatile boolean mPictureInPictureVisible;
@@ -5464,6 +5472,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        }

        mRequestedOrSleepingDefaultDisplay = true;
        mIsGoingToSleepDefaultDisplay = true;

        // In case startedGoingToSleep is called after screenTurnedOff (the source caller is in
        // order but the methods run on different threads) and updateScreenOffSleepToken was
        // skipped. Then acquire sleep token if screen was off.
        if (!mDefaultDisplayPolicy.isScreenOnFully() && !mDefaultDisplayPolicy.isScreenOnEarly()
                && com.android.window.flags.Flags.skipSleepingWhenSwitchingDisplay()) {
            updateScreenOffSleepToken(true /* acquire */, false /* isSwappingDisplay */);
        }

        if (mKeyguardDelegate != null) {
            mKeyguardDelegate.onStartedGoingToSleep(pmSleepReason);
@@ -5487,6 +5504,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        MetricsLogger.histogram(mContext, "screen_timeout", mLockScreenTimeout / 1000);

        mRequestedOrSleepingDefaultDisplay = false;
        mIsGoingToSleepDefaultDisplay = false;
        mDefaultDisplayPolicy.setAwake(false);

        // We must get this work done here because the power manager will drop
@@ -5522,7 +5540,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        }
        EventLogTags.writeScreenToggled(1);


        mIsGoingToSleepDefaultDisplay = false;
        mDefaultDisplayPolicy.setAwake(true);

        // Since goToSleep performs these functions synchronously, we must
@@ -5624,7 +5642,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        if (DEBUG_WAKEUP) Slog.i(TAG, "Display" + displayId + " turned off...");

        if (displayId == DEFAULT_DISPLAY) {
            updateScreenOffSleepToken(true, isSwappingDisplay);
            if (!isSwappingDisplay || mIsGoingToSleepDefaultDisplay
                    || !com.android.window.flags.Flags.skipSleepingWhenSwitchingDisplay()) {
                updateScreenOffSleepToken(true /* acquire */, isSwappingDisplay);
            }
            mRequestedOrSleepingDefaultDisplay = false;
            mDefaultDisplayPolicy.screenTurnedOff();
            synchronized (mLock) {
+79 −4
Original line number Diff line number Diff line
@@ -16,10 +16,14 @@

package com.android.server.policy;

import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManagerGlobal.ADD_OKAY;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -33,18 +37,27 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;

import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.Context;
import android.os.PowerManager;
import android.platform.test.flag.junit.SetFlagsRule;

import androidx.test.filters.SmallTest;

import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.DisplayPolicy;
import com.android.server.wm.DisplayRotation;
import com.android.server.wm.WindowManagerInternal;

import org.junit.After;
import org.junit.Before;
@@ -64,16 +77,27 @@ public class PhoneWindowManagerTests {
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    PhoneWindowManager mPhoneWindowManager;
    private ActivityTaskManagerInternal mAtmInternal;
    private Context mContext;

    @Before
    public void setUp() {
        mPhoneWindowManager = spy(new PhoneWindowManager());
        spyOn(ActivityManager.getService());
        mContext = getInstrumentation().getTargetContext();
        spyOn(mContext);
        mAtmInternal = mock(ActivityTaskManagerInternal.class);
        LocalServices.addService(ActivityTaskManagerInternal.class, mAtmInternal);
        mPhoneWindowManager.mActivityTaskManagerInternal = mAtmInternal;
        LocalServices.addService(WindowManagerInternal.class, mock(WindowManagerInternal.class));
    }

    @After
    public void tearDown() {
        reset(ActivityManager.getService());
        reset(mContext);
        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
        LocalServices.removeServiceForTest(WindowManagerInternal.class);
    }

    @Test
@@ -98,6 +122,60 @@ public class PhoneWindowManagerTests {
        verify(mPhoneWindowManager).createHomeDockIntent();
    }

    @Test
    public void testScreenTurnedOff() {
        mSetFlagsRule.enableFlags(com.android.window.flags.Flags
                .FLAG_SKIP_SLEEPING_WHEN_SWITCHING_DISPLAY);
        doNothing().when(mPhoneWindowManager).updateSettings(any());
        doNothing().when(mPhoneWindowManager).initializeHdmiState();
        final boolean[] isScreenTurnedOff = { false };
        final DisplayPolicy displayPolicy = mock(DisplayPolicy.class);
        doAnswer(invocation -> isScreenTurnedOff[0] = true).when(displayPolicy).screenTurnedOff();
        doAnswer(invocation -> !isScreenTurnedOff[0]).when(displayPolicy).isScreenOnEarly();
        doAnswer(invocation -> !isScreenTurnedOff[0]).when(displayPolicy).isScreenOnFully();

        mPhoneWindowManager.mDefaultDisplayPolicy = displayPolicy;
        mPhoneWindowManager.mDefaultDisplayRotation = mock(DisplayRotation.class);
        final ActivityTaskManagerInternal.SleepTokenAcquirer tokenAcquirer =
                mock(ActivityTaskManagerInternal.SleepTokenAcquirer.class);
        doReturn(tokenAcquirer).when(mAtmInternal).createSleepTokenAcquirer(anyString());
        final PowerManager pm = mock(PowerManager.class);
        doReturn(true).when(pm).isInteractive();
        doReturn(pm).when(mContext).getSystemService(eq(Context.POWER_SERVICE));

        mContext.getMainThreadHandler().runWithScissors(() -> mPhoneWindowManager.init(
                new PhoneWindowManager.Injector(mContext,
                        mock(WindowManagerPolicy.WindowManagerFuncs.class))), 0);
        assertThat(isScreenTurnedOff[0]).isFalse();
        assertThat(mPhoneWindowManager.mIsGoingToSleepDefaultDisplay).isFalse();

        // Skip sleep-token for non-sleep-screen-off.
        clearInvocations(tokenAcquirer);
        mPhoneWindowManager.screenTurnedOff(DEFAULT_DISPLAY, true /* isSwappingDisplay */);
        verify(tokenAcquirer, never()).acquire(anyInt(), anyBoolean());
        assertThat(isScreenTurnedOff[0]).isTrue();

        // Apply sleep-token for sleep-screen-off.
        mPhoneWindowManager.startedGoingToSleep(DEFAULT_DISPLAY, 0 /* reason */);
        assertThat(mPhoneWindowManager.mIsGoingToSleepDefaultDisplay).isTrue();
        mPhoneWindowManager.screenTurnedOff(DEFAULT_DISPLAY, true /* isSwappingDisplay */);
        verify(tokenAcquirer).acquire(eq(DEFAULT_DISPLAY), eq(true));

        mPhoneWindowManager.finishedGoingToSleep(DEFAULT_DISPLAY, 0 /* reason */);
        assertThat(mPhoneWindowManager.mIsGoingToSleepDefaultDisplay).isFalse();

        // Simulate unexpected reversed order: screenTurnedOff -> startedGoingToSleep. The sleep
        // token can still be acquired.
        isScreenTurnedOff[0] = false;
        clearInvocations(tokenAcquirer);
        mPhoneWindowManager.screenTurnedOff(DEFAULT_DISPLAY, true /* isSwappingDisplay */);
        verify(tokenAcquirer, never()).acquire(anyInt(), anyBoolean());
        assertThat(displayPolicy.isScreenOnEarly()).isFalse();
        assertThat(displayPolicy.isScreenOnFully()).isFalse();
        mPhoneWindowManager.startedGoingToSleep(DEFAULT_DISPLAY, 0 /* reason */);
        verify(tokenAcquirer).acquire(eq(DEFAULT_DISPLAY), eq(false));
    }

    @Test
    public void testCheckAddPermission_withoutAccessibilityOverlay_noAccessibilityAppOpLogged() {
        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
@@ -130,11 +208,8 @@ public class PhoneWindowManagerTests {

    private void mockStartDockOrHome() throws Exception {
        doNothing().when(ActivityManager.getService()).stopAppSwitches();
        ActivityTaskManagerInternal mMockActivityTaskManagerInternal =
                mock(ActivityTaskManagerInternal.class);
        when(mMockActivityTaskManagerInternal.startHomeOnDisplay(
        when(mAtmInternal.startHomeOnDisplay(
                anyInt(), anyString(), anyInt(), anyBoolean(), anyBoolean())).thenReturn(false);
        mPhoneWindowManager.mActivityTaskManagerInternal = mMockActivityTaskManagerInternal;
        mPhoneWindowManager.mUserManagerInternal = mock(UserManagerInternal.class);
    }
}