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

Commit 323dc46c authored by Eric Lin's avatar Eric Lin Committed by Android (Google) Code Review
Browse files

Merge "Improve device state callback for thread context." into main

parents d9633805 8102e840
Loading
Loading
Loading
Loading
+53 −34
Original line number Diff line number Diff line
@@ -16,13 +16,17 @@

package android.hardware.devicestate;

import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;

@@ -40,7 +44,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;

import com.android.internal.util.ConcurrentUtils;
import com.android.window.flags.Flags;

import org.junit.Before;
@@ -52,6 +55,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
@@ -103,7 +107,7 @@ public final class DeviceStateManagerGlobalTest {
                permissionEnforcer, true /* simulatePostCallback */);
        final DeviceStateManagerGlobal dsmGlobal = new DeviceStateManagerGlobal(service);
        final DeviceStateCallback callback = mock(DeviceStateCallback.class);
        dsmGlobal.registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
        dsmGlobal.registerDeviceStateCallback(callback, DIRECT_EXECUTOR);

        verify(callback, never()).onDeviceStateChanged(any());

@@ -120,49 +124,68 @@ public final class DeviceStateManagerGlobalTest {
        final IDeviceStateManager service = new TestDeviceStateManagerService(permissionEnforcer);
        final DeviceStateManagerGlobal dsmGlobal = new DeviceStateManagerGlobal(service);
        final DeviceStateCallback callback = mock(DeviceStateCallback.class);
        dsmGlobal.registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
        dsmGlobal.registerDeviceStateCallback(callback, DIRECT_EXECUTOR);

        verify(callback).onDeviceStateChanged(eq(DEFAULT_DEVICE_STATE));
    }

    @Test
    public void registerCallback() {
        final DeviceStateCallback callback1 = mock(DeviceStateCallback.class);
        final DeviceStateCallback callback2 = mock(DeviceStateCallback.class);
    public void registerCallback_usesExecutorForCallbacks() {
        final DeviceStateCallback callback = mock(DeviceStateCallback.class);
        final Executor executor = mock(Executor.class);
        doAnswer(invocation -> {
            Runnable runnable = (Runnable) invocation.getArguments()[0];
            runnable.run();
            return null;
        }).when(executor).execute(any(Runnable.class));

        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback1,
                ConcurrentUtils.DIRECT_EXECUTOR);
        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback2,
                ConcurrentUtils.DIRECT_EXECUTOR);
        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, executor);
        mService.setBaseState(OTHER_DEVICE_STATE);
        mService.setSupportedStates(List.of(OTHER_DEVICE_STATE));

        // Verify initial callbacks
        verify(callback1).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
        verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
        verify(callback2).onDeviceStateChanged(eq(mService.getMergedState()));
        // Verify that the given executor is used for both initial and subsequent callbacks.
        verify(executor, times(4)).execute(any(Runnable.class));
    }

        reset(callback1);
        reset(callback2);
    @Test
    public void registerCallback_supportedStatesChanged() {
        final DeviceStateCallback callback1 = mock(DeviceStateCallback.class);
        final DeviceStateCallback callback2 = mock(DeviceStateCallback.class);
        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback1, DIRECT_EXECUTOR);
        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback2, DIRECT_EXECUTOR);

        // Change the supported states and verify callback
        // Change the supported states and verify callback.
        mService.setSupportedStates(List.of(DEFAULT_DEVICE_STATE));

        verify(callback1).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
        verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
        mService.setSupportedStates(List.of(DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE));
    }

        reset(callback1);
        reset(callback2);
    @Test
    public void registerCallback_baseStateChanged() {
        final DeviceStateCallback callback1 = mock(DeviceStateCallback.class);
        final DeviceStateCallback callback2 = mock(DeviceStateCallback.class);
        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback1, DIRECT_EXECUTOR);
        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback2, DIRECT_EXECUTOR);
        mService.setSupportedStates(List.of(DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE));

        // Change the base state and verify callback
        // Change the base state and verify callback.
        mService.setBaseState(OTHER_DEVICE_STATE);

        verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
        verify(callback2).onDeviceStateChanged(eq(mService.getMergedState()));
    }

        reset(callback1);
        reset(callback2);

        // Change the requested state and verify callback
    @Test
    public void registerCallback_requestedStateChanged() {
        final DeviceStateCallback callback1 = mock(DeviceStateCallback.class);
        final DeviceStateCallback callback2 = mock(DeviceStateCallback.class);
        final DeviceStateRequest request =
                DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE.getIdentifier()).build();
        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback1, DIRECT_EXECUTOR);
        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback2, DIRECT_EXECUTOR);

        // Change the requested state and verify callback.
        mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);

        verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
@@ -173,8 +196,7 @@ public final class DeviceStateManagerGlobalTest {
    public void unregisterCallback() {
        final DeviceStateCallback callback = mock(DeviceStateCallback.class);

        mDeviceStateManagerGlobal
                .registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, DIRECT_EXECUTOR);

        // Verify initial callbacks
        verify(callback).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
@@ -191,8 +213,7 @@ public final class DeviceStateManagerGlobalTest {
    @Test
    public void submitRequest() {
        final DeviceStateCallback callback = mock(DeviceStateCallback.class);
        mDeviceStateManagerGlobal
                .registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, DIRECT_EXECUTOR);

        verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
        reset(callback);
@@ -212,8 +233,7 @@ public final class DeviceStateManagerGlobalTest {
    @Test
    public void submitBaseStateOverrideRequest() {
        final DeviceStateCallback callback = mock(DeviceStateCallback.class);
        mDeviceStateManagerGlobal
                .registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, DIRECT_EXECUTOR);

        verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
        reset(callback);
@@ -234,8 +254,7 @@ public final class DeviceStateManagerGlobalTest {
    @Test
    public void submitBaseAndEmulatedStateOverride() {
        final DeviceStateCallback callback = mock(DeviceStateCallback.class);
        mDeviceStateManagerGlobal
                .registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, DIRECT_EXECUTOR);

        verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
        reset(callback);
@@ -275,7 +294,7 @@ public final class DeviceStateManagerGlobalTest {
        final DeviceStateRequest request =
                DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE.getIdentifier()).build();
        mDeviceStateManagerGlobal.requestState(request,
                ConcurrentUtils.DIRECT_EXECUTOR /* executor */,
                DIRECT_EXECUTOR /* executor */,
                callback /* callback */);

        verify(callback).onRequestActivated(eq(request));
+23 −13
Original line number Diff line number Diff line
@@ -26,10 +26,12 @@ import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.hardware.devicestate.DeviceStateUtil;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;

import androidx.annotation.BinderThread;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -43,6 +45,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

/**
@@ -68,39 +71,49 @@ public final class DeviceStateManagerFoldingFeatureProducer
     */
    private DeviceState mCurrentDeviceState = INVALID_DEVICE_STATE;

    @NonNull
    private final Context mContext;

    @NonNull
    private final RawFoldingFeatureProducer mRawFoldSupplier;

    @NonNull
    private final DeviceStateMapper mDeviceStateMapper;

    @VisibleForTesting
    final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
        // The GuardedBy analysis is intra-procedural, meaning it doesn’t consider the getData()
        // implementation. See https://errorprone.info/bugpattern/GuardedBy for limitations.
        @SuppressWarnings("GuardedBy")
        @MainThread
    private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
        @BinderThread // Subsequent callback after registered.
        @MainThread // Initial callback if registration is on the main thread.
        @Override
        public void onDeviceStateChanged(@NonNull DeviceState state) {
            mCurrentDeviceState = state;
            mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer.this
                    ::notifyFoldingFeatureChangeLocked);
            final boolean isMainThread = Looper.myLooper() == Looper.getMainLooper();
            final Executor executor = isMainThread ? Runnable::run : mContext.getMainExecutor();
            executor.execute(() -> {
                DeviceStateManagerFoldingFeatureProducer.this.onDeviceStateChanged(state);
            });
        }
    };

    public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
            @NonNull RawFoldingFeatureProducer rawFoldSupplier,
            @NonNull DeviceStateManager deviceStateManager) {
        mContext = context;
        mRawFoldSupplier = rawFoldSupplier;
        mDeviceStateMapper =
                new DeviceStateMapper(context, deviceStateManager.getSupportedDeviceStates());

        if (!mDeviceStateMapper.isDeviceStateToPostureMapEmpty()) {
            Objects.requireNonNull(deviceStateManager)
                    .registerCallback(context.getMainExecutor(), mDeviceStateCallback);
                    .registerCallback(Runnable::run, mDeviceStateCallback);
        }
    }

    @MainThread
    @VisibleForTesting
    void onDeviceStateChanged(@NonNull DeviceState state) {
        mCurrentDeviceState = state;
        mRawFoldSupplier.getData(this::notifyFoldingFeatureChangeLocked);
    }

    /**
     * Add a callback to mCallbacks if there is no device state. This callback will be run
     * once a device state is set. Otherwise,run the callback immediately.
@@ -118,9 +131,6 @@ public final class DeviceStateManagerFoldingFeatureProducer
        }
    }

    // The GuardedBy analysis is intra-procedural, meaning it doesn’t consider the implementation of
    // addDataChangedCallback(). See https://errorprone.info/bugpattern/GuardedBy for limitations.
    @SuppressWarnings("GuardedBy")
    @Override
    protected void onListenersChanged() {
        super.onListenersChanged();
+34 −7
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.res.Resources
import android.hardware.devicestate.DeviceState
import android.hardware.devicestate.DeviceStateManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.window.common.layout.CommonFoldingFeature
import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_FLAT
import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_HALF_OPENED
@@ -33,13 +34,14 @@ import androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATU
import com.android.internal.R
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import java.util.concurrent.Executor
import java.util.function.Consumer
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.stub
@@ -69,14 +71,39 @@ class DeviceStateManagerFoldingFeatureProducerTest {
    }

    @Test
    fun testRegisterCallback_usesMainExecutor() {
    fun testRegisterCallback_initialCallbackOnMainThread_executesDirectly() {
        DeviceStateManagerFoldingFeatureProducer(
            mMockContext,
            mRawFoldSupplier,
            mMockDeviceStateManager,
        )
        val callbackCaptor = argumentCaptor<DeviceStateManager.DeviceStateCallback>()
        verify(mMockDeviceStateManager).registerCallback(any(), callbackCaptor.capture())

        verify(mMockDeviceStateManager).registerCallback(eq(mMockContext.mainExecutor), any())
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            callbackCaptor.firstValue.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
        }

        verify(mMockContext, never()).getMainExecutor()
    }

    @Test
    fun testRegisterCallback_subsequentCallbacks_postsToMainThread() {
        val mockMainExecutor = mock<Executor>()
        mMockContext.stub {
            on { getMainExecutor() } doReturn mockMainExecutor
        }
        DeviceStateManagerFoldingFeatureProducer(
            mMockContext,
            mRawFoldSupplier,
            mMockDeviceStateManager,
        )
        val callbackCaptor = argumentCaptor<DeviceStateManager.DeviceStateCallback>()
        verify(mMockDeviceStateManager).registerCallback(any(), callbackCaptor.capture())

        callbackCaptor.firstValue.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)

        verify(mockMainExecutor).execute(any())
    }

    @Test
@@ -86,7 +113,7 @@ class DeviceStateManagerFoldingFeatureProducerTest {
            mRawFoldSupplier,
            mMockDeviceStateManager,
        )
        ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
        ffp.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)

        val currentData = ffp.getCurrentData()

@@ -209,7 +236,7 @@ class DeviceStateManagerFoldingFeatureProducerTest {
            mRawFoldSupplier,
            mMockDeviceStateManager,
        )
        ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
        ffp.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
        val storeFeaturesConsumer = mock<Consumer<List<CommonFoldingFeature>>>()

        ffp.getData(storeFeaturesConsumer)
@@ -229,8 +256,8 @@ class DeviceStateManagerFoldingFeatureProducerTest {
        ffp.getData(storeFeaturesConsumer)

        verify(storeFeaturesConsumer, never()).accept(any())
        ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
        ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_OPENED)
        ffp.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
        ffp.onDeviceStateChanged(DEVICE_STATE_OPENED)
        verify(storeFeaturesConsumer).accept(HALF_OPENED_FOLDING_FEATURES)
    }