diff options
| author | 2020-09-10 16:52:48 +0000 | |
|---|---|---|
| committer | 2020-10-07 13:49:09 -0700 | |
| commit | b16775425a1c713b5fbcbf5872362582e6adcf0d (patch) | |
| tree | 3f2835121300e0cd90f2d6da6026e31fc93fdc1d | |
| parent | e9988880468f8df266e7c96f1071ee443f675f93 (diff) | |
Add DeviceStateManagerService and stub implementation of policy.
Test: atest DeviceStateManagerServiceTest
Bug: 159401801
Change-Id: Ib7f424e905c80812b36e0e0bb30771224ce56e34
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); + } + } +} |