summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2020-09-10 16:52:48 +0000
committer Darryl L Johnson <darryljohnson@google.com> 2020-10-07 13:49:09 -0700
commitb16775425a1c713b5fbcbf5872362582e6adcf0d (patch)
tree3f2835121300e0cd90f2d6da6026e31fc93fdc1d
parente9988880468f8df266e7c96f1071ee443f675f93 (diff)
Add DeviceStateManagerService and stub implementation of policy.
Test: atest DeviceStateManagerServiceTest Bug: 159401801 Change-Id: Ib7f424e905c80812b36e0e0bb30771224ce56e34
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java270
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStatePolicy.java40
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateProvider.java69
-rw-r--r--services/core/java/com/android/server/devicestate/OWNERS3
-rw-r--r--services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java44
-rw-r--r--services/core/java/com/android/server/policy/DeviceStateProviderImpl.java45
-rw-r--r--services/java/com/android/server/SystemServer.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java224
8 files changed, 700 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
new file mode 100644
index 000000000000..4cff5c013bda
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -0,0 +1,270 @@
+/*
+ * 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.server.devicestate;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.util.IntArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+import com.android.server.policy.DeviceStatePolicyImpl;
+
+import java.util.Arrays;
+
+/**
+ * A system service that manages the state of a device with user-configurable hardware like a
+ * foldable phone.
+ * <p>
+ * Device state is an abstract concept that allows mapping the current state of the device to the
+ * state of the system. For example, system services (like
+ * {@link com.android.server.display.DisplayManagerService display manager} and
+ * {@link com.android.server.wm.WindowManagerService window manager}) and system UI may have
+ * different behaviors depending on the physical state of the device. This is useful for
+ * variable-state devices, like foldable or rollable devices, that can be configured by users into
+ * differing hardware states, which each may have a different expected use case.
+ * </p>
+ * <p>
+ * The {@link DeviceStateManagerService} is responsible for receiving state change requests from
+ * the {@link DeviceStateProvider} to modify the current device state and communicating with the
+ * {@link DeviceStatePolicy policy} to ensure the system is configured to match the requested state.
+ * </p>
+ *
+ * @see DeviceStatePolicy
+ */
+public final class DeviceStateManagerService extends SystemService {
+ /** Invalid device state. */
+ public static final int INVALID_DEVICE_STATE = -1;
+
+ private static final String TAG = "DeviceStateManagerService";
+ private static final boolean DEBUG = false;
+
+ private final Object mLock = new Object();
+ @NonNull
+ private final DeviceStatePolicy mDeviceStatePolicy;
+
+ @GuardedBy("mLock")
+ private IntArray mSupportedDeviceStates;
+
+ // The current committed device state.
+ @GuardedBy("mLock")
+ private int mCommittedState = INVALID_DEVICE_STATE;
+ // The device state that is currently pending callback from the policy to be committed.
+ @GuardedBy("mLock")
+ private int mPendingState = INVALID_DEVICE_STATE;
+ // Whether or not the policy is currently waiting to be notified of the current pending state.
+ @GuardedBy("mLock")
+ private boolean mIsPolicyWaitingForState = false;
+ // The device state that is currently requested and is next to be configured and committed.
+ @GuardedBy("mLock")
+ private int mRequestedState = INVALID_DEVICE_STATE;
+
+ public DeviceStateManagerService(@NonNull Context context) {
+ this(context, new DeviceStatePolicyImpl());
+ }
+
+ @VisibleForTesting
+ DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) {
+ super(context);
+ mDeviceStatePolicy = policy;
+ }
+
+ @Override
+ public void onStart() {
+ mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener());
+ }
+
+ /**
+ * Returns the current state the system is in. Note that the system may be in the process of
+ * configuring a different state.
+ *
+ * @see #getPendingState()
+ */
+ @VisibleForTesting
+ int getCommittedState() {
+ synchronized (mLock) {
+ return mCommittedState;
+ }
+ }
+
+ /**
+ * Returns the state the system is currently configuring, or {@link #INVALID_DEVICE_STATE} if
+ * the system is not in the process of configuring a state.
+ */
+ @VisibleForTesting
+ int getPendingState() {
+ synchronized (mLock) {
+ return mPendingState;
+ }
+ }
+
+ /**
+ * Returns the requested state. The service will configure the device to match the requested
+ * state when possible.
+ */
+ @VisibleForTesting
+ int getRequestedState() {
+ synchronized (mLock) {
+ return mRequestedState;
+ }
+ }
+
+ private void updateSupportedStates(int[] supportedDeviceStates) {
+ // Must ensure sorted as isSupportedStateLocked() impl uses binary search.
+ Arrays.sort(supportedDeviceStates, 0, supportedDeviceStates.length);
+ synchronized (mLock) {
+ mSupportedDeviceStates = IntArray.wrap(supportedDeviceStates);
+
+ if (mRequestedState != INVALID_DEVICE_STATE
+ && !isSupportedStateLocked(mRequestedState)) {
+ // The current requested state is no longer valid. We'll clear it here, though
+ // we won't actually update the current state with a call to
+ // updatePendingStateLocked() as doing so will not have any effect.
+ mRequestedState = INVALID_DEVICE_STATE;
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} if the provided state is supported. Requires that
+ * {@link #mSupportedDeviceStates} is sorted prior to calling.
+ */
+ private boolean isSupportedStateLocked(int state) {
+ return mSupportedDeviceStates.binarySearch(state) >= 0;
+ }
+
+ /**
+ * Requests that the system enter the provided {@code state}. The request may not be honored
+ * under certain conditions, for example if the provided state is not supported.
+ *
+ * @see #isSupportedStateLocked(int)
+ */
+ private void requestState(int state) {
+ synchronized (mLock) {
+ if (isSupportedStateLocked(state)) {
+ mRequestedState = state;
+ }
+ updatePendingStateLocked();
+ }
+
+ notifyPolicyIfNeeded();
+ }
+
+ /**
+ * Tries to update the current configuring state with the current requested state. Must call
+ * {@link #notifyPolicyIfNeeded()} to actually notify the policy that the state is being
+ * changed.
+ */
+ private void updatePendingStateLocked() {
+ if (mRequestedState == INVALID_DEVICE_STATE) {
+ // No currently requested state.
+ return;
+ }
+
+ if (mPendingState != INVALID_DEVICE_STATE) {
+ // Have pending state, can not configure a new state until the state is committed.
+ return;
+ }
+
+ if (mRequestedState == mCommittedState) {
+ // No need to notify the policy as the committed state matches the requested state.
+ return;
+ }
+
+ mPendingState = mRequestedState;
+ mIsPolicyWaitingForState = true;
+ }
+
+ /**
+ * Notifies the policy to configure the supplied state. Should not be called with {@link #mLock}
+ * held.
+ */
+ private void notifyPolicyIfNeeded() {
+ if (Thread.holdsLock(mLock)) {
+ Throwable error = new Throwable("Attempting to notify DeviceStatePolicy with service"
+ + " lock held");
+ error.fillInStackTrace();
+ Slog.w(TAG, error);
+ }
+ int state;
+ synchronized (mLock) {
+ if (!mIsPolicyWaitingForState) {
+ return;
+ }
+ mIsPolicyWaitingForState = false;
+ state = mPendingState;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Notifying policy to configure state: " + state);
+ }
+ mDeviceStatePolicy.configureDeviceForState(state, this::commitPendingState);
+ }
+
+ /**
+ * Commits the current pending state after a callback from the {@link DeviceStatePolicy}.
+ *
+ * <pre>
+ * ------------- ----------- -------------
+ * Provider -> | Requested | -> | Pending | -> Policy -> | Committed |
+ * ------------- ----------- -------------
+ * </pre>
+ * <p>
+ * When a new state is requested it immediately enters the requested state. Once the policy is
+ * available to accept a new state, which could also be immediately if there is no current
+ * pending state at the point of request, the policy is notified and a callback is provided to
+ * trigger the state to be committed.
+ * </p>
+ */
+ private void commitPendingState() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Committing state: " + mPendingState);
+ }
+ mCommittedState = mPendingState;
+ mPendingState = INVALID_DEVICE_STATE;
+ updatePendingStateLocked();
+ }
+
+ notifyPolicyIfNeeded();
+ }
+
+ private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
+ @Override
+ public void onSupportedDeviceStatesChanged(int[] newDeviceStates) {
+ for (int i = 0; i < newDeviceStates.length; i++) {
+ if (newDeviceStates[i] < 0) {
+ throw new IllegalArgumentException("Supported device states includes invalid"
+ + " value: " + newDeviceStates[i]);
+ }
+ }
+
+ updateSupportedStates(newDeviceStates);
+ }
+
+ @Override
+ public void onStateChanged(int state) {
+ if (state < 0) {
+ throw new IllegalArgumentException("Invalid state: " + state);
+ }
+
+ requestState(state);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
new file mode 100644
index 000000000000..274b8e562098
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
@@ -0,0 +1,40 @@
+/*
+ * 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.server.devicestate;
+
+import android.annotation.NonNull;
+
+/**
+ * Interface for the component responsible for supplying the current device state as well as
+ * configuring the state of the system in response to device state changes.
+ *
+ * @see DeviceStateManagerService
+ */
+public interface DeviceStatePolicy {
+ /** Returns the provider of device states. */
+ DeviceStateProvider getDeviceStateProvider();
+
+ /**
+ * Configures the system into the provided state. Guaranteed not to be called again until the
+ * {@code onComplete} callback has been executed.
+ *
+ * @param state the state the system should be configured for.
+ * @param onComplete a callback that must be triggered once the system has been properly
+ * configured to match the supplied state.
+ */
+ void configureDeviceForState(int state, @NonNull Runnable onComplete);
+}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
new file mode 100644
index 000000000000..0e8bf9bda4aa
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -0,0 +1,69 @@
+/*
+ * 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.server.devicestate;
+
+/**
+ * Responsible for providing the set of currently supported device states and well as the current
+ * device state.
+ *
+ * @see DeviceStatePolicy
+ */
+public interface DeviceStateProvider {
+ /**
+ * Registers a listener for changes in provider state.
+ * <p>
+ * It is <b>required</b> that {@link Listener#onSupportedDeviceStatesChanged(int[])} be called
+ * followed by {@link Listener#onStateChanged(int)} with the initial values on successful
+ * registration of the listener.
+ */
+ void setListener(Listener listener);
+
+ /** Callback for changes in {@link DeviceStateProvider} state. */
+ interface Listener {
+ /**
+ * Called to notify the listener of a change in supported device states. Required to be
+ * called once on successful registration of the listener and then once on every
+ * subsequent change in supported device states.
+ * <p>
+ * The set of device states can change based on the current hardware state of the device.
+ * For example, if a device state depends on a particular peripheral device (display, etc)
+ * it would only be reported as supported when the device is plugged. Otherwise, it should
+ * not be included in the set of supported states.
+ * <p>
+ * All values provided must be greater than or equal to zero and there must always be at
+ * least one supported device state.
+ *
+ * @param newDeviceStates array of supported device states.
+ *
+ * @throws IllegalArgumentException if the list of device states is empty or if one of the
+ * provided states is less than 0.
+ */
+ void onSupportedDeviceStatesChanged(int[] newDeviceStates);
+
+ /**
+ * Called to notify the listener of a change in current device state. Required to be called
+ * once on successful registration of the listener and then once on every subsequent change
+ * in device state. Value must have been included in the set of supported device states
+ * provided in the most recent call to {@link #onSupportedDeviceStatesChanged(int[])}.
+ *
+ * @param state the new device state.
+ *
+ * @throws IllegalArgumentException if the state is less than 0.
+ */
+ void onStateChanged(int state);
+ }
+}
diff --git a/services/core/java/com/android/server/devicestate/OWNERS b/services/core/java/com/android/server/devicestate/OWNERS
new file mode 100644
index 000000000000..ae79fc0e2c2d
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/OWNERS
@@ -0,0 +1,3 @@
+ogunwale@google.com
+akulian@google.com
+darryljohnson@google.com
diff --git a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
new file mode 100644
index 000000000000..54f618327da8
--- /dev/null
+++ b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
@@ -0,0 +1,44 @@
+/*
+ * 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.server.policy;
+
+import android.annotation.NonNull;
+
+import com.android.server.devicestate.DeviceStatePolicy;
+import com.android.server.devicestate.DeviceStateProvider;
+
+/**
+ * Default empty implementation of {@link DeviceStatePolicy}.
+ *
+ * @see DeviceStateProviderImpl
+ */
+public final class DeviceStatePolicyImpl implements DeviceStatePolicy {
+ private final DeviceStateProvider mProvider;
+
+ public DeviceStatePolicyImpl() {
+ mProvider = new DeviceStateProviderImpl();
+ }
+
+ public DeviceStateProvider getDeviceStateProvider() {
+ return mProvider;
+ }
+
+ @Override
+ public void configureDeviceForState(int state, @NonNull Runnable onComplete) {
+ onComplete.run();
+ }
+}
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
new file mode 100644
index 000000000000..85ab0bc12cae
--- /dev/null
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -0,0 +1,45 @@
+/*
+ * 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.server.policy;
+
+import android.annotation.Nullable;
+
+import com.android.server.devicestate.DeviceStateProvider;
+
+/**
+ * Default implementation of {@link DeviceStateProvider}. Currently only supports
+ * {@link #DEFAULT_DEVICE_STATE}.
+ *
+ * @see DeviceStatePolicyImpl
+ */
+final class DeviceStateProviderImpl implements DeviceStateProvider {
+ private static final int DEFAULT_DEVICE_STATE = 0;
+
+ @Nullable
+ private Listener mListener = null;
+
+ @Override
+ public void setListener(Listener listener) {
+ if (mListener != null) {
+ throw new RuntimeException("Provider already has a listener set.");
+ }
+
+ mListener = listener;
+ mListener.onSupportedDeviceStatesChanged(new int[]{ DEFAULT_DEVICE_STATE });
+ mListener.onStateChanged(DEFAULT_DEVICE_STATE);
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index eca9f1556d43..177cb1aaaddd 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -107,6 +107,7 @@ import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.coverage.CoverageService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
+import com.android.server.devicestate.DeviceStateManagerService;
import com.android.server.display.DisplayManagerService;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.dreams.DreamManagerService;
@@ -1254,6 +1255,10 @@ public final class SystemServer {
mSystemServiceManager.startService(AppIntegrityManagerService.class);
t.traceEnd();
+ t.traceBegin("DeviceStateManagerService");
+ mSystemServiceManager.startService(DeviceStateManagerService.class);
+ t.traceEnd();
+
} catch (Throwable e) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting core service");
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
new file mode 100644
index 000000000000..59d4e2ae6ae4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.server.devicestate;
+
+import static com.android.server.devicestate.DeviceStateManagerService.INVALID_DEVICE_STATE;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link DeviceStateManagerService}.
+ * <p/>
+ * Run with <code>atest DeviceStateManagerServiceTest</code>.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class DeviceStateManagerServiceTest {
+ private static final int DEFAULT_DEVICE_STATE = 0;
+ private static final int OTHER_DEVICE_STATE = 1;
+ private static final int UNSUPPORTED_DEVICE_STATE = 999;
+
+ private TestDeviceStatePolicy mPolicy;
+ private TestDeviceStateProvider mProvider;
+ private DeviceStateManagerService mService;
+
+ @Before
+ public void setup() {
+ mProvider = new TestDeviceStateProvider();
+ mPolicy = new TestDeviceStatePolicy(mProvider);
+ mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy);
+ mService.onStart();
+ }
+
+ @Test
+ public void requestStateChange() {
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+
+ mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+ assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+ }
+
+ @Test
+ public void requestStateChange_pendingState() {
+ mPolicy.blockConfigure();
+
+ mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+
+ mProvider.notifyRequestState(DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+
+ mPolicy.resumeConfigure();
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+ }
+
+ @Test
+ public void requestStateChange_unsupportedState() {
+ mProvider.notifyRequestState(UNSUPPORTED_DEVICE_STATE);
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+ }
+
+ @Test
+ public void requestStateChange_invalidState() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ mProvider.notifyRequestState(INVALID_DEVICE_STATE);
+ });
+ }
+
+ @Test
+ public void supportedStatesChanged() {
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+
+ mProvider.notifySupportedDeviceStates(new int []{ DEFAULT_DEVICE_STATE });
+
+ // The current committed and requests states do not change because the current state remains
+ // supported.
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+ }
+
+ @Test
+ public void supportedStatesChanged_invalidState() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ mProvider.notifySupportedDeviceStates(new int []{ INVALID_DEVICE_STATE });
+ });
+ }
+
+ @Test
+ public void supportedStatesChanged_unsupportedRequestedState() {
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+
+ mProvider.notifySupportedDeviceStates(new int []{ OTHER_DEVICE_STATE });
+
+ // The current requested state is cleared because it is no longer supported.
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), INVALID_DEVICE_STATE);
+
+ mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+
+ assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
+ }
+
+ private static final class TestDeviceStatePolicy implements DeviceStatePolicy {
+ private final DeviceStateProvider mProvider;
+ private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE;
+ private boolean mConfigureBlocked = false;
+ private Runnable mPendingConfigureCompleteRunnable;
+
+ TestDeviceStatePolicy(DeviceStateProvider provider) {
+ mProvider = provider;
+ }
+
+ @Override
+ public DeviceStateProvider getDeviceStateProvider() {
+ return mProvider;
+ }
+
+ public void blockConfigure() {
+ mConfigureBlocked = true;
+ }
+
+ public void resumeConfigure() {
+ mConfigureBlocked = false;
+ if (mPendingConfigureCompleteRunnable != null) {
+ Runnable onComplete = mPendingConfigureCompleteRunnable;
+ mPendingConfigureCompleteRunnable = null;
+ onComplete.run();
+ }
+ }
+
+ public int getMostRecentRequestedStateToConfigure() {
+ return mLastDeviceStateRequestedToConfigure;
+ }
+
+ @Override
+ public void configureDeviceForState(int state, Runnable onComplete) {
+ if (mPendingConfigureCompleteRunnable != null) {
+ throw new IllegalStateException("configureDeviceForState() called while configure"
+ + " is pending");
+ }
+
+ mLastDeviceStateRequestedToConfigure = state;
+ if (mConfigureBlocked) {
+ mPendingConfigureCompleteRunnable = onComplete;
+ return;
+ }
+ onComplete.run();
+ }
+ }
+
+ private static final class TestDeviceStateProvider implements DeviceStateProvider {
+ private int[] mSupportedDeviceStates = new int[]{ DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE };
+ private int mCurrentDeviceState = DEFAULT_DEVICE_STATE;
+ private Listener mListener;
+
+ @Override
+ public void setListener(Listener listener) {
+ if (mListener != null) {
+ throw new IllegalArgumentException("Provider already has listener set.");
+ }
+
+ mListener = listener;
+ mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates);
+ mListener.onStateChanged(mCurrentDeviceState);
+ }
+
+ public void notifySupportedDeviceStates(int[] supportedDeviceStates) {
+ mSupportedDeviceStates = supportedDeviceStates;
+ mListener.onSupportedDeviceStatesChanged(supportedDeviceStates);
+ }
+
+ public void notifyRequestState(int state) {
+ mCurrentDeviceState = state;
+ mListener.onStateChanged(state);
+ }
+ }
+}