Merge "Add notifications for the concurrent display mode"

This commit is contained in:
Jiaming Liu
2023-01-31 22:56:48 +00:00
committed by Android (Google) Code Review
13 changed files with 711 additions and 66 deletions

View File

@@ -207,4 +207,25 @@
be set by OEMs for devices which use eUICCs. -->
<integer-array name="non_removable_euicc_slots"></integer-array>
<!-- Device state identifiers and strings for system notifications. The string arrays must have
the same length and order as the identifier array. -->
<integer-array name="device_state_notification_state_identifiers">
<!-- TODO(b/267231269) change to concurrent display identifier when ready -->
<item>-1</item>
</integer-array>
<string-array name="device_state_notification_names">
<item>@string/concurrent_display_notification_name</item>
</string-array>
<string-array name="device_state_notification_active_titles">
<item>@string/concurrent_display_notification_active_title</item>
</string-array>
<string-array name="device_state_notification_active_contents">
<item>@string/concurrent_display_notification_active_content</item>
</string-array>
<string-array name="device_state_notification_thermal_titles">
<item>@string/concurrent_display_notification_thermal_title</item>
</string-array>
<string-array name="device_state_notification_thermal_contents">
<item>@string/concurrent_display_notification_thermal_content</item>
</string-array>
</resources>

View File

@@ -6241,4 +6241,17 @@ ul.</string>
<string name="mic_access_on_toast">Microphone is available</string>
<!-- Toast message that is shown when the user mutes the microphone from the keyboard. [CHAR LIMIT=TOAST] -->
<string name="mic_access_off_toast">Microphone is blocked</string>
<!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] -->
<string name="concurrent_display_notification_name">Dual screen</string>
<!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] -->
<string name="concurrent_display_notification_active_title">Dual screen is on</string>
<!-- Content of concurrent display active notification. [CHAR LIMIT=NONE] -->
<string name="concurrent_display_notification_active_content"><xliff:g id="app_name" example="Camera app">%1$s</xliff:g> is using both displays to show content</string>
<!-- Title of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
<string name="concurrent_display_notification_thermal_title">Device is too warm</string>
<!-- Content of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
<string name="concurrent_display_notification_thermal_content">Dual Screen is unavailable because your phone is getting too warm</string>
<!-- Text of device state notification turn off button. [CHAR LIMIT=NONE] -->
<string name="device_state_notification_turn_off_button">Turn off</string>
</resources>

View File

@@ -4879,6 +4879,18 @@
<java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
<java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
<java-symbol type="integer" name="config_deviceStateRearDisplay"/>
<java-symbol type="array" name="device_state_notification_state_identifiers"/>
<java-symbol type="array" name="device_state_notification_names"/>
<java-symbol type="array" name="device_state_notification_active_titles"/>
<java-symbol type="array" name="device_state_notification_active_contents"/>
<java-symbol type="array" name="device_state_notification_thermal_titles"/>
<java-symbol type="array" name="device_state_notification_thermal_contents"/>
<java-symbol type="string" name="concurrent_display_notification_name"/>
<java-symbol type="string" name="concurrent_display_notification_active_title"/>
<java-symbol type="string" name="concurrent_display_notification_active_content"/>
<java-symbol type="string" name="concurrent_display_notification_thermal_title"/>
<java-symbol type="string" name="concurrent_display_notification_thermal_content"/>
<java-symbol type="string" name="device_state_notification_turn_off_button"/>
<java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
<!-- For app language picker -->

View File

@@ -25,6 +25,7 @@ import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STA
import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
import static com.android.server.devicestate.OverrideRequestController.FLAG_THERMAL_CRITICAL;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
import static com.android.server.devicestate.OverrideRequestController.STATUS_UNKNOWN;
@@ -183,6 +184,9 @@ public final class DeviceStateManagerService extends SystemService {
ActivityTaskManagerInternal.ScreenObserver mOverrideRequestScreenObserver =
new OverrideRequestScreenObserver();
@NonNull
private final DeviceStateNotificationController mDeviceStateNotificationController;
public DeviceStateManagerService(@NonNull Context context) {
this(context, DeviceStatePolicy.Provider
.fromResources(context.getResources())
@@ -211,6 +215,13 @@ public final class DeviceStateManagerService extends SystemService {
mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener);
mBinderService = new BinderService();
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mDeviceStateNotificationController = new DeviceStateNotificationController(
context, mHandler,
() -> {
synchronized (mLock) {
mActiveOverride.ifPresent(mOverrideRequestController::cancelRequest);
}
});
}
@Override
@@ -346,7 +357,8 @@ public final class DeviceStateManagerService extends SystemService {
return mBinderService;
}
private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
private void updateSupportedStates(DeviceState[] supportedDeviceStates,
@DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
synchronized (mLock) {
final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
@@ -370,7 +382,7 @@ public final class DeviceStateManagerService extends SystemService {
return;
}
mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers);
mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers, reason);
updatePendingStateLocked();
setRearDisplayStateLocked();
@@ -596,7 +608,7 @@ public final class DeviceStateManagerService extends SystemService {
@GuardedBy("mLock")
private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
@OverrideRequestController.RequestStatus int status) {
@OverrideRequestController.RequestStatus int status, int flags) {
if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) {
switch (status) {
case STATUS_ACTIVE:
@@ -616,10 +628,19 @@ public final class DeviceStateManagerService extends SystemService {
switch (status) {
case STATUS_ACTIVE:
mActiveOverride = Optional.of(request);
mDeviceStateNotificationController.showStateActiveNotificationIfNeeded(
request.getRequestedState(), request.getUid());
break;
case STATUS_CANCELED:
if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
mActiveOverride = Optional.empty();
mDeviceStateNotificationController.cancelNotification(
request.getRequestedState());
if ((flags & FLAG_THERMAL_CRITICAL) == FLAG_THERMAL_CRITICAL) {
mDeviceStateNotificationController
.showThermalCriticalNotificationIfNeeded(
request.getRequestedState());
}
}
break;
case STATUS_UNKNOWN: // same as default
@@ -700,7 +721,7 @@ public final class DeviceStateManagerService extends SystemService {
}
}
private void requestStateInternal(int state, int flags, int callingPid,
private void requestStateInternal(int state, int flags, int callingPid, int callingUid,
@NonNull IBinder token, boolean hasControlDeviceStatePermission) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
@@ -721,8 +742,8 @@ public final class DeviceStateManagerService extends SystemService {
+ " is not supported.");
}
OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
state, flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
// If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
if (!hasControlDeviceStatePermission && mRearDisplayState != null
@@ -762,7 +783,7 @@ public final class DeviceStateManagerService extends SystemService {
}
private void requestBaseStateOverrideInternal(int state, int flags, int callingPid,
@NonNull IBinder token) {
int callingUid, @NonNull IBinder token) {
synchronized (mLock) {
final Optional<DeviceState> deviceState = getStateLocked(state);
if (!deviceState.isPresent()) {
@@ -782,8 +803,8 @@ public final class DeviceStateManagerService extends SystemService {
+ " token: " + token);
}
OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
OVERRIDE_REQUEST_TYPE_BASE_STATE);
OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
state, flags, OVERRIDE_REQUEST_TYPE_BASE_STATE);
mOverrideRequestController.addBaseStateRequest(request);
}
}
@@ -953,11 +974,12 @@ public final class DeviceStateManagerService extends SystemService {
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState;
@Override
public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates,
@DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
if (newDeviceStates.length == 0) {
throw new IllegalArgumentException("Supported device states must not be empty");
}
updateSupportedStates(newDeviceStates);
updateSupportedStates(newDeviceStates, reason);
}
@Override
@@ -1085,6 +1107,7 @@ public final class DeviceStateManagerService extends SystemService {
@Override // Binder call
public void requestState(IBinder token, int state, int flags) {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
// Allow top processes to request a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
@@ -1099,7 +1122,8 @@ public final class DeviceStateManagerService extends SystemService {
final long callingIdentity = Binder.clearCallingIdentity();
try {
requestStateInternal(state, flags, callingPid, token, hasControlStatePermission);
requestStateInternal(state, flags, callingPid, callingUid, token,
hasControlStatePermission);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -1124,6 +1148,7 @@ public final class DeviceStateManagerService extends SystemService {
@Override // Binder call
public void requestBaseStateOverride(IBinder token, int state, int flags) {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to control base state of device.");
@@ -1133,7 +1158,7 @@ public final class DeviceStateManagerService extends SystemService {
final long callingIdentity = Binder.clearCallingIdentity();
try {
requestBaseStateOverrideInternal(state, flags, callingPid, token);
requestBaseStateOverrideInternal(state, flags, callingPid, callingUid, token);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}

View File

@@ -0,0 +1,293 @@
/*
* Copyright (C) 2023 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.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Handler;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
/**
* Manages the user-visible device state notifications.
*/
class DeviceStateNotificationController extends BroadcastReceiver {
private static final String TAG = "DeviceStateNotificationController";
@VisibleForTesting static final String INTENT_ACTION_CANCEL_STATE =
"com.android.server.devicestate.INTENT_ACTION_CANCEL_STATE";
@VisibleForTesting static final int NOTIFICATION_ID = 1;
@VisibleForTesting static final String CHANNEL_ID = "DeviceStateManager";
@VisibleForTesting static final String NOTIFICATION_TAG = "DeviceStateManager";
private final Context mContext;
private final Handler mHandler;
private final NotificationManager mNotificationManager;
private final PackageManager mPackageManager;
// Stores the notification title and content indexed with the device state identifier.
private final SparseArray<NotificationInfo> mNotificationInfos;
// The callback when a device state is requested to be canceled.
private final Runnable mCancelStateRunnable;
DeviceStateNotificationController(@NonNull Context context, @NonNull Handler handler,
@NonNull Runnable cancelStateRunnable) {
this(context, handler, cancelStateRunnable, getNotificationInfos(context),
context.getPackageManager(), context.getSystemService(NotificationManager.class));
}
@VisibleForTesting
DeviceStateNotificationController(
@NonNull Context context, @NonNull Handler handler,
@NonNull Runnable cancelStateRunnable,
@NonNull SparseArray<NotificationInfo> notificationInfos,
@NonNull PackageManager packageManager,
@NonNull NotificationManager notificationManager) {
mContext = context;
mHandler = handler;
mCancelStateRunnable = cancelStateRunnable;
mNotificationInfos = notificationInfos;
mPackageManager = packageManager;
mNotificationManager = notificationManager;
mContext.registerReceiver(
this,
new IntentFilter(INTENT_ACTION_CANCEL_STATE),
android.Manifest.permission.CONTROL_DEVICE_STATE,
mHandler,
Context.RECEIVER_NOT_EXPORTED);
}
/**
* Displays the ongoing notification indicating that the device state is active. Does nothing if
* the state does not have an active notification.
*
* @param state the active device state identifier.
* @param requestingAppUid the uid of the requesting app used to retrieve the app name.
*/
void showStateActiveNotificationIfNeeded(int state, int requestingAppUid) {
NotificationInfo info = mNotificationInfos.get(state);
if (info == null || !info.hasActiveNotification()) {
return;
}
String requesterApplicationLabel = getApplicationLabel(requestingAppUid);
if (requesterApplicationLabel != null) {
showNotification(
info.name, info.activeNotificationTitle,
String.format(info.activeNotificationContent, requesterApplicationLabel),
true /* ongoing */
);
} else {
Slog.e(TAG, "Cannot determine the requesting app name when showing state active "
+ "notification. uid=" + requestingAppUid + ", state=" + state);
}
}
/**
* Displays the notification indicating that the device state is canceled due to thermal
* critical condition. Does nothing if the state does not have a thermal critical notification.
*
* @param state the identifier of the device state being canceled.
*/
void showThermalCriticalNotificationIfNeeded(int state) {
NotificationInfo info = mNotificationInfos.get(state);
if (info == null || !info.hasThermalCriticalNotification()) {
return;
}
showNotification(
info.name, info.thermalCriticalNotificationTitle,
info.thermalCriticalNotificationContent, false /* ongoing */
);
}
/**
* Cancels the notification of the corresponding device state.
*
* @param state the device state identifier.
*/
void cancelNotification(int state) {
if (!mNotificationInfos.contains(state)) {
return;
}
mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
}
@Override
public void onReceive(@NonNull Context context, @Nullable Intent intent) {
if (intent != null) {
if (INTENT_ACTION_CANCEL_STATE.equals(intent.getAction())) {
mCancelStateRunnable.run();
}
}
}
/**
* Displays a notification with the specified name, title, and content.
*
* @param name the name of the notification.
* @param title the title of the notification.
* @param content the content of the notification.
* @param ongoing if true, display an ongoing (sticky) notification with a turn off button.
*/
private void showNotification(
@NonNull String name, @NonNull String title, @NonNull String content, boolean ongoing) {
final NotificationChannel channel = new NotificationChannel(
CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH);
final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_lock) // TODO(b/266833171) update icons when available.
.setContentTitle(title)
.setContentText(content)
.setSubText(name)
.setLocalOnly(true)
.setOngoing(ongoing)
.setCategory(Notification.CATEGORY_SYSTEM);
if (ongoing) {
final Intent intent = new Intent(INTENT_ACTION_CANCEL_STATE)
.setPackage(mContext.getPackageName());
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
final Notification.Action action = new Notification.Action.Builder(
null /* icon */,
mContext.getString(R.string.device_state_notification_turn_off_button),
pendingIntent)
.build();
builder.addAction(action);
}
mNotificationManager.createNotificationChannel(channel);
mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
}
/**
* Loads the resources for the notifications. The device state identifiers and strings are
* stored in arrays. All the string arrays must have the same length and same order as the
* identifier array.
*/
private static SparseArray<NotificationInfo> getNotificationInfos(Context context) {
final SparseArray<NotificationInfo> notificationInfos = new SparseArray<>();
final int[] stateIdentifiers =
context.getResources().getIntArray(
R.array.device_state_notification_state_identifiers);
final String[] names =
context.getResources().getStringArray(R.array.device_state_notification_names);
final String[] activeNotificationTitles =
context.getResources().getStringArray(
R.array.device_state_notification_active_titles);
final String[] activeNotificationContents =
context.getResources().getStringArray(
R.array.device_state_notification_active_contents);
final String[] thermalCriticalNotificationTitles =
context.getResources().getStringArray(
R.array.device_state_notification_thermal_titles);
final String[] thermalCriticalNotificationContents =
context.getResources().getStringArray(
R.array.device_state_notification_thermal_contents);
if (stateIdentifiers.length != names.length
|| stateIdentifiers.length != activeNotificationTitles.length
|| stateIdentifiers.length != activeNotificationContents.length
|| stateIdentifiers.length != thermalCriticalNotificationTitles.length
|| stateIdentifiers.length != thermalCriticalNotificationContents.length
) {
throw new IllegalStateException(
"The length of state identifiers and notification texts must match!");
}
for (int i = 0; i < stateIdentifiers.length; i++) {
int identifier = stateIdentifiers[i];
if (identifier == DeviceStateManager.INVALID_DEVICE_STATE) {
continue;
}
notificationInfos.put(
identifier,
new NotificationInfo(
names[i], activeNotificationTitles[i], activeNotificationContents[i],
thermalCriticalNotificationTitles[i],
thermalCriticalNotificationContents[i])
);
}
return notificationInfos;
}
/**
* A helper function to get app name (label) using the app uid.
*
* @param uid the uid of the app.
* @return app name (label) if found, or null otherwise.
*/
@Nullable
private String getApplicationLabel(int uid) {
String packageName = mPackageManager.getNameForUid(uid);
try {
ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
packageName, PackageManager.ApplicationInfoFlags.of(0));
return appInfo.loadLabel(mPackageManager).toString();
} catch (PackageManager.NameNotFoundException e) {
return null;
}
}
/**
* A data class storing string resources of the notification of a device state.
*/
@VisibleForTesting
static class NotificationInfo {
public final String name;
public final String activeNotificationTitle;
public final String activeNotificationContent;
public final String thermalCriticalNotificationTitle;
public final String thermalCriticalNotificationContent;
NotificationInfo(String name, String activeNotificationTitle,
String activeNotificationContent, String thermalCriticalNotificationTitle,
String thermalCriticalNotificationContent) {
this.name = name;
this.activeNotificationTitle = activeNotificationTitle;
this.activeNotificationContent = activeNotificationContent;
this.thermalCriticalNotificationTitle = thermalCriticalNotificationTitle;
this.thermalCriticalNotificationContent = thermalCriticalNotificationContent;
}
boolean hasActiveNotification() {
return activeNotificationTitle != null && activeNotificationTitle.length() > 0;
}
boolean hasThermalCriticalNotification() {
return thermalCriticalNotificationTitle != null
&& thermalCriticalNotificationTitle.length() > 0;
}
}
}

View File

@@ -19,8 +19,12 @@ 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 java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Responsible for providing the set of supported {@link DeviceState device states} as well as the
* current device state.
@@ -28,12 +32,42 @@ import android.annotation.IntRange;
* @see DeviceStatePolicy
*/
public interface DeviceStateProvider {
int SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT = 0;
/**
* Indicating that the supported device states changed callback is trigger for initial listener
* registration.
*/
int SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED = 1;
/**
* Indicating that the supported device states have changed because the thermal condition
* returned to normal status from critical status.
*/
int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL = 2;
/**
* Indicating that the supported device states have changed because of thermal critical
* condition.
*/
int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL = 3;
@IntDef(prefix = { "SUPPORTED_DEVICE_STATES_CHANGED_" }, value = {
SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT,
SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED,
SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL,
SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
})
@Retention(RetentionPolicy.SOURCE)
@interface SupportedStatesUpdatedReason {}
/**
* Registers a listener for changes in provider state.
* <p>
* It is <b>required</b> that {@link Listener#onSupportedDeviceStatesChanged(DeviceState[])} be
* called followed by {@link Listener#onStateChanged(int)} with the initial values on successful
* registration of the listener.
* It is <b>required</b> that
* {@link Listener#onSupportedDeviceStatesChanged(DeviceState[], int)} be called followed by
* {@link Listener#onStateChanged(int)} with the initial values on successful registration of
* the listener.
*/
void setListener(Listener listener);
@@ -53,18 +87,20 @@ public interface DeviceStateProvider {
* to zero and there must always be at least one supported device state.
*
* @param newDeviceStates array of supported device states.
* @param reason the reason for the supported device states change.
*
* @throws IllegalArgumentException if the list of device states is empty or if one of the
* provided states contains an invalid identifier.
*/
void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates);
void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates,
@SupportedStatesUpdatedReason int reason);
/**
* 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(DeviceState[])}.
* {@link #onSupportedDeviceStatesChanged(DeviceState[], int)}.
*
* @param identifier the identifier of the new device state.
*

View File

@@ -31,6 +31,7 @@ import java.lang.annotation.RetentionPolicy;
final class OverrideRequest {
private final IBinder mToken;
private final int mPid;
private final int mUid;
private final int mRequestedState;
@DeviceStateRequest.RequestFlags
private final int mFlags;
@@ -68,10 +69,11 @@ final class OverrideRequest {
@Retention(RetentionPolicy.SOURCE)
public @interface OverrideRequestType {}
OverrideRequest(IBinder token, int pid, int requestedState,
OverrideRequest(IBinder token, int pid, int uid, int requestedState,
@DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) {
mToken = token;
mPid = pid;
mUid = uid;
mRequestedState = requestedState;
mFlags = flags;
mRequestType = requestType;
@@ -85,6 +87,10 @@ final class OverrideRequest {
return mPid;
}
int getUid() {
return mUid;
}
int getRequestedState() {
return mRequestedState;
}

View File

@@ -59,6 +59,11 @@ final class OverrideRequestController {
@Retention(RetentionPolicy.SOURCE)
@interface RequestStatus {}
/**
* A flag indicating that the status change was triggered by thermal critical status.
*/
static final int FLAG_THERMAL_CRITICAL = 1 << 0;
static String statusToString(@RequestStatus int status) {
switch (status) {
case STATUS_ACTIVE:
@@ -106,7 +111,7 @@ final class OverrideRequestController {
void addRequest(@NonNull OverrideRequest request) {
OverrideRequest previousRequest = mRequest;
mRequest = request;
mListener.onStatusChanged(request, STATUS_ACTIVE);
mListener.onStatusChanged(request, STATUS_ACTIVE, 0 /* flags */);
if (previousRequest != null) {
cancelRequestLocked(previousRequest);
@@ -116,7 +121,7 @@ final class OverrideRequestController {
void addBaseStateRequest(@NonNull OverrideRequest request) {
OverrideRequest previousRequest = mBaseStateRequest;
mBaseStateRequest = request;
mListener.onStatusChanged(request, STATUS_ACTIVE);
mListener.onStatusChanged(request, STATUS_ACTIVE, 0 /* flags */);
if (previousRequest != null) {
cancelRequestLocked(previousRequest);
@@ -219,14 +224,17 @@ final class OverrideRequestController {
* Notifies the controller that the set of supported states has changed. The controller will
* notify the listener of all changes to request status as a result of this change.
*/
void handleNewSupportedStates(int[] newSupportedStates) {
void handleNewSupportedStates(int[] newSupportedStates,
@DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
boolean isThermalCritical =
reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
if (mBaseStateRequest != null && !contains(newSupportedStates,
mBaseStateRequest.getRequestedState())) {
cancelCurrentBaseStateRequestLocked();
cancelCurrentBaseStateRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
}
if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) {
cancelCurrentRequestLocked();
cancelCurrentRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
}
}
@@ -244,7 +252,11 @@ final class OverrideRequestController {
}
private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel) {
mListener.onStatusChanged(requestToCancel, STATUS_CANCELED);
cancelRequestLocked(requestToCancel, 0 /* flags */);
}
private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel, int flags) {
mListener.onStatusChanged(requestToCancel, STATUS_CANCELED, flags);
}
/**
@@ -252,12 +264,16 @@ final class OverrideRequestController {
* Notifies the listener of the canceled status as well.
*/
private void cancelCurrentRequestLocked() {
cancelCurrentRequestLocked(0 /* flags */);
}
private void cancelCurrentRequestLocked(int flags) {
if (mRequest == null) {
Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
return;
}
mStickyRequest = false;
cancelRequestLocked(mRequest);
cancelRequestLocked(mRequest, flags);
mRequest = null;
}
@@ -266,11 +282,15 @@ final class OverrideRequestController {
* Notifies the listener of the canceled status as well.
*/
private void cancelCurrentBaseStateRequestLocked() {
cancelCurrentBaseStateRequestLocked(0 /* flags */);
}
private void cancelCurrentBaseStateRequestLocked(int flags) {
if (mBaseStateRequest == null) {
Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
return;
}
cancelRequestLocked(mBaseStateRequest);
cancelRequestLocked(mBaseStateRequest, flags);
mBaseStateRequest = null;
}
@@ -284,12 +304,14 @@ final class OverrideRequestController {
}
public interface StatusChangeListener {
/**
* Notifies the listener of a change in request status. If a change within the controller
* causes one request to become active and one to become either suspended or cancelled, this
* method is guaranteed to be called with the active request first before the suspended or
* cancelled request.
*/
void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus);
void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus,
int flags);
}
}

View File

@@ -366,12 +366,12 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
}
mListener = listener;
}
notifySupportedStatesChanged();
notifySupportedStatesChanged(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
notifyDeviceStateChangedIfNeeded();
}
/** Notifies the listener that the set of supported device states has changed. */
private void notifySupportedStatesChanged() {
private void notifySupportedStatesChanged(@SupportedStatesUpdatedReason int reason) {
List<DeviceState> supportedStates = new ArrayList<>();
Listener listener;
synchronized (mLock) {
@@ -390,7 +390,7 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
}
listener.onSupportedDeviceStatesChanged(
supportedStates.toArray(new DeviceState[supportedStates.size()]));
supportedStates.toArray(new DeviceState[supportedStates.size()]), reason);
}
/** Computes the current device state and notifies the listener of a change, if needed. */
@@ -688,7 +688,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
if (isThermalStatusCriticalOrAbove != isPreviousThermalStatusCriticalOrAbove) {
Slog.i(TAG, "Updating supported device states due to thermal status change."
+ " isThermalStatusCriticalOrAbove: " + isThermalStatusCriticalOrAbove);
notifySupportedStatesChanged();
notifySupportedStatesChanged(
isThermalStatusCriticalOrAbove
? SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
: SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL);
}
}

View File

@@ -945,13 +945,15 @@ public final class DeviceStateManagerServiceTest {
}
mListener = listener;
mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates);
mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates,
SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
mListener.onStateChanged(mSupportedDeviceStates[0].getIdentifier());
}
public void notifySupportedDeviceStates(DeviceState[] supportedDeviceStates) {
mSupportedDeviceStates = supportedDeviceStates;
mListener.onSupportedDeviceStatesChanged(supportedDeviceStates);
mListener.onSupportedDeviceStatesChanged(supportedDeviceStates,
SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
}
public void setState(int identifier) {

View File

@@ -0,0 +1,187 @@
/*
* Copyright (C) 2023 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 org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
/**
* Unit tests for {@link DeviceStateNotificationController}.
* <p/>
* Run with <code>atest com.android.server.devicestate.DeviceStateNotificationControllerTest</code>.
*/
@Presubmit
@RunWith(AndroidJUnit4.class)
public class DeviceStateNotificationControllerTest {
private static final int STATE_WITHOUT_NOTIFICATION = 1;
private static final int STATE_WITH_ACTIVE_NOTIFICATION = 2;
private static final int STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION = 3;
private static final int VALID_APP_UID = 1000;
private static final int INVALID_APP_UID = 2000;
private static final String VALID_APP_NAME = "Valid app name";
private static final String INVALID_APP_NAME = "Invalid app name";
private static final String VALID_APP_LABEL = "Valid app label";
private static final String NAME_1 = "name1";
private static final String TITLE_1 = "title1";
private static final String CONTENT_1 = "content1:%1$s";
private static final String NAME_2 = "name2";
private static final String TITLE_2 = "title2";
private static final String CONTENT_2 = "content2:%1$s";
private static final String THERMAL_TITLE_2 = "thermal_title2";
private static final String THERMAL_CONTENT_2 = "thermal_content2";
private DeviceStateNotificationController mController;
private final ArgumentCaptor<Notification> mNotificationCaptor = ArgumentCaptor.forClass(
Notification.class);
private final NotificationManager mNotificationManager = mock(NotificationManager.class);
@Before
public void setup() throws Exception {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
Handler handler = mock(Handler.class);
PackageManager packageManager = mock(PackageManager.class);
Runnable cancelStateRunnable = mock(Runnable.class);
ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
final SparseArray<DeviceStateNotificationController.NotificationInfo> notificationInfos =
new SparseArray<>();
notificationInfos.put(STATE_WITH_ACTIVE_NOTIFICATION,
new DeviceStateNotificationController.NotificationInfo(
NAME_1, TITLE_1, CONTENT_1,
"", ""));
notificationInfos.put(STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION,
new DeviceStateNotificationController.NotificationInfo(
NAME_2, TITLE_2, CONTENT_2,
THERMAL_TITLE_2, THERMAL_CONTENT_2));
when(packageManager.getNameForUid(VALID_APP_UID)).thenReturn(VALID_APP_NAME);
when(packageManager.getNameForUid(INVALID_APP_UID)).thenReturn(INVALID_APP_NAME);
when(packageManager.getApplicationInfo(eq(VALID_APP_NAME), ArgumentMatchers.any()))
.thenReturn(applicationInfo);
when(packageManager.getApplicationInfo(eq(INVALID_APP_NAME), ArgumentMatchers.any()))
.thenThrow(new PackageManager.NameNotFoundException());
when(applicationInfo.loadLabel(eq(packageManager))).thenReturn(VALID_APP_LABEL);
mController = new DeviceStateNotificationController(
context, handler, cancelStateRunnable, notificationInfos,
packageManager, mNotificationManager);
}
@Test
public void test_activeNotification() {
mController.showStateActiveNotificationIfNeeded(
STATE_WITH_ACTIVE_NOTIFICATION, VALID_APP_UID);
// Verify that the notification manager is called with correct notification information.
verify(mNotificationManager).notify(
eq(DeviceStateNotificationController.NOTIFICATION_TAG),
eq(DeviceStateNotificationController.NOTIFICATION_ID),
mNotificationCaptor.capture());
Notification notification = mNotificationCaptor.getValue();
assertEquals(TITLE_1, notification.extras.getString(Notification.EXTRA_TITLE));
assertEquals(String.format(CONTENT_1, VALID_APP_LABEL),
notification.extras.getString(Notification.EXTRA_TEXT));
assertEquals(Notification.FLAG_ONGOING_EVENT,
notification.flags & Notification.FLAG_ONGOING_EVENT);
// Verify that the notification action is as expected.
Notification.Action[] actions = notification.actions;
assertEquals(1, actions.length);
Notification.Action action = actions[0];
assertEquals(DeviceStateNotificationController.INTENT_ACTION_CANCEL_STATE,
action.actionIntent.getIntent().getAction());
// Verify that the notification is properly canceled.
mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
verify(mNotificationManager).cancel(
DeviceStateNotificationController.NOTIFICATION_TAG,
DeviceStateNotificationController.NOTIFICATION_ID);
}
@Test
public void test_thermalNotification() {
// Verify that the active notification is created.
mController.showStateActiveNotificationIfNeeded(
STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION, VALID_APP_UID);
verify(mNotificationManager).notify(
eq(DeviceStateNotificationController.NOTIFICATION_TAG),
eq(DeviceStateNotificationController.NOTIFICATION_ID),
mNotificationCaptor.capture());
Notification notification = mNotificationCaptor.getValue();
assertEquals(TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
assertEquals(String.format(CONTENT_2, VALID_APP_LABEL),
notification.extras.getString(Notification.EXTRA_TEXT));
assertEquals(Notification.FLAG_ONGOING_EVENT,
notification.flags & Notification.FLAG_ONGOING_EVENT);
Mockito.clearInvocations(mNotificationManager);
// Verify that the thermal critical notification is created.
mController.showThermalCriticalNotificationIfNeeded(
STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION);
verify(mNotificationManager).notify(
eq(DeviceStateNotificationController.NOTIFICATION_TAG),
eq(DeviceStateNotificationController.NOTIFICATION_ID),
mNotificationCaptor.capture());
notification = mNotificationCaptor.getValue();
assertEquals(THERMAL_TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
assertEquals(THERMAL_CONTENT_2, notification.extras.getString(Notification.EXTRA_TEXT));
assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT);
// Verify that the notification is canceled.
mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
verify(mNotificationManager).cancel(
DeviceStateNotificationController.NOTIFICATION_TAG,
DeviceStateNotificationController.NOTIFICATION_ID);
}
@Test
public void test_deviceStateWithoutNotification() {
// Verify that no notification is created.
mController.showStateActiveNotificationIfNeeded(
STATE_WITHOUT_NOTIFICATION, VALID_APP_UID);
verify(mNotificationManager, Mockito.never()).notify(
eq(DeviceStateNotificationController.NOTIFICATION_TAG),
eq(DeviceStateNotificationController.NOTIFICATION_ID),
mNotificationCaptor.capture());
}
}

View File

@@ -58,7 +58,7 @@ public final class OverrideRequestControllerTest {
@Test
public void addRequest() {
OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
assertNull(mStatusListener.getLastStatus(request));
@@ -68,14 +68,14 @@ public final class OverrideRequestControllerTest {
@Test
public void addRequest_cancelExistingRequestThroughNewRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
assertNull(mStatusListener.getLastStatus(firstRequest));
mController.addRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
assertNull(mStatusListener.getLastStatus(secondRequest));
@@ -86,7 +86,7 @@ public final class OverrideRequestControllerTest {
@Test
public void addRequest_cancelActiveRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
mController.addRequest(firstRequest);
@@ -100,7 +100,7 @@ public final class OverrideRequestControllerTest {
@Test
public void addBaseStateRequest() {
OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
assertNull(mStatusListener.getLastStatus(request));
@@ -110,14 +110,14 @@ public final class OverrideRequestControllerTest {
@Test
public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
assertNull(mStatusListener.getLastStatus(firstRequest));
mController.addBaseStateRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
assertNull(mStatusListener.getLastStatus(secondRequest));
@@ -128,7 +128,7 @@ public final class OverrideRequestControllerTest {
@Test
public void addBaseStateRequest_cancelActiveBaseStateRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
mController.addBaseStateRequest(firstRequest);
@@ -142,12 +142,13 @@ public final class OverrideRequestControllerTest {
@Test
public void handleBaseStateChanged() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */,
DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */,
OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* uid */,
0 /* requestedState */,
0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
@@ -167,10 +168,11 @@ public final class OverrideRequestControllerTest {
@Test
public void handleProcessDied() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* uid */,
1 /* requestedState */,
0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
@@ -189,10 +191,11 @@ public final class OverrideRequestControllerTest {
public void handleProcessDied_stickyRequests() {
mController.setStickyRequestsAllowed(true);
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
mController.addRequest(firstRequest);
@@ -211,10 +214,11 @@ public final class OverrideRequestControllerTest {
@Test
public void handleNewSupportedStates() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* uid */,
1 /* requestedState */,
0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
@@ -224,18 +228,20 @@ public final class OverrideRequestControllerTest {
mController.addBaseStateRequest(baseStateRequest);
assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
mController.handleNewSupportedStates(new int[]{0, 1});
mController.handleNewSupportedStates(new int[]{0, 1},
DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
mController.handleNewSupportedStates(new int[]{0});
mController.handleNewSupportedStates(new int[]{0},
DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
}
@Test
public void cancelOverrideRequestsTest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
mController.addRequest(firstRequest);
@@ -250,7 +256,7 @@ public final class OverrideRequestControllerTest {
private Map<OverrideRequest, Integer> mLastStatusMap = new HashMap<>();
@Override
public void onStatusChanged(@NonNull OverrideRequest request, int newStatus) {
public void onStatusChanged(@NonNull OverrideRequest request, int newStatus, int flags) {
mLastStatusMap.put(request, newStatus);
}

View File

@@ -19,6 +19,9 @@ package com.android.server.policy;
import static android.content.Context.SENSOR_SERVICE;
import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED;
import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL;
import static com.android.server.policy.DeviceStateProviderImpl.DEFAULT_DEVICE_STATE;
import static org.junit.Assert.assertArrayEquals;
@@ -124,7 +127,8 @@ public final class DeviceStateProviderImplTest {
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(new DeviceState[]{DEFAULT_DEVICE_STATE},
mDeviceStateArrayCaptor.getValue());
@@ -151,7 +155,8 @@ public final class DeviceStateProviderImplTest {
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "", 0 /* flags */) };
@@ -183,7 +188,8 @@ public final class DeviceStateProviderImplTest {
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS),
new DeviceState(2, "", 0 /* flags */) };
@@ -212,7 +218,8 @@ public final class DeviceStateProviderImplTest {
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "", 0 /* flags */) };
@@ -247,7 +254,8 @@ public final class DeviceStateProviderImplTest {
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "CLOSED", 0 /* flags */) };
@@ -265,7 +273,8 @@ public final class DeviceStateProviderImplTest {
Mockito.clearInvocations(listener);
provider.notifyLidSwitchChanged(1, true /* lidOpen */);
verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue());
}
@@ -336,7 +345,8 @@ public final class DeviceStateProviderImplTest {
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -358,7 +368,8 @@ public final class DeviceStateProviderImplTest {
provider.onSensorChanged(event0);
verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(3, mIntegerCaptor.getValue().intValue());
@@ -370,7 +381,8 @@ public final class DeviceStateProviderImplTest {
provider.onSensorChanged(event1);
verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(2, mIntegerCaptor.getValue().intValue());
@@ -382,7 +394,8 @@ public final class DeviceStateProviderImplTest {
provider.onSensorChanged(event2);
verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue());
}
@@ -397,7 +410,8 @@ public final class DeviceStateProviderImplTest {
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -410,12 +424,14 @@ public final class DeviceStateProviderImplTest {
Mockito.clearInvocations(listener);
provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_MODERATE);
verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
Mockito.clearInvocations(listener);
// The THERMAL_TEST state should be disabled.
provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_CRITICAL);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -426,7 +442,8 @@ public final class DeviceStateProviderImplTest {
// The THERMAL_TEST state should be re-enabled.
provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -468,7 +485,8 @@ public final class DeviceStateProviderImplTest {
provider.onSensorChanged(event2);
verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
}
@@ -513,7 +531,8 @@ public final class DeviceStateProviderImplTest {
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),