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 0dd0a70835b3e..da44c2efe14e6 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6241,4 +6241,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 b29dfb7e25c50..d82e898a64f46 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4879,6 +4879,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 */),