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

Commit e72fa043 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Ensure device state availability in registration." into main

parents b16fb234 8de89b99
Loading
Loading
Loading
Loading
+14 −7
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.window.flags.Flags;

import java.util.ArrayList;
import java.util.List;
@@ -47,6 +48,7 @@ import java.util.concurrent.Executor;
@VisibleForTesting(visibility = Visibility.PACKAGE)
public final class DeviceStateManagerGlobal {
    @Nullable
    @GuardedBy("DeviceStateManagerGlobal.class")
    private static DeviceStateManagerGlobal sInstance;
    private static final String TAG = "DeviceStateManagerGlobal";
    private static final boolean DEBUG = Build.IS_DEBUGGABLE;
@@ -84,10 +86,12 @@ public final class DeviceStateManagerGlobal {
    @GuardedBy("mLock")
    private DeviceStateInfo mLastReceivedInfo;

    // Constructor should be called while holding the lock.
    // @GuardedBy("DeviceStateManagerGlobal.class") can't be used on constructors.
    @VisibleForTesting
    public DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) {
        mDeviceStateManager = deviceStateManager;
        registerCallbackIfNeededLocked();
        registerCallbackLocked();
    }

    /**
@@ -279,14 +283,17 @@ public final class DeviceStateManagerGlobal {
        }
    }

    private void registerCallbackIfNeededLocked() {
        if (mCallback != null) {
            return;
        }

    @GuardedBy("DeviceStateManagerGlobal.class")
    private void registerCallbackLocked() {
        mCallback = new DeviceStateManagerCallback();
        try {
            if (Flags.wlinfoOncreate()) {
                synchronized (mLock) {
                    mLastReceivedInfo = mDeviceStateManager.registerCallback(mCallback);
                }
            } else {
                mDeviceStateManager.registerCallback(mCallback);
            }
        } catch (RemoteException ex) {
            mCallback = null;
            throw ex.rethrowFromSystemServer();
+11 −3
Original line number Diff line number Diff line
@@ -32,16 +32,24 @@ interface IDeviceStateManager {
    DeviceStateInfo getDeviceStateInfo();

    /**
     * Registers a callback to receive notifications from the device state manager. Only one
     * callback can be registered per-process.
     * Registers a callback to receive notifications from the device state manager and returns the
     * current {@link DeviceStateInfo}. Only one callback can be registered per-process.
     * <p>
     * As the callback mechanism is used to alert the caller of changes to request status a callback
     * <b>MUST</b> be registered before calling {@link #requestState(IBinder, int, int)} or
     * {@link #cancelRequest(IBinder)}, otherwise an exception will be thrown.
     * <p>
     * Upon successful registration, this method returns the committed {@link DeviceStateInfo} if
     * available, ensuring the availability of the device state after the callback is registered.
     * This guarantees that the client will have access to the latest device state immediately upon
     * completion of the two-way IPC call.
     *
     * @param callback the callback to register for device state notifications.
     * @return DeviceStateInfo the current device state information including the committed state
     *         or null if no state has been committed by the {@link DeviceStateProvider} yet.
     * @throws SecurityException if a callback is already registered for the calling process.
     */
    void registerCallback(in IDeviceStateManagerCallback callback);
    @nullable DeviceStateInfo registerCallback(in IDeviceStateManagerCallback callback);

    /**
     * Requests that the device enter the supplied {@code state}. A callback <b>MUST</b> have been
+2 −0
Original line number Diff line number Diff line
@@ -28,8 +28,10 @@ android_test {
    static_libs: [
        "androidx.test.ext.junit",
        "androidx.test.rules",
        "flag-junit",
        "frameworks-base-testutils",
        "mockito-target-minus-junit4",
        "platform-parametric-runner-lib",
        "platform-test-annotations",
        "truth",
    ],
+74 −7
Original line number Diff line number Diff line
@@ -18,8 +18,10 @@ package android.hardware.devicestate;

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.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -29,15 +31,20 @@ import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.test.FakePermissionEnforcer;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

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

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@@ -46,6 +53,9 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;

import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;

/**
 * Unit tests for {@link DeviceStateManagerGlobal}.
 *
@@ -53,18 +63,30 @@ import java.util.Set;
 * atest FrameworksCoreDeviceStateManagerTests:DeviceStateManagerGlobalTest
 */
@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWith(ParameterizedAndroidJunit4.class)
public final class DeviceStateManagerGlobalTest {
    private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(
            new DeviceState.Configuration.Builder(0 /* identifier */, "" /* name */).build());
    private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(
            new DeviceState.Configuration.Builder(1 /* identifier */, "" /* name */).build());

    @Rule
    public final SetFlagsRule mSetFlagsRule;

    @Parameters(name = "{0}")
    public static List<FlagsParameterization> getParams() {
        return FlagsParameterization.allCombinationsOf(Flags.FLAG_WLINFO_ONCREATE);
    }

    @NonNull
    private TestDeviceStateManagerService mService;
    @NonNull
    private DeviceStateManagerGlobal mDeviceStateManagerGlobal;

    public DeviceStateManagerGlobalTest(FlagsParameterization flags) {
        mSetFlagsRule = new SetFlagsRule(flags);
    }

    @Before
    public void setUp() {
        final FakePermissionEnforcer permissionEnforcer = new FakePermissionEnforcer();
@@ -73,6 +95,36 @@ public final class DeviceStateManagerGlobalTest {
        assertThat(mService.mCallbacks).isNotEmpty();
    }

    @Test
    @DisableFlags(Flags.FLAG_WLINFO_ONCREATE)
    public void create_whenWlinfoOncreateIsDisabled_receivesDeviceStateInfoFromCallback() {
        final FakePermissionEnforcer permissionEnforcer = new FakePermissionEnforcer();
        final TestDeviceStateManagerService service = new TestDeviceStateManagerService(
                permissionEnforcer, true /* simulatePostCallback */);
        final DeviceStateManagerGlobal dsmGlobal = new DeviceStateManagerGlobal(service);
        final DeviceStateCallback callback = mock(DeviceStateCallback.class);
        dsmGlobal.registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);

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

        // Simulate DeviceStateManagerService#registerProcess by notifying clients of current device
        // state via callback.
        service.notifyDeviceStateInfoChanged();
        verify(callback).onDeviceStateChanged(eq(DEFAULT_DEVICE_STATE));
    }

    @Test
    @EnableFlags(Flags.FLAG_WLINFO_ONCREATE)
    public void create_whenWlinfoOncreateIsEnabled_returnsDeviceStateInfoFromRegistration() {
        final FakePermissionEnforcer permissionEnforcer = new FakePermissionEnforcer();
        final IDeviceStateManager service = new TestDeviceStateManagerService(permissionEnforcer);
        final DeviceStateManagerGlobal dsmGlobal = new DeviceStateManagerGlobal(service);
        final DeviceStateCallback callback = mock(DeviceStateCallback.class);
        dsmGlobal.registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);

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

    @Test
    public void registerCallback() {
        final DeviceStateCallback callback1 = mock(DeviceStateCallback.class);
@@ -267,10 +319,17 @@ public final class DeviceStateManagerGlobalTest {
        @Nullable
        private Request mBaseStateRequest;

        private final boolean mSimulatePostCallback;
        private final Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();

        TestDeviceStateManagerService(@NonNull FakePermissionEnforcer enforcer) {
            this(enforcer, false /* simulatePostCallback */);
        }

        TestDeviceStateManagerService(@NonNull FakePermissionEnforcer enforcer,
                boolean simulatePostCallback) {
            super(enforcer);
            mSimulatePostCallback = simulatePostCallback;
        }

        @NonNull
@@ -304,19 +363,27 @@ public final class DeviceStateManagerGlobalTest {
            return getInfo();
        }

        @Nullable
        @Override
        public void registerCallback(IDeviceStateManagerCallback callback) {
        public DeviceStateInfo registerCallback(IDeviceStateManagerCallback callback) {
            if (mCallbacks.contains(callback)) {
                throw new SecurityException("Callback is already registered.");
            }

            mCallbacks.add(callback);
            if (Flags.wlinfoOncreate()) {
                return getInfo();
            }

            if (!mSimulatePostCallback) {
                try {
                    callback.onDeviceStateInfoChanged(getInfo());
                } catch (RemoteException e) {
                    e.rethrowFromSystemServer();
                }
            }
            return null;
        }

        @Override
        public void requestState(@NonNull IBinder token, int state, int unusedFlags) {
+17 −10
Original line number Diff line number Diff line
@@ -778,7 +778,8 @@ public final class DeviceStateManagerService extends SystemService {
        processRecord.notifyRequestActiveAsync(request.getToken());
    }

    private void registerProcess(int pid, IDeviceStateManagerCallback callback) {
    @Nullable
    private DeviceStateInfo registerProcess(int pid, IDeviceStateManagerCallback callback) {
        synchronized (mLock) {
            if (mProcessRecords.contains(pid)) {
                throw new SecurityException("The calling process has already registered an"
@@ -794,16 +795,21 @@ public final class DeviceStateManagerService extends SystemService {
            }
            mProcessRecords.put(pid, record);

            // Callback clients should not be notified of invalid device states, so calls to
            // #getDeviceStateInfoLocked should be gated on checks if a committed state is present
            // before getting the device state info.
            final DeviceStateInfo currentInfo =
                    mCommittedState.isPresent() ? getDeviceStateInfoLocked() : null;
            if (com.android.window.flags.Flags.wlinfoOncreate()) {
                return currentInfo;
            } else {
                // Callback clients should not be notified of invalid device states, so calls to
                // #getDeviceStateInfoLocked should be gated on checks if a committed state is
                // present before getting the device state info.
                if (currentInfo != null) {
                // If there is not a committed state we'll wait to notify the process of the initial
                // value.
                    // If there is not a committed state we'll wait to notify the process of the
                    // initial value.
                    record.notifyDeviceStateInfoAsync(currentInfo);
                }
                return null;
            }
        }
    }

@@ -1286,8 +1292,9 @@ public final class DeviceStateManagerService extends SystemService {
            }
        }

        @Nullable
        @Override // Binder call
        public void registerCallback(IDeviceStateManagerCallback callback) {
        public DeviceStateInfo registerCallback(IDeviceStateManagerCallback callback) {
            if (callback == null) {
                throw new IllegalArgumentException("Device state callback must not be null.");
            }
@@ -1295,7 +1302,7 @@ public final class DeviceStateManagerService extends SystemService {
            final int callingPid = Binder.getCallingPid();
            final long token = Binder.clearCallingIdentity();
            try {
                registerProcess(callingPid, callback);
                return registerProcess(callingPid, callback);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
Loading