Allow override requests to outlive the requesting process.

This change introduces a flag, FLAG_CANCEL_STICKY_REQUESTS, on the
device states that are provided by the DeviceStateProvider. If the flag
is set on a device state it indicates that the device state should
cancel any outstanding sticky requests when entered. If at least one
device state is configured to be a terminal device state (has the flag
set) the service will be configured to allow sticky requests, otherwise
requests will continue to be cancelled when the requesting process dies.

Fixes: 192671286

Test: atest DeviceStateManagerServiceTest
Test: atest DeviceStateProviderImplTest
Test: atest DeviceStateTest
Test: atest OverrideRequestControllerTest
Change-Id: I7a6bdc9334342fa818920b56a4f0c22629bb101f
This commit is contained in:
Darryl L Johnson
2021-07-23 17:06:17 -07:00
committed by Kenneth Ford
parent a5ab448ff0
commit 7dde391fc1
11 changed files with 210 additions and 65 deletions

View File

@@ -75,13 +75,13 @@ public final class DeviceStateManager {
/**
* Submits a {@link DeviceStateRequest request} to modify the device state.
* <p>
* By default, the request is kept active until a call to
* {@link #cancelRequest(DeviceStateRequest)} or until one of the following occurs:
* By default, the request is kept active until one of the following occurs:
* <ul>
* <li>The system deems the request can no longer be honored, for example if the requested
* state becomes unsupported.
* <li>A call to {@link #cancelRequest(DeviceStateRequest)}.
* <li>Another processes submits a request succeeding this request in which case the request
* will be suspended until the interrupting request is canceled.
* <li>The requested state has become unsupported.
* <li>The process submitting the request dies.
* </ul>
* However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}.
*

View File

@@ -19,11 +19,14 @@ package com.android.server.devicestate;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -39,6 +42,19 @@ import java.util.Objects;
* @see DeviceStateManagerService
*/
public final class DeviceState {
/**
* Flag that indicates sticky requests should be cancelled when this device state becomes the
* base device state.
*/
public static final int FLAG_CANCEL_STICKY_REQUESTS = 1 << 0;
/** @hide */
@IntDef(prefix = {"FLAG_"}, flag = true, value = {
FLAG_CANCEL_STICKY_REQUESTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeviceStateFlags {}
/** Unique identifier for the device state. */
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE)
private final int mIdentifier;
@@ -47,14 +63,19 @@ public final class DeviceState {
@NonNull
private final String mName;
@DeviceStateFlags
private final int mFlags;
public DeviceState(
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
@NonNull String name) {
@NonNull String name,
@DeviceStateFlags int flags) {
Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE, MAXIMUM_DEVICE_STATE,
"identifier");
mIdentifier = identifier;
mName = name;
mFlags = flags;
}
/** Returns the unique identifier for the device state. */
@@ -69,6 +90,11 @@ public final class DeviceState {
return mName;
}
@DeviceStateFlags
public int getFlags() {
return mFlags;
}
@Override
public String toString() {
return "DeviceState{" + "identifier=" + mIdentifier + ", name='" + mName + '\'' + '}';
@@ -80,11 +106,12 @@ public final class DeviceState {
if (o == null || getClass() != o.getClass()) return false;
DeviceState that = (DeviceState) o;
return mIdentifier == that.mIdentifier
&& Objects.equals(mName, that.mName);
&& Objects.equals(mName, that.mName)
&& mFlags == that.mFlags;
}
@Override
public int hashCode() {
return Objects.hash(mIdentifier, mName);
return Objects.hash(mIdentifier, mName, mFlags);
}
}

View File

@@ -275,12 +275,21 @@ public final class DeviceStateManagerService extends SystemService {
synchronized (mLock) {
final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
// Whether or not at least one device state has the flag FLAG_CANCEL_STICKY_REQUESTS
// set. If set to true, the OverrideRequestController will be configured to allow sticky
// requests.
boolean hasTerminalDeviceState = false;
mDeviceStates.clear();
for (int i = 0; i < supportedDeviceStates.length; i++) {
DeviceState state = supportedDeviceStates[i];
if ((state.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) {
hasTerminalDeviceState = true;
}
mDeviceStates.put(state.getIdentifier(), state);
}
mOverrideRequestController.setStickyRequestsAllowed(hasTerminalDeviceState);
final int[] newStateIdentifiers = getSupportedStateIdentifiersLocked();
if (Arrays.equals(oldStateIdentifiers, newStateIdentifiers)) {
return;
@@ -338,6 +347,9 @@ public final class DeviceStateManagerService extends SystemService {
}
mBaseState = Optional.of(baseState);
if ((baseState.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) {
mOverrideRequestController.cancelStickyRequests();
}
mOverrideRequestController.handleBaseStateChanged();
updatePendingStateLocked();

View File

@@ -84,10 +84,28 @@ final class OverrideRequestController {
// List of override requests with the most recent override request at the end.
private final ArrayList<OverrideRequest> mRequests = new ArrayList<>();
private boolean mStickyRequestsAllowed;
// List of override requests that have outlived their process and will only be cancelled through
// a call to cancelStickyRequests().
private final ArrayList<OverrideRequest> mStickyRequests = new ArrayList<>();
OverrideRequestController(@NonNull StatusChangeListener listener) {
mListener = listener;
}
/**
* Sets sticky requests as either allowed or disallowed. When sticky requests are allowed a call
* to {@link #handleProcessDied(int)} will not result in the request being cancelled
* immediately. Instead, the request will be marked sticky and must be cancelled with a call
* to {@link #cancelStickyRequests()}.
*/
void setStickyRequestsAllowed(boolean stickyRequestsAllowed) {
mStickyRequestsAllowed = stickyRequestsAllowed;
if (!mStickyRequestsAllowed) {
cancelStickyRequests();
}
}
/**
* Adds a request to the top of the stack and notifies the listener of all changes to request
* status as a result of this operation.
@@ -122,6 +140,18 @@ final class OverrideRequestController {
mListener.onStatusChanged(request, STATUS_CANCELED);
}
/**
* Cancels all requests that are currently marked sticky and notifies the listener of all
* changes to request status as a result of this operation.
*
* @see #setStickyRequestsAllowed(boolean)
*/
void cancelStickyRequests() {
mTmpRequestsToCancel.clear();
mTmpRequestsToCancel.addAll(mStickyRequests);
cancelRequestsLocked(mTmpRequestsToCancel);
}
/**
* Returns {@code true} if this controller is current managing a request with the specified
* {@code token}, {@code false} otherwise.
@@ -140,6 +170,7 @@ final class OverrideRequestController {
return;
}
mTmpRequestsToCancel.clear();
OverrideRequest prevActiveRequest = getLast(mRequests);
for (OverrideRequest request : mRequests) {
if (request.getPid() == pid) {
@@ -147,18 +178,14 @@ final class OverrideRequestController {
}
}
mRequests.removeAll(mTmpRequestsToCancel);
if (!mRequests.isEmpty()) {
OverrideRequest newActiveRequest = getLast(mRequests);
if (newActiveRequest != prevActiveRequest) {
mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
}
if (mStickyRequestsAllowed) {
// Do not cancel the requests now because sticky requests are allowed. These
// requests will be cancelled on a call to cancelStickyRequests().
mStickyRequests.addAll(mTmpRequestsToCancel);
return;
}
for (int i = 0; i < mTmpRequestsToCancel.size(); i++) {
mListener.onStatusChanged(mTmpRequestsToCancel.get(i), STATUS_CANCELED);
}
mTmpRequestsToCancel.clear();
cancelRequestsLocked(mTmpRequestsToCancel);
}
/**
@@ -173,6 +200,7 @@ final class OverrideRequestController {
return false;
}
mTmpRequestsToCancel.clear();
OverrideRequest prevActiveRequest = getLast(mRequests);
for (int i = 0; i < mRequests.size(); i++) {
OverrideRequest request = mRequests.get(i);
@@ -181,21 +209,8 @@ final class OverrideRequestController {
}
}
mRequests.removeAll(mTmpRequestsToCancel);
OverrideRequest newActiveRequest = null;
if (!mRequests.isEmpty()) {
newActiveRequest = getLast(mRequests);
if (newActiveRequest != prevActiveRequest) {
mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
}
}
for (int i = 0; i < mTmpRequestsToCancel.size(); i++) {
mListener.onStatusChanged(mTmpRequestsToCancel.get(i), STATUS_CANCELED);
}
mTmpRequestsToCancel.clear();
return newActiveRequest != prevActiveRequest;
final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
return newActiveRequest;
}
/**
@@ -210,7 +225,7 @@ final class OverrideRequestController {
return false;
}
OverrideRequest prevActiveRequest = getLast(mRequests);
mTmpRequestsToCancel.clear();
for (int i = 0; i < mRequests.size(); i++) {
OverrideRequest request = mRequests.get(i);
if (!contains(newSupportedStates, request.getRequestedState())) {
@@ -218,21 +233,8 @@ final class OverrideRequestController {
}
}
mRequests.removeAll(mTmpRequestsToCancel);
OverrideRequest newActiveRequest = null;
if (!mRequests.isEmpty()) {
newActiveRequest = getLast(mRequests);
if (newActiveRequest != prevActiveRequest) {
mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
}
}
for (int i = 0; i < mTmpRequestsToCancel.size(); i++) {
mListener.onStatusChanged(mTmpRequestsToCancel.get(i), STATUS_CANCELED);
}
mTmpRequestsToCancel.clear();
return newActiveRequest != prevActiveRequest;
final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
return newActiveRequest;
}
void dumpInternal(PrintWriter pw) {
@@ -249,6 +251,36 @@ final class OverrideRequestController {
}
}
/**
* Handles cancelling a set of requests. If the set of requests to cancel will lead to a new
* request becoming active this request will also be notified of its change in state.
*
* @return {@code true} if calling this method has lead to a new active request, {@code false}
* otherwise.
*/
private boolean cancelRequestsLocked(List<OverrideRequest> requestsToCancel) {
if (requestsToCancel.isEmpty()) {
return false;
}
OverrideRequest prevActiveRequest = getLast(mRequests);
boolean causedNewRequestToBecomeActive = false;
mRequests.removeAll(requestsToCancel);
mStickyRequests.removeAll(requestsToCancel);
if (!mRequests.isEmpty()) {
OverrideRequest newActiveRequest = getLast(mRequests);
if (newActiveRequest != prevActiveRequest) {
mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
causedNewRequestToBecomeActive = true;
}
}
for (int i = 0; i < requestsToCancel.size(); i++) {
mListener.onStatusChanged(requestsToCancel.get(i), STATUS_CANCELED);
}
return causedNewRequestToBecomeActive;
}
private int getRequestIndex(@NonNull IBinder token) {
final int numberOfRequests = mRequests.size();
if (numberOfRequests == 0) {

View File

@@ -41,6 +41,7 @@ import com.android.server.devicestate.DeviceState;
import com.android.server.devicestate.DeviceStateProvider;
import com.android.server.policy.devicestate.config.Conditions;
import com.android.server.policy.devicestate.config.DeviceStateConfig;
import com.android.server.policy.devicestate.config.Flags;
import com.android.server.policy.devicestate.config.LidSwitchCondition;
import com.android.server.policy.devicestate.config.NumericRange;
import com.android.server.policy.devicestate.config.SensorCondition;
@@ -87,7 +88,7 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
@VisibleForTesting
static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE,
"DEFAULT");
"DEFAULT", 0 /* flags */);
private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
@@ -131,7 +132,26 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
config.getDeviceState()) {
final int state = stateConfig.getIdentifier().intValue();
final String name = stateConfig.getName() == null ? "" : stateConfig.getName();
deviceStateList.add(new DeviceState(state, name));
int flags = 0;
final Flags configFlags = stateConfig.getFlags();
if (configFlags != null) {
List<String> configFlagStrings = configFlags.getFlag();
for (int i = 0; i < configFlagStrings.size(); i++) {
final String configFlagString = configFlagStrings.get(i);
switch (configFlagString) {
case "FLAG_CANCEL_STICKY_REQUESTS":
flags |= DeviceState.FLAG_CANCEL_STICKY_REQUESTS;
break;
default:
Slog.w(TAG, "Parsed unknown flag with name: "
+ configFlagString);
break;
}
}
}
deviceStateList.add(new DeviceState(state, name, flags));
final Conditions condition = stateConfig.getConditions();
conditionsList.add(condition);

View File

@@ -40,10 +40,19 @@
<xs:element name="name" type="xs:string" minOccurs="0">
<xs:annotation name="nullable" />
</xs:element>
<xs:element name="flags" type="flags" />
<xs:element name="conditions" type="conditions" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="flags">
<xs:sequence>
<xs:element name="flag" type="xs:string" minOccurs="0" maxOccurs="unbounded">
<xs:annotation name="nullable" />
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="conditions">
<xs:sequence>
<xs:element name="lid-switch" type="lidSwitchCondition" minOccurs="0">

View File

@@ -11,9 +11,11 @@ package com.android.server.policy.devicestate.config {
public class DeviceState {
ctor public DeviceState();
method public com.android.server.policy.devicestate.config.Conditions getConditions();
method public com.android.server.policy.devicestate.config.Flags getFlags();
method public java.math.BigInteger getIdentifier();
method @Nullable public String getName();
method public void setConditions(com.android.server.policy.devicestate.config.Conditions);
method public void setFlags(com.android.server.policy.devicestate.config.Flags);
method public void setIdentifier(java.math.BigInteger);
method public void setName(@Nullable String);
}
@@ -23,6 +25,11 @@ package com.android.server.policy.devicestate.config {
method public java.util.List<com.android.server.policy.devicestate.config.DeviceState> getDeviceState();
}
public class Flags {
ctor public Flags();
method @Nullable public java.util.List<java.lang.String> getFlag();
}
public class LidSwitchCondition {
ctor public LidSwitchCondition();
method public boolean getOpen();

View File

@@ -55,10 +55,13 @@ import javax.annotation.Nullable;
@Presubmit
@RunWith(AndroidJUnit4.class)
public final class DeviceStateManagerServiceTest {
private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER");
private static final DeviceState DEFAULT_DEVICE_STATE =
new DeviceState(0, "DEFAULT", 0 /* flags */);
private static final DeviceState OTHER_DEVICE_STATE =
new DeviceState(1, "OTHER", 0 /* flags */);
// A device state that is not reported as being supported for the default test provider.
private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(255, "UNSUPPORTED");
private static final DeviceState UNSUPPORTED_DEVICE_STATE =
new DeviceState(255, "UNSUPPORTED", 0 /* flags */);
private TestDeviceStatePolicy mPolicy;
private TestDeviceStateProvider mProvider;

View File

@@ -41,24 +41,26 @@ public final class DeviceStateTest {
@Test
public void testConstruct() {
final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */,
"CLOSED" /* name */);
"CLOSED" /* name */, DeviceState.FLAG_CANCEL_STICKY_REQUESTS /* flags */);
assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE);
assertEquals(state.getName(), "CLOSED");
assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_STICKY_REQUESTS);
}
@Test
public void testConstruct_nullName() {
final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE /* identifier */,
null /* name */);
null /* name */, 0/* flags */);
assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE);
assertNull(state.getName());
assertEquals(state.getFlags(), 0);
}
@Test
public void testConstruct_tooLargeIdentifier() {
assertThrows(IllegalArgumentException.class, () -> {
final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE + 1 /* identifier */,
null /* name */);
null /* name */, 0 /* flags */);
});
}
@@ -66,7 +68,7 @@ public final class DeviceStateTest {
public void testConstruct_tooSmallIdentifier() {
assertThrows(IllegalArgumentException.class, () -> {
final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE - 1 /* identifier */,
null /* name */);
null /* name */, 0 /* flags */);
});
}
}

View File

@@ -164,6 +164,32 @@ public final class OverrideRequestControllerTest {
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@Test
public void handleProcessDied_stickyRequests() {
mController.setStickyRequestsAllowed(true);
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */,
0 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
mController.addRequest(secondRequest);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
mController.handleProcessDied(1);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
mController.cancelStickyRequests();
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
}
@Test
public void handleNewSupportedStates() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,

View File

@@ -150,8 +150,9 @@ public final class DeviceStateProviderImplTest {
provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, ""),
new DeviceState(2, "") };
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "", 0 /* flags */) };
assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
verify(listener).onStateChanged(mIntegerCaptor.capture());
@@ -187,8 +188,9 @@ public final class DeviceStateProviderImplTest {
provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, ""),
new DeviceState(2, "CLOSED") };
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "CLOSED", 0 /* flags */) };
assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
// onStateChanged() should not be called because the provider has not yet been notified of
@@ -264,8 +266,11 @@ public final class DeviceStateProviderImplTest {
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
assertArrayEquals(
new DeviceState[]{ new DeviceState(1, "CLOSED"), new DeviceState(2, "HALF_OPENED"),
new DeviceState(3, "OPENED") }, mDeviceStateArrayCaptor.getValue());
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
new DeviceState(2, "HALF_OPENED", 0 /* flags */),
new DeviceState(3, "OPENED", 0 /* flags */) },
mDeviceStateArrayCaptor.getValue());
// onStateChanged() should not be called because the provider has not yet been notified of
// the initial sensor state.
verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
@@ -350,8 +355,10 @@ public final class DeviceStateProviderImplTest {
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
assertArrayEquals(
new DeviceState[]{ new DeviceState(1, "CLOSED"), new DeviceState(2, "HALF_OPENED"),
}, mDeviceStateArrayCaptor.getValue());
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
new DeviceState(2, "HALF_OPENED", 0 /* flags */)
}, mDeviceStateArrayCaptor.getValue());
// onStateChanged() should be called because the provider could not find the sensor.
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue());