Merge "Add notifications for the concurrent display mode"
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 9e735d0..c3366cf 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -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>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0dd0a70..da44c2e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6241,4 +6241,17 @@
<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>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b29dfb7..d82e898 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -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 -->
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 064cd2d..ba96861 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -25,6 +25,7 @@
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 @@
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 @@
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 @@
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 @@
return;
}
- mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers);
+ mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers, reason);
updatePendingStateLocked();
setRearDisplayStateLocked();
@@ -596,7 +608,7 @@
@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 @@
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 @@
}
}
- 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 @@
+ " 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 @@
}
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 @@
+ " 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 @@
@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 @@
@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 @@
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 @@
@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 @@
final long callingIdentity = Binder.clearCallingIdentity();
try {
- requestBaseStateOverrideInternal(state, flags, callingPid, token);
+ requestBaseStateOverrideInternal(state, flags, callingPid, callingUid, token);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
new file mode 100644
index 0000000..2f14998
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
@@ -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;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 109bf63..fecc13f 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -19,8 +19,12 @@
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 @@
* @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 @@
* 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.
*
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index 325e384..74cf184 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -31,6 +31,7 @@
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 @@
@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 @@
return mPid;
}
+ int getUid() {
+ return mUid;
+ }
+
int getRequestedState() {
return mRequestedState;
}
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index e39c732..2ed4765 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -59,6 +59,11 @@
@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 @@
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 @@
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 @@
* 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 @@
}
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 @@
* 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 @@
* 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 @@
}
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);
}
}
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 7f733ef..8d7f782 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -366,12 +366,12 @@
}
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 @@
}
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 @@
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);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 82f6493..c952609 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -945,13 +945,15 @@
}
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) {
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
new file mode 100644
index 0000000..8196d6a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
@@ -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());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index 430504c..fe37f42 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -58,7 +58,7 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 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 @@
@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 @@
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 @@
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);
}
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 7fac9b6..7125796 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -19,6 +19,9 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 */),