From b42d6cb4cea38048af946aa2c11ef113afed2ead Mon Sep 17 00:00:00 2001 From: Jiaming Liu Date: Thu, 19 Jan 2023 18:04:45 +0000 Subject: [PATCH] Add notifications for the concurrent display mode Show system notifications when the concurrent display mode is active or when the concurrent display mode is canceled due to critical thermal condition. Summary of the changes: 1. Added DeviceStateNotificationController to manage the device state notifications. 2. In DeviceStateProvider, added "reason" to the callback onSupportedDeviceStatesChanged to indicate the cause of the supported device states change (e.g. change due to thermal condition) 3. In OverrideRequestController, added flags to the onStatusChanged callback to indicate the cause of status change (e.g. FLAG_THERMAL_CRITICAL means that the change is caused by critical thermal condition). 4. In OverrideRequest, added a uid field, which is used to get the app name displayed in the notification. Fix: 266096470, 261127342, 264799106 Test: atest com.android.server.policy.DeviceStateProviderImplTest com.android.server.devicestate.DeviceStateManagerServiceTest com.android.server.devicestate.OverrideRequestControllerTest com.android.server.devicestate.DeviceStateNotificationControllerTest Change-Id: Ieb590d47dfbfd37e9e732b179a63ed25e080d0f0 --- core/res/res/values/arrays.xml | 21 ++ core/res/res/values/strings.xml | 13 + core/res/res/values/symbols.xml | 12 + .../DeviceStateManagerService.java | 51 ++- .../DeviceStateNotificationController.java | 293 ++++++++++++++++++ .../devicestate/DeviceStateProvider.java | 46 ++- .../server/devicestate/OverrideRequest.java | 8 +- .../OverrideRequestController.java | 40 ++- .../policy/DeviceStateProviderImpl.java | 11 +- .../DeviceStateManagerServiceTest.java | 6 +- ...DeviceStateNotificationControllerTest.java | 187 +++++++++++ .../OverrideRequestControllerTest.java | 38 ++- .../policy/DeviceStateProviderImplTest.java | 51 ++- 13 files changed, 711 insertions(+), 66 deletions(-) create mode 100644 services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java create mode 100644 services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index 9e735d0508c7f..c3366cf0e306a 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. --> + + + + -1 + + + @string/concurrent_display_notification_name + + + @string/concurrent_display_notification_active_title + + + @string/concurrent_display_notification_active_content + + + @string/concurrent_display_notification_thermal_title + + + @string/concurrent_display_notification_thermal_content + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e330ee316ed43..365555eabea1a 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6234,4 +6234,17 @@ ul. Microphone is available Microphone is blocked + + + Dual screen + + Dual screen is on + + %1$s is using both displays to show content + + Device is too warm + + Dual Screen is unavailable because your phone is getting too warm + + Turn off diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 76af814a30420..7f791c61a9dbd 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4874,6 +4874,18 @@ + + + + + + + + + + + + diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 064cd2d501610..ba96861f00cc0 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 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 = 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); } 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 0000000000000..2f149987e91a8 --- /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 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 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 getNotificationInfos(Context context) { + final SparseArray 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 109bf6358e8bc..fecc13fd0d034 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 @@ 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. *

- * It is required 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 required 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. * diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java index 325e384cb8ad9..74cf184e826e4 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 @@ 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; } diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java index e39c7324d7bda..2ed4765874f79 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 @@ 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); } } diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java index 7f733efef8a23..8d7f78209a4e7 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 @@ 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 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); } } 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 82f64933c2826..c952609a8da4b 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 @@ 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) { 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 0000000000000..8196d6a35cbd6 --- /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}. + *

+ * Run with atest com.android.server.devicestate.DeviceStateNotificationControllerTest. + */ +@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 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 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 430504ca24284..fe37f4241d8e5 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 @@ 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 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 7fac9b6081141..7125796e7b986 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 @@ 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 */),