Loading services/core/java/com/android/server/devicestate/DeviceStateManagerService.java 0 → 100644 +270 −0 Original line number Diff line number Diff line /* * 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); } } } services/core/java/com/android/server/devicestate/DeviceStatePolicy.java 0 → 100644 +40 −0 Original line number Diff line number Diff line /* * 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); } services/core/java/com/android/server/devicestate/DeviceStateProvider.java 0 → 100644 +69 −0 Original line number Diff line number Diff line /* * 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); } } services/core/java/com/android/server/devicestate/OWNERS 0 → 100644 +3 −0 Original line number Diff line number Diff line ogunwale@google.com akulian@google.com darryljohnson@google.com services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java 0 → 100644 +44 −0 Original line number Diff line number Diff line /* * 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(); } } Loading
services/core/java/com/android/server/devicestate/DeviceStateManagerService.java 0 → 100644 +270 −0 Original line number Diff line number Diff line /* * 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); } } }
services/core/java/com/android/server/devicestate/DeviceStatePolicy.java 0 → 100644 +40 −0 Original line number Diff line number Diff line /* * 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); }
services/core/java/com/android/server/devicestate/DeviceStateProvider.java 0 → 100644 +69 −0 Original line number Diff line number Diff line /* * 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); } }
services/core/java/com/android/server/devicestate/OWNERS 0 → 100644 +3 −0 Original line number Diff line number Diff line ogunwale@google.com akulian@google.com darryljohnson@google.com
services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java 0 → 100644 +44 −0 Original line number Diff line number Diff line /* * 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(); } }