diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index b35481d3c31ba..97e753e2bdeb8 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -227,6 +227,12 @@ @string/concurrent_display_notification_thermal_content + + @string/concurrent_display_notification_power_save_title + + + @string/concurrent_display_notification_power_save_content + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d8e69d748324d..6afdae5086232 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6267,6 +6267,12 @@ ul. Device is too warm Dual Screen is unavailable because your phone is getting too warm + + Dual Screen is unavailable + + Dual Screen is unavailable because Battery Saver is on. You can turn this off in Settings. + + Go to Settings Turn off diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c10612e16acf5..1cb56e0c203b8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4931,12 +4931,17 @@ + + + + + diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java index a589313a1e921..00af22468fd3d 100644 --- a/services/core/java/com/android/server/devicestate/DeviceState.java +++ b/services/core/java/com/android/server/devicestate/DeviceState.java @@ -76,7 +76,13 @@ public final class DeviceState { * This flag indicates that the corresponding state should be disabled when the device is * overheating and reaching the critical status. */ - public static final int FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4; + public static final int FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4; + + /** + * This flag indicates that the corresponding state should be disabled when power save mode + * is enabled. + */ + public static final int FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE = 1 << 5; /** @hide */ @IntDef(prefix = {"FLAG_"}, flag = true, value = { @@ -84,7 +90,8 @@ public final class DeviceState { FLAG_APP_INACCESSIBLE, FLAG_EMULATED_ONLY, FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP, - FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL + FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL, + FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE }) @Retention(RetentionPolicy.SOURCE) public @interface DeviceStateFlags {} diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 43ee5e268fe05..964569008accc 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_POWER_SAVE_ENABLED; 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; @@ -609,7 +610,8 @@ public final class DeviceStateManagerService extends SystemService { @GuardedBy("mLock") private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request, - @OverrideRequestController.RequestStatus int status, int flags) { + @OverrideRequestController.RequestStatus int status, + @OverrideRequestController.StatusChangedFlag int flags) { if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) { switch (status) { case STATUS_ACTIVE: @@ -641,6 +643,10 @@ public final class DeviceStateManagerService extends SystemService { mDeviceStateNotificationController .showThermalCriticalNotificationIfNeeded( request.getRequestedState()); + } else if ((flags & FLAG_POWER_SAVE_ENABLED) == FLAG_POWER_SAVE_ENABLED) { + mDeviceStateNotificationController + .showPowerSaveNotificationIfNeeded( + request.getRequestedState()); } } break; diff --git a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java index 900874044881c..ab261ac240910 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java @@ -16,6 +16,8 @@ package com.android.server.devicestate; +import static android.provider.Settings.ACTION_BATTERY_SAVER_SETTINGS; + import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; @@ -101,10 +103,16 @@ class DeviceStateNotificationController extends BroadcastReceiver { } String requesterApplicationLabel = getApplicationLabel(requestingAppUid); if (requesterApplicationLabel != null) { + final Intent intent = new Intent(INTENT_ACTION_CANCEL_STATE) + .setPackage(mContext.getPackageName()); + final PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE); showNotification( info.name, info.activeNotificationTitle, String.format(info.activeNotificationContent, requesterApplicationLabel), - true /* ongoing */, R.drawable.ic_dual_screen + true /* ongoing */, R.drawable.ic_dual_screen, + pendingIntent, + mContext.getString(R.string.device_state_notification_turn_off_button) ); } else { Slog.e(TAG, "Cannot determine the requesting app name when showing state active " @@ -126,7 +134,33 @@ class DeviceStateNotificationController extends BroadcastReceiver { showNotification( info.name, info.thermalCriticalNotificationTitle, info.thermalCriticalNotificationContent, false /* ongoing */, - R.drawable.ic_thermostat + R.drawable.ic_thermostat, + null /* pendingIntent */, + null /* actionText */ + ); + } + + /** + * Displays the notification indicating that the device state is canceled due to power + * save mode being enabled. Does nothing if the state does not have a power save mode + * notification. + * + * @param state the identifier of the device state being canceled. + */ + void showPowerSaveNotificationIfNeeded(int state) { + NotificationInfo info = mNotificationInfos.get(state); + if (info == null || !info.hasPowerSaveModeNotification()) { + return; + } + final Intent intent = new Intent(ACTION_BATTERY_SAVER_SETTINGS); + final PendingIntent pendingIntent = PendingIntent.getActivity( + mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE); + showNotification( + info.name, info.powerSaveModeNotificationTitle, + info.powerSaveModeNotificationContent, false /* ongoing */, + R.drawable.ic_thermostat, + pendingIntent, + mContext.getString(R.string.device_state_notification_settings_button) ); } @@ -161,7 +195,8 @@ class DeviceStateNotificationController extends BroadcastReceiver { */ private void showNotification( @NonNull String name, @NonNull String title, @NonNull String content, boolean ongoing, - @DrawableRes int iconRes) { + @DrawableRes int iconRes, + @Nullable PendingIntent pendingIntent, @Nullable String actionText) { final NotificationChannel channel = new NotificationChannel( CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH); final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID) @@ -173,14 +208,10 @@ class DeviceStateNotificationController extends BroadcastReceiver { .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); + if (pendingIntent != null && actionText != null) { final Notification.Action action = new Notification.Action.Builder( null /* icon */, - mContext.getString(R.string.device_state_notification_turn_off_button), + actionText, pendingIntent) .build(); builder.addAction(action); @@ -215,12 +246,21 @@ class DeviceStateNotificationController extends BroadcastReceiver { final String[] thermalCriticalNotificationContents = context.getResources().getStringArray( R.array.device_state_notification_thermal_contents); + final String[] powerSaveModeNotificationTitles = + context.getResources().getStringArray( + R.array.device_state_notification_power_save_titles); + final String[] powerSaveModeNotificationContents = + context.getResources().getStringArray( + R.array.device_state_notification_power_save_contents); + if (stateIdentifiers.length != names.length || stateIdentifiers.length != activeNotificationTitles.length || stateIdentifiers.length != activeNotificationContents.length || stateIdentifiers.length != thermalCriticalNotificationTitles.length || stateIdentifiers.length != thermalCriticalNotificationContents.length + || stateIdentifiers.length != powerSaveModeNotificationTitles.length + || stateIdentifiers.length != powerSaveModeNotificationContents.length ) { throw new IllegalStateException( "The length of state identifiers and notification texts must match!"); @@ -237,7 +277,9 @@ class DeviceStateNotificationController extends BroadcastReceiver { new NotificationInfo( names[i], activeNotificationTitles[i], activeNotificationContents[i], thermalCriticalNotificationTitles[i], - thermalCriticalNotificationContents[i]) + thermalCriticalNotificationContents[i], + powerSaveModeNotificationTitles[i], + powerSaveModeNotificationContents[i]) ); } @@ -272,16 +314,21 @@ class DeviceStateNotificationController extends BroadcastReceiver { public final String activeNotificationContent; public final String thermalCriticalNotificationTitle; public final String thermalCriticalNotificationContent; + public final String powerSaveModeNotificationTitle; + public final String powerSaveModeNotificationContent; NotificationInfo(String name, String activeNotificationTitle, String activeNotificationContent, String thermalCriticalNotificationTitle, - String thermalCriticalNotificationContent) { + String thermalCriticalNotificationContent, String powerSaveModeNotificationTitle, + String powerSaveModeNotificationContent) { this.name = name; this.activeNotificationTitle = activeNotificationTitle; this.activeNotificationContent = activeNotificationContent; this.thermalCriticalNotificationTitle = thermalCriticalNotificationTitle; this.thermalCriticalNotificationContent = thermalCriticalNotificationContent; + this.powerSaveModeNotificationTitle = powerSaveModeNotificationTitle; + this.powerSaveModeNotificationContent = powerSaveModeNotificationContent; } boolean hasActiveNotification() { @@ -292,5 +339,10 @@ class DeviceStateNotificationController extends BroadcastReceiver { return thermalCriticalNotificationTitle != null && thermalCriticalNotificationTitle.length() > 0; } + + boolean hasPowerSaveModeNotification() { + return powerSaveModeNotificationTitle != null + && powerSaveModeNotificationTitle.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 fecc13fd0d034..af33de0426b1c 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java @@ -52,11 +52,24 @@ public interface DeviceStateProvider { */ int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL = 3; + /** + * Indicating that the supported device states have changed because power save mode was enabled. + */ + int SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED = 4; + + /** + * Indicating that the supported device states have changed because power save mode was + * disabled. + */ + int SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED = 5; + @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 + SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL, + SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED, + SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED }) @Retention(RetentionPolicy.SOURCE) @interface SupportedStatesUpdatedReason {} diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java index 2ed4765874f79..46f0bc0d98056 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java @@ -64,6 +64,18 @@ final class OverrideRequestController { */ static final int FLAG_THERMAL_CRITICAL = 1 << 0; + /** + * A flag indicating that the status change was triggered by power save mode. + */ + static final int FLAG_POWER_SAVE_ENABLED = 1 << 1; + + @IntDef(flag = true, prefix = {"FLAG_"}, value = { + FLAG_THERMAL_CRITICAL, + FLAG_POWER_SAVE_ENABLED + }) + @Retention(RetentionPolicy.SOURCE) + @interface StatusChangedFlag {} + static String statusToString(@RequestStatus int status) { switch (status) { case STATUS_ACTIVE: @@ -228,13 +240,18 @@ final class OverrideRequestController { @DeviceStateProvider.SupportedStatesUpdatedReason int reason) { boolean isThermalCritical = reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL; + boolean isPowerSaveEnabled = + reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED; + @StatusChangedFlag int flags = 0; + flags |= isThermalCritical ? FLAG_THERMAL_CRITICAL : 0; + flags |= isPowerSaveEnabled ? FLAG_POWER_SAVE_ENABLED : 0; if (mBaseStateRequest != null && !contains(newSupportedStates, mBaseStateRequest.getRequestedState())) { - cancelCurrentBaseStateRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0); + cancelCurrentBaseStateRequestLocked(flags); } if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) { - cancelCurrentRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0); + cancelCurrentRequestLocked(flags); } } @@ -255,7 +272,8 @@ final class OverrideRequestController { cancelRequestLocked(requestToCancel, 0 /* flags */); } - private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel, int flags) { + private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel, + @StatusChangedFlag int flags) { mListener.onStatusChanged(requestToCancel, STATUS_CANCELED, flags); } @@ -267,7 +285,7 @@ final class OverrideRequestController { cancelCurrentRequestLocked(0 /* flags */); } - private void cancelCurrentRequestLocked(int flags) { + private void cancelCurrentRequestLocked(@StatusChangedFlag int flags) { if (mRequest == null) { Slog.w(TAG, "Attempted to cancel a null OverrideRequest"); return; @@ -285,7 +303,7 @@ final class OverrideRequestController { cancelCurrentBaseStateRequestLocked(0 /* flags */); } - private void cancelCurrentBaseStateRequestLocked(int flags) { + private void cancelCurrentBaseStateRequestLocked(@StatusChangedFlag int flags) { if (mBaseStateRequest == null) { Slog.w(TAG, "Attempted to cancel a null OverrideRequest"); return; @@ -312,6 +330,6 @@ final class OverrideRequestController { * cancelled request. */ void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus, - int flags); + @StatusChangedFlag 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 8d7f78209a4e7..3644054e3b780 100644 --- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java +++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java @@ -21,7 +21,10 @@ import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STA import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -101,8 +104,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY"; private static final String FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = "FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP"; - private static final String FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL = - "FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL"; + private static final String FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL = + "FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL"; + private static final String FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE = + "FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE"; /** Interface that allows reading the device state configuration. */ interface ReadableConfig { @@ -162,9 +167,12 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP: flags |= DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP; break; - case FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL: - flags |= DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL; + case FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL: + flags |= DeviceState + .FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL; break; + case FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE: + flags |= DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE; default: Slog.w(TAG, "Parsed unknown flag with name: " + configFlagString); @@ -210,6 +218,9 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, @GuardedBy("mLock") private @PowerManager.ThermalStatus int mThermalStatus = PowerManager.THERMAL_STATUS_NONE; + @GuardedBy("mLock") + private boolean mPowerSaveModeEnabled; + private DeviceStateProviderImpl(@NonNull Context context, @NonNull List deviceStates, @NonNull List stateConditions) { @@ -224,14 +235,32 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, setStateConditions(deviceStates, stateConditions); - // If any of the device states are thermal sensitive, i.e. it should be disabled when the - // device is overheating, then we will update the list of supported states when thermal - // status changes. - if (hasThermalSensitiveState(deviceStates)) { - PowerManager powerManager = context.getSystemService(PowerManager.class); - if (powerManager != null) { + PowerManager powerManager = context.getSystemService(PowerManager.class); + if (powerManager != null) { + // If any of the device states are thermal sensitive, i.e. it should be disabled when + // the device is overheating, then we will update the list of supported states when + // thermal status changes. + if (hasThermalSensitiveState(deviceStates)) { powerManager.addThermalStatusListener(this); } + + // If any of the device states are power sensitive, i.e. it should be disabled when + // power save mode is enabled, then we will update the list of supported states when + // power save mode is toggled. + if (hasPowerSaveSensitiveState(deviceStates)) { + IntentFilter filter = new IntentFilter( + PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL); + BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL.equals( + intent.getAction())) { + onPowerSaveModeChanged(powerManager.isPowerSaveMode()); + } + } + }; + mContext.registerReceiver(receiver, filter); + } } } @@ -382,7 +411,11 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, for (DeviceState deviceState : mOrderedStates) { if (isThermalStatusCriticalOrAbove(mThermalStatus) && deviceState.hasFlag( - DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) { + DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) { + continue; + } + if (mPowerSaveModeEnabled && deviceState.hasFlag( + DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) { continue; } supportedStates.add(deviceState); @@ -674,6 +707,18 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, } } + @VisibleForTesting + void onPowerSaveModeChanged(boolean isPowerSaveModeEnabled) { + synchronized (mLock) { + if (mPowerSaveModeEnabled != isPowerSaveModeEnabled) { + mPowerSaveModeEnabled = isPowerSaveModeEnabled; + notifySupportedStatesChanged( + isPowerSaveModeEnabled ? SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED + : SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED); + } + } + } + @Override public void onThermalStatusChanged(@PowerManager.ThermalStatus int thermalStatus) { int previousThermalStatus; @@ -709,7 +754,16 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, private static boolean hasThermalSensitiveState(List deviceStates) { for (DeviceState state : deviceStates) { - if (state.hasFlag(DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) { + if (state.hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) { + return true; + } + } + return false; + } + + private static boolean hasPowerSaveSensitiveState(List deviceStates) { + for (int i = 0; i < deviceStates.size(); i++) { + if (deviceStates.get(i).hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) { return true; } } diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java index 8196d6a35cbd6..e396263b1679d 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java @@ -52,7 +52,7 @@ 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 STATE_WITH_ALL_NOTIFICATION = 3; private static final int VALID_APP_UID = 1000; private static final int INVALID_APP_UID = 2000; @@ -68,6 +68,8 @@ public class DeviceStateNotificationControllerTest { 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 static final String POWER_SAVE_TITLE_2 = "power_save_title2"; + private static final String POWER_SAVE_CONTENT_2 = "power_save_content2"; private DeviceStateNotificationController mController; @@ -88,11 +90,12 @@ public class DeviceStateNotificationControllerTest { notificationInfos.put(STATE_WITH_ACTIVE_NOTIFICATION, new DeviceStateNotificationController.NotificationInfo( NAME_1, TITLE_1, CONTENT_1, - "", "")); - notificationInfos.put(STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION, + "", "", "", "")); + notificationInfos.put(STATE_WITH_ALL_NOTIFICATION, new DeviceStateNotificationController.NotificationInfo( NAME_2, TITLE_2, CONTENT_2, - THERMAL_TITLE_2, THERMAL_CONTENT_2)); + THERMAL_TITLE_2, THERMAL_CONTENT_2, + POWER_SAVE_TITLE_2, POWER_SAVE_CONTENT_2)); when(packageManager.getNameForUid(VALID_APP_UID)).thenReturn(VALID_APP_NAME); when(packageManager.getNameForUid(INVALID_APP_UID)).thenReturn(INVALID_APP_NAME); @@ -138,11 +141,47 @@ public class DeviceStateNotificationControllerTest { DeviceStateNotificationController.NOTIFICATION_ID); } + @Test + public void test_powerSaveNotification() { + // Verify that the active notification is created. + mController.showStateActiveNotificationIfNeeded( + STATE_WITH_ALL_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.showPowerSaveNotificationIfNeeded( + STATE_WITH_ALL_NOTIFICATION); + verify(mNotificationManager).notify( + eq(DeviceStateNotificationController.NOTIFICATION_TAG), + eq(DeviceStateNotificationController.NOTIFICATION_ID), + mNotificationCaptor.capture()); + notification = mNotificationCaptor.getValue(); + assertEquals(POWER_SAVE_TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE)); + assertEquals(POWER_SAVE_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_ALL_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); + STATE_WITH_ALL_NOTIFICATION, VALID_APP_UID); verify(mNotificationManager).notify( eq(DeviceStateNotificationController.NOTIFICATION_TAG), eq(DeviceStateNotificationController.NOTIFICATION_ID), @@ -157,7 +196,7 @@ public class DeviceStateNotificationControllerTest { // Verify that the thermal critical notification is created. mController.showThermalCriticalNotificationIfNeeded( - STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION); + STATE_WITH_ALL_NOTIFICATION); verify(mNotificationManager).notify( eq(DeviceStateNotificationController.NOTIFICATION_TAG), eq(DeviceStateNotificationController.NOTIFICATION_ID), @@ -168,7 +207,7 @@ public class DeviceStateNotificationControllerTest { assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT); // Verify that the notification is canceled. - mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION); + mController.cancelNotification(STATE_WITH_ALL_NOTIFICATION); verify(mNotificationManager).cancel( DeviceStateNotificationController.NOTIFICATION_TAG, DeviceStateNotificationController.NOTIFICATION_ID); 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 7125796e7b986..7e40f96154d2f 100644 --- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java @@ -20,6 +20,8 @@ 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_POWER_SAVE_DISABLED; +import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED; 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; @@ -327,7 +329,8 @@ public final class DeviceStateProviderImplTest { + " THERMAL_TEST\n" + " \n" + " FLAG_EMULATED_ONLY\n" - + " FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL\n" + + " FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL\n" + + " FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE\n" + " \n" + " \n" + "\n"; @@ -354,7 +357,8 @@ public final class DeviceStateProviderImplTest { new DeviceState(3, "OPENED", 0 /* flags */), new DeviceState(4, "THERMAL_TEST", DeviceState.FLAG_EMULATED_ONLY - | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) }, + | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL + | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) }, mDeviceStateArrayCaptor.getValue()); // onStateChanged() should not be called because the provider has not yet been notified of // the initial sensor state. @@ -419,7 +423,8 @@ public final class DeviceStateProviderImplTest { new DeviceState(3, "OPENED", 0 /* flags */), new DeviceState(4, "THERMAL_TEST", DeviceState.FLAG_EMULATED_ONLY - | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) }, + | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL + | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) }, mDeviceStateArrayCaptor.getValue()); Mockito.clearInvocations(listener); @@ -451,7 +456,65 @@ public final class DeviceStateProviderImplTest { new DeviceState(3, "OPENED", 0 /* flags */), new DeviceState(4, "THERMAL_TEST", DeviceState.FLAG_EMULATED_ONLY - | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) }, + | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL + | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) }, + mDeviceStateArrayCaptor.getValue()); + } + + @Test + public void test_flagDisableWhenPowerSaveEnabled() throws Exception { + Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE); + when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor)); + DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor); + + provider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */); + DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class); + provider.setListener(listener); + + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED)); + assertArrayEquals( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "THERMAL_TEST", + DeviceState.FLAG_EMULATED_ONLY + | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL + | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) }, + mDeviceStateArrayCaptor.getValue()); + Mockito.clearInvocations(listener); + + provider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */); + verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED)); + Mockito.clearInvocations(listener); + + // The THERMAL_TEST state should be disabled due to power save being enabled. + provider.onPowerSaveModeChanged(true /* isPowerSaveModeEnabled */); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED)); + assertArrayEquals( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */) }, + mDeviceStateArrayCaptor.getValue()); + Mockito.clearInvocations(listener); + + // The THERMAL_TEST state should be re-enabled. + provider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED)); + assertArrayEquals( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "THERMAL_TEST", + DeviceState.FLAG_EMULATED_ONLY + | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL + | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) }, mDeviceStateArrayCaptor.getValue()); }