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

Commit dd41fa9f authored by Mike Schneider's avatar Mike Schneider Committed by Automerger Merge Worker
Browse files

Merge "Add SCREEN_STATE flags to QuickStepContract" into udc-dev am: 8348312f am: bf02cb77

parents 10261c80 bf02cb77
Loading
Loading
Loading
Loading
+27 −1
Original line number Diff line number Diff line
@@ -116,6 +116,27 @@ public class QuickStepContract {
    public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
    // Device dreaming state
    public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27;
    // Whether the screen is currently on. Note that the screen is considered on while turning on,
    // but not while turning off.
    public static final int SYSUI_STATE_SCREEN_ON = 1 << 28;
    // Whether the screen is currently transitioning into the state indicated by
    // SYSUI_STATE_SCREEN_ON.
    public static final int SYSUI_STATE_SCREEN_TRANSITION = 1 << 29;

    // Mask for SystemUiStateFlags to isolate SYSUI_STATE_SCREEN_ON and
    // SYSUI_STATE_SCREEN_TRANSITION, to match SCREEN_STATE_*
    public static final int SYSUI_STATE_SCREEN_STATE_MASK =
            SYSUI_STATE_SCREEN_ON | SYSUI_STATE_SCREEN_TRANSITION;
    // Screen is off.
    public static final int SCREEN_STATE_OFF = 0;
    // Screen is on.
    public static final int SCREEN_STATE_ON = SYSUI_STATE_SCREEN_ON;
    // Screen is still on, but transitioning to turn off.
    public static final int SCREEN_STATE_TURNING_OFF = SYSUI_STATE_SCREEN_TRANSITION;
    // Screen was off and is now turning on.
    public static final int SCREEN_STATE_TURNING_ON =
            SYSUI_STATE_SCREEN_TRANSITION | SYSUI_STATE_SCREEN_ON;

    // Whether the back gesture is allowed (or ignored) by the Shade
    public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = SystemProperties.getBoolean(
            "persist.wm.debug.shade_allow_back_gesture", false);
@@ -148,7 +169,9 @@ public class QuickStepContract {
            SYSUI_STATE_IMMERSIVE_MODE,
            SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING,
            SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE,
            SYSUI_STATE_DEVICE_DREAMING
            SYSUI_STATE_DEVICE_DREAMING,
            SYSUI_STATE_SCREEN_ON,
            SYSUI_STATE_SCREEN_TRANSITION,
    })
    public @interface SystemUiStateFlags {}

@@ -187,6 +210,9 @@ public class QuickStepContract {
        str.add((flags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0
                ? "freeform_active_in_desktop_mode" : "");
        str.add((flags & SYSUI_STATE_DEVICE_DREAMING) != 0 ? "device_dreaming" : "");
        str.add("screen_"
                + ((flags & SYSUI_STATE_SCREEN_TRANSITION) != 0 ? "turning_" : "")
                + ((flags & SYSUI_STATE_SCREEN_ON) != 0 ? "on" : "off"));

        return str.toString();
    }
+49 −5
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WIN
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_ON;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_TRANSITION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -121,7 +123,8 @@ import dagger.Lazy;
public class OverviewProxyService implements CallbackController<OverviewProxyListener>,
        NavigationModeController.ModeChangedListener, Dumpable {

    private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
    @VisibleForTesting
    static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";

    public static final String TAG_OPS = "OverviewProxyService";
    private static final long BACKOFF_MILLIS = 1000;
@@ -548,6 +551,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
        mUiEventLogger = uiEventLogger;
        mDisplayTracker = displayTracker;
        mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
        mSysuiUnlockAnimationController = sysuiUnlockAnimationController;

        dumpManager.registerDumpable(getClass().getSimpleName(), this);

@@ -596,7 +600,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
        // Connect to the service
        updateEnabledState();
        startConnectionToCurrentUser();
        mSysuiUnlockAnimationController = sysuiUnlockAnimationController;

        // Listen for assistant changes
        assistUtils.registerVoiceInteractionSessionListener(mVoiceInteractionSessionListener);
@@ -726,10 +729,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
            return;
        }
        mHandler.removeCallbacks(mConnectionRunnable);
        Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP)
                .setPackage(mRecentsComponentName.getPackageName());
        try {
            mBound = mContext.bindServiceAsUser(launcherServiceIntent,
            mBound = mContext.bindServiceAsUser(mQuickStepIntent,
                    mOverviewServiceConnection,
                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                    UserHandle.of(mUserTracker.getUserId()));
@@ -862,6 +863,11 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
         */
        @Override
        public void onScreenTurnedOn() {
            mSysUiState
                .setFlag(SYSUI_STATE_SCREEN_ON, true)
                .setFlag(SYSUI_STATE_SCREEN_TRANSITION, false)
                .commitUpdate(mContext.getDisplayId());

            try {
                if (mOverviewProxy != null) {
                    mOverviewProxy.onScreenTurnedOn();
@@ -873,11 +879,27 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
            }
        }

        /**
         * Notifies the Launcher that screen turned off.
         */
        @Override
        public void onScreenTurnedOff() {
            mSysUiState
                .setFlag(SYSUI_STATE_SCREEN_ON, false)
                .setFlag(SYSUI_STATE_SCREEN_TRANSITION, false)
                .commitUpdate(mContext.getDisplayId());
        }

        /**
         * Notifies the Launcher that screen is starting to turn on.
         */
        @Override
        public void onScreenTurningOff() {
            mSysUiState
                .setFlag(SYSUI_STATE_SCREEN_ON, false)
                .setFlag(SYSUI_STATE_SCREEN_TRANSITION, true)
                .commitUpdate(mContext.getDisplayId());

            try {
                if (mOverviewProxy != null) {
                    mOverviewProxy.onScreenTurningOff();
@@ -894,6 +916,11 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
         */
        @Override
        public void onScreenTurningOn() {
            mSysUiState
                .setFlag(SYSUI_STATE_SCREEN_ON, true)
                .setFlag(SYSUI_STATE_SCREEN_TRANSITION, true)
                .commitUpdate(mContext.getDisplayId());

            try {
                if (mOverviewProxy != null) {
                    mOverviewProxy.onScreenTurningOn();
@@ -1005,4 +1032,21 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
        default void onAssistantGestureCompletion(float velocity) {}
        default void startAssistant(Bundle bundle) {}
    }

    /**
     * Shuts down this service at the end of a testcase.
     * <p>
     * The in-production service is never shuts down, and it was not designed with testing in mind.
     * This unregisters the mechanisms by which the service will be revived after a testcase.
     * <p>
     * NOTE: This is a stop-gap introduced when first added some tests to this class. It should
     * probably be replaced by proper lifecycle management on this class.
     */
    @VisibleForTesting()
    void shutdownForTest() {
        mContext.unregisterReceiver(mLauncherStateChangedReceiver);
        mIsEnabled = false;
        mHandler.removeCallbacks(mConnectionRunnable);
        disconnectFromLauncherService();
    }
}
+186 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.recents

import android.content.ComponentName
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.testing.AndroidTestingRunner
import android.testing.TestableContext
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.app.AssistUtils
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.model.SysUiState
import com.android.systemui.navigationbar.NavigationBarController
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.recents.OverviewProxyService.ACTION_QUICKSTEP
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.recents.IOverviewProxy
import com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_OFF
import com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_ON
import com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_TURNING_OFF
import com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_TURNING_ON
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_STATE_MASK
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.sysui.ShellInterface
import com.google.common.util.concurrent.MoreExecutors
import dagger.Lazy
import java.util.Optional
import java.util.concurrent.Executor
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.intThat
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class OverviewProxyServiceTest : SysuiTestCase() {

    @Main private val executor: Executor = MoreExecutors.directExecutor()

    private lateinit var subject: OverviewProxyService
    private val dumpManager = DumpManager()
    private val displayTracker = FakeDisplayTracker(mContext)
    private val sysUiState = SysUiState(displayTracker)
    private val screenLifecycle = ScreenLifecycle(dumpManager)

    @Mock private lateinit var overviewProxy: IOverviewProxy.Stub
    @Mock private lateinit var packageManager: PackageManager

    // The following mocks belong to not-yet-tested parts of OverviewProxyService.
    @Mock private lateinit var commandQueue: CommandQueue
    @Mock private lateinit var shellInterface: ShellInterface
    @Mock private lateinit var navBarController: NavigationBarController
    @Mock private lateinit var centralSurfaces: CentralSurfaces
    @Mock private lateinit var navModeController: NavigationModeController
    @Mock private lateinit var statusBarWinController: NotificationShadeWindowController
    @Mock private lateinit var userTracker: UserTracker
    @Mock private lateinit var uiEventLogger: UiEventLogger
    @Mock private lateinit var sysuiUnlockAnimationController: KeyguardUnlockAnimationController
    @Mock private lateinit var assistUtils: AssistUtils
    @Mock
    private lateinit var unfoldTransitionProgressForwarder:
        Optional<UnfoldTransitionProgressForwarder>

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        val serviceComponent = ComponentName("test_package", "service_provider")
        context.addMockService(serviceComponent, overviewProxy)
        context.addMockServiceResolver(
            TestableContext.MockServiceResolver {
                if (it.action == ACTION_QUICKSTEP) serviceComponent else null
            }
        )
        whenever(overviewProxy.queryLocalInterface(ArgumentMatchers.anyString()))
            .thenReturn(overviewProxy)
        whenever(overviewProxy.asBinder()).thenReturn(overviewProxy)

        // packageManager.resolveServiceAsUser has to return non-null for
        // OverviewProxyService#isEnabled to become true.
        context.setMockPackageManager(packageManager)
        whenever(packageManager.resolveServiceAsUser(any(), anyInt(), anyInt()))
            .thenReturn(mock(ResolveInfo::class.java))

        subject =
            OverviewProxyService(
                context,
                executor,
                commandQueue,
                shellInterface,
                Lazy { navBarController },
                Lazy { Optional.of(centralSurfaces) },
                navModeController,
                statusBarWinController,
                sysUiState,
                userTracker,
                screenLifecycle,
                uiEventLogger,
                displayTracker,
                sysuiUnlockAnimationController,
                assistUtils,
                dumpManager,
                unfoldTransitionProgressForwarder
            )
    }

    @After
    fun tearDown() {
        subject.shutdownForTest()
    }

    @Test
    fun `ScreenLifecycle - screenTurnedOn triggers SysUI state flag changes `() {
        screenLifecycle.dispatchScreenTurnedOn()

        verify(overviewProxy)
            .onSystemUiStateChanged(
                intThat { it and SYSUI_STATE_SCREEN_STATE_MASK == SCREEN_STATE_ON }
            )
    }

    @Test
    fun `ScreenLifecycle - screenTurningOn triggers SysUI state flag changes `() {
        screenLifecycle.dispatchScreenTurningOn()

        verify(overviewProxy)
            .onSystemUiStateChanged(
                intThat { it and SYSUI_STATE_SCREEN_STATE_MASK == SCREEN_STATE_TURNING_ON }
            )
    }

    @Test
    fun `ScreenLifecycle - screenTurnedOff triggers SysUI state flag changes `() {
        screenLifecycle.dispatchScreenTurnedOff()

        verify(overviewProxy)
            .onSystemUiStateChanged(
                intThat { it and SYSUI_STATE_SCREEN_STATE_MASK == SCREEN_STATE_OFF }
            )
    }

    @Test
    fun `ScreenLifecycle - screenTurningOff triggers SysUI state flag changes `() {
        screenLifecycle.dispatchScreenTurningOff()

        verify(overviewProxy)
            .onSystemUiStateChanged(
                intThat { it and SYSUI_STATE_SCREEN_STATE_MASK == SCREEN_STATE_TURNING_OFF }
            )
    }
}
+67 −15
Original line number Diff line number Diff line
@@ -33,11 +33,15 @@ import android.provider.Settings;
import android.util.ArrayMap;
import android.view.LayoutInflater;

import androidx.annotation.Nullable;

import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

import java.util.ArrayList;

/**
 * A ContextWrapper with utilities specifically designed to make Testing easier.
 *
@@ -61,6 +65,7 @@ public class TestableContext extends ContextWrapper implements TestRule {
    private final TestableContentResolver mTestableContentResolver;
    private final TestableSettingsProvider mSettingsProvider;

    private ArrayList<MockServiceResolver> mMockServiceResolvers;
    private ArrayMap<String, Object> mMockSystemServices;
    private ArrayMap<ComponentName, IBinder> mMockServices;
    private ArrayMap<ServiceConnection, ComponentName> mActiveServices;
@@ -220,19 +225,48 @@ public class TestableContext extends ContextWrapper implements TestRule {
     * with the specified service, and will call {@link ServiceConnection#onServiceDisconnected}
     * when the service is unbound.
     * </p>
     *
     * @see #addMockServiceResolver(MockServiceResolver) for custom resolution of service Intents to
     * ComponentNames
     */
    public void addMockService(ComponentName component, IBinder service) {
        if (mMockServices == null) mMockServices = new ArrayMap<>();
        mMockServices.put(component, service);
    }

    /**
     * Strategy to resolve a service {@link Intent} to a mock service {@link ComponentName}.
     */
    public interface MockServiceResolver {
        @Nullable
        ComponentName resolve(Intent service);
    }

    /**
     * Registers a strategy to resolve service intents to registered mock services.
     * <p>
     * The result of the first {@link MockServiceResolver} to return a non-null
     * {@link ComponentName} is used to look up a mock service. The mock service must be registered
     * via {@link #addMockService(ComponentName, IBinder)} separately, using the same component
     * name.
     *
     * If none of the resolvers return a non-null value, or the first returned component name
     * does not link to a registered mock service, the bind requests are passed to the base context
     *
     * The resolvers are queried in order of registration.
     */
    public void addMockServiceResolver(MockServiceResolver resolver) {
        if (mMockServiceResolvers == null) mMockServiceResolvers = new ArrayList<>();
        mMockServiceResolvers.add(resolver);
    }

    /**
     * @see #addMockService(ComponentName, IBinder)
     */
    @Override
    public boolean bindService(Intent service, ServiceConnection conn, int flags) {
        if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
        if (checkMocks(service.getComponent(), conn)) return true;
        if (checkMocks(service, conn)) return true;
        return super.bindService(service, conn, flags);
    }

@@ -243,7 +277,7 @@ public class TestableContext extends ContextWrapper implements TestRule {
    public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
            Handler handler, UserHandle user) {
        if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
        if (checkMocks(service.getComponent(), conn)) return true;
        if (checkMocks(service, conn)) return true;
        return super.bindServiceAsUser(service, conn, flags, handler, user);
    }

@@ -254,18 +288,36 @@ public class TestableContext extends ContextWrapper implements TestRule {
    public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
            UserHandle user) {
        if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
        if (checkMocks(service.getComponent(), conn)) return true;
        if (checkMocks(service, conn)) return true;
        return super.bindServiceAsUser(service, conn, flags, user);
    }

    private boolean checkMocks(ComponentName component, ServiceConnection conn) {
        if (mMockServices != null && component != null && mMockServices.containsKey(component)) {
    private boolean checkMocks(Intent service, ServiceConnection conn) {
        if (mMockServices == null) return false;

        ComponentName serviceComponent = resolveMockServiceComponent(service);
        if (serviceComponent == null) return false;

        IBinder serviceImpl = mMockServices.get(serviceComponent);
        if (serviceImpl == null) return false;

        if (mActiveServices == null) mActiveServices = new ArrayMap<>();
            mActiveServices.put(conn, component);
            conn.onServiceConnected(component, mMockServices.get(component));
        mActiveServices.put(conn, serviceComponent);
        conn.onServiceConnected(serviceComponent, serviceImpl);
        return true;
    }
        return false;

    private ComponentName resolveMockServiceComponent(Intent service) {
        ComponentName specifiedComponentName = service.getComponent();
        if (specifiedComponentName != null) return specifiedComponentName;

        if (mMockServiceResolvers == null) return null;

        for (MockServiceResolver resolver : mMockServiceResolvers) {
            ComponentName resolvedComponent = resolver.resolve(service);
            if (resolvedComponent != null) return resolvedComponent;
        }
        return null;
    }

    /**