diff options
12 files changed, 435 insertions, 17 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 71a05a909a09..076fddf234b9 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1597,6 +1597,10 @@ package android.hardware.display { method public void restoreDozeSettings(int); } + public final class ColorDisplayManager { + method @FlaggedApi("android.app.modes_api") @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean isSaturationActivated(); + } + public final class DisplayManager { method public boolean areUserDisabledHdrTypesAllowed(); method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearGlobalUserPreferredDisplayMode(); diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java index aafa7d520632..f927b8b52912 100644 --- a/core/java/android/hardware/display/ColorDisplayManager.java +++ b/core/java/android/hardware/display/ColorDisplayManager.java @@ -17,12 +17,14 @@ package android.hardware.display; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.ContentResolver; import android.content.Context; import android.metrics.LogMaker; @@ -397,6 +399,8 @@ public final class ColorDisplayManager { * @return {@code true} if the display is not at full saturation * @hide */ + @TestApi + @FlaggedApi(android.app.Flags.FLAG_MODES_API) @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean isSaturationActivated() { return mManager.isSaturationActivated(); diff --git a/core/java/android/service/notification/DeviceEffectsApplier.java b/core/java/android/service/notification/DeviceEffectsApplier.java new file mode 100644 index 000000000000..234ff4dd0852 --- /dev/null +++ b/core/java/android/service/notification/DeviceEffectsApplier.java @@ -0,0 +1,38 @@ +/* + * 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 android.service.notification; + +/** + * Responsible for making any service calls needed to apply the set of {@link ZenDeviceEffects} that + * make sense for the current platform. + * @hide + */ +public interface DeviceEffectsApplier { + /** + * Applies the {@link ZenDeviceEffects} to the device. + * + * <p>The supplied {@code effects} represents the "consolidated" device effects, i.e. the + * union of the effects of all the {@link ZenModeConfig.ZenRule} instances that are currently + * active. If no rules are active (or no active rules specify custom effects) then {@code + * effects} will be all-default (i.e. {@link ZenDeviceEffects#hasEffects} will return {@code + * false}. + * + * <p>This will be called whenever the set of consolidated effects changes (normally through + * the activation or deactivation of zen rules). + */ + void apply(ZenDeviceEffects effects); +} diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java index db0b7ffa0913..0e82b6c2c7d7 100644 --- a/core/java/android/service/notification/ZenDeviceEffects.java +++ b/core/java/android/service/notification/ZenDeviceEffects.java @@ -18,6 +18,7 @@ package android.service.notification; import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Flags; import android.os.Parcel; import android.os.Parcelable; @@ -359,6 +360,27 @@ public final class ZenDeviceEffects implements Parcelable { return this; } + /** + * Applies the effects that are {@code true} on the supplied {@link ZenDeviceEffects} to + * this builder (essentially logically-ORing the effect set). + * @hide + */ + @NonNull + public Builder add(@Nullable ZenDeviceEffects effects) { + if (effects == null) return this; + if (effects.shouldDisplayGrayscale()) setShouldDisplayGrayscale(true); + if (effects.shouldSuppressAmbientDisplay()) setShouldSuppressAmbientDisplay(true); + if (effects.shouldDimWallpaper()) setShouldDimWallpaper(true); + if (effects.shouldUseNightMode()) setShouldUseNightMode(true); + if (effects.shouldDisableAutoBrightness()) setShouldDisableAutoBrightness(true); + if (effects.shouldDisableTapToWake()) setShouldDisableTapToWake(true); + if (effects.shouldDisableTiltToWake()) setShouldDisableTiltToWake(true); + if (effects.shouldDisableTouch()) setShouldDisableTouch(true); + if (effects.shouldMinimizeRadioUsage()) setShouldMinimizeRadioUsage(true); + if (effects.shouldMaximizeDoze()) setShouldMaximizeDoze(true); + return this; + } + /** Builds a {@link ZenDeviceEffects} object based on the builder's state. */ @NonNull public ZenDeviceEffects build() { diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 3cf28c919f07..69a6e6d998a4 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -535,6 +535,8 @@ applications that come with the platform <!-- Permission required for CTS test IntentRedirectionTest --> <permission name="android.permission.QUERY_CLONED_APPS"/> <permission name="android.permission.GET_BINDING_UID_IMPORTANCE"/> + <!-- Permission required for CTS test NotificationManagerZenTest --> + <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index b6a0c7bafa44..d12d9d665a8c 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -562,6 +562,9 @@ <!-- Permissions required for CTS test - NotificationManagerTest --> <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" /> + <!-- Permissions required for CTS test - NotificationManagerZenTest --> + <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> + <!-- Permissions required for CTS test - CtsContactsProviderTestCases --> <uses-permission android:name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" /> <uses-permission android:name="android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS" /> diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index e3aa161f001a..a313bcf1f7af 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -1745,8 +1745,8 @@ public final class ColorDisplayService extends SystemService { @Override public boolean setSaturationLevel(int level) { - final boolean hasTransformsPermission = getContext() - .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + final boolean hasTransformsPermission = getContext().checkCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) == PackageManager.PERMISSION_GRANTED; final boolean hasLegacyPermission = getContext() .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION) diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java new file mode 100644 index 000000000000..9fdeda4b4bd0 --- /dev/null +++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java @@ -0,0 +1,71 @@ +/* + * 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.notification; + +import android.app.UiModeManager; +import android.app.WallpaperManager; +import android.content.Context; +import android.hardware.display.ColorDisplayManager; +import android.os.Binder; +import android.os.PowerManager; +import android.service.notification.DeviceEffectsApplier; +import android.service.notification.ZenDeviceEffects; + +/** Default implementation for {@link DeviceEffectsApplier}. */ +class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { + + private static final String SUPPRESS_AMBIENT_DISPLAY_TOKEN = + "DefaultDeviceEffectsApplier:SuppressAmbientDisplay"; + private static final int SATURATION_LEVEL_GRAYSCALE = 0; + private static final int SATURATION_LEVEL_FULL_COLOR = 100; + private static final float WALLPAPER_DIM_AMOUNT_DIMMED = 0.6f; + private static final float WALLPAPER_DIM_AMOUNT_NORMAL = 0f; + + private final ColorDisplayManager mColorDisplayManager; + private final PowerManager mPowerManager; + private final UiModeManager mUiModeManager; + private final WallpaperManager mWallpaperManager; + + DefaultDeviceEffectsApplier(Context context) { + mColorDisplayManager = context.getSystemService(ColorDisplayManager.class); + mPowerManager = context.getSystemService(PowerManager.class); + mUiModeManager = context.getSystemService(UiModeManager.class); + mWallpaperManager = context.getSystemService(WallpaperManager.class); + } + + @Override + public void apply(ZenDeviceEffects effects) { + Binder.withCleanCallingIdentity(() -> { + mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN, + effects.shouldSuppressAmbientDisplay()); + + if (mColorDisplayManager != null) { + mColorDisplayManager.setSaturationLevel( + effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE + : SATURATION_LEVEL_FULL_COLOR); + } + + if (mWallpaperManager != null) { + mWallpaperManager.setWallpaperDimAmount( + effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED + : WALLPAPER_DIM_AMOUNT_NORMAL); + } + + // TODO: b/308673343 - Apply dark theme (via UiModeManager) when screen is off. + }); + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 3c6887c17e97..02845fb03119 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2941,6 +2941,12 @@ public class NotificationManagerService extends SystemService { registerDeviceConfigChange(); migrateDefaultNAS(); maybeShowInitialReviewPermissionsNotification(); + + if (android.app.Flags.modesApi()) { + // Cannot be done earlier, as some services aren't ready until this point. + mZenModeHelper.setDeviceEffectsApplier( + new DefaultDeviceEffectsApplier(getContext())); + } } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis()); } else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) { diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 5c37eeaba180..218519fef68b 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -27,8 +27,8 @@ import static android.service.notification.NotificationServiceProto.ROOT_CONFIG; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; -import android.annotation.IntDef; import android.annotation.DrawableRes; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -76,6 +76,7 @@ import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.Condition; import android.service.notification.ConditionProviderService; +import android.service.notification.DeviceEffectsApplier; import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ZenRule; @@ -175,6 +176,8 @@ public class ZenModeHelper { @VisibleForTesting protected int mZenMode; @VisibleForTesting protected NotificationManager.Policy mConsolidatedPolicy; + @GuardedBy("mConfigLock") + private ZenDeviceEffects mConsolidatedDeviceEffects = new ZenDeviceEffects.Builder().build(); private int mUser = UserHandle.USER_SYSTEM; private final Object mConfigLock = new Object(); @@ -182,6 +185,8 @@ public class ZenModeHelper { @VisibleForTesting protected ZenModeConfig mConfig; @VisibleForTesting protected AudioManagerInternal mAudioManager; protected PackageManager mPm; + @GuardedBy("mConfigLock") + private DeviceEffectsApplier mDeviceEffectsApplier; private long mSuppressedEffects; public static final long SUPPRESSED_EFFECT_NOTIFICATIONS = 1; @@ -189,7 +194,7 @@ public class ZenModeHelper { public static final long SUPPRESSED_EFFECT_ALL = SUPPRESSED_EFFECT_CALLS | SUPPRESSED_EFFECT_NOTIFICATIONS; - @VisibleForTesting protected boolean mIsBootComplete; + @VisibleForTesting protected boolean mIsSystemServicesReady; private String[] mPriorityOnlyDndExemptPackages; @@ -285,10 +290,33 @@ public class ZenModeHelper { mPm = mContext.getPackageManager(); mHandler.postMetricsTimer(); cleanUpZenRules(); - mIsBootComplete = true; + mIsSystemServicesReady = true; showZenUpgradeNotification(mZenMode); } + /** + * Set the {@link DeviceEffectsApplier} used to apply the consolidated effects. + * + * <p>If effects were calculated previously (for example, when we loaded a {@link ZenModeConfig} + * that includes activated rules), they will be applied immediately. + */ + void setDeviceEffectsApplier(@NonNull DeviceEffectsApplier deviceEffectsApplier) { + if (!Flags.modesApi()) { + return; + } + ZenDeviceEffects consolidatedDeviceEffects; + synchronized (mConfigLock) { + if (mDeviceEffectsApplier != null) { + throw new IllegalStateException("Already set up a DeviceEffectsApplier!"); + } + mDeviceEffectsApplier = deviceEffectsApplier; + consolidatedDeviceEffects = mConsolidatedDeviceEffects; + } + if (consolidatedDeviceEffects.hasEffects()) { + applyConsolidatedDeviceEffects(); + } + } + public void onUserSwitched(int user) { loadConfigForUser(user, "onUserSwitched"); } @@ -1349,7 +1377,7 @@ public class ZenModeHelper { mConfig = config; dispatchOnConfigChanged(); - updateConsolidatedPolicy(reason); + updateAndApplyConsolidatedPolicyAndDeviceEffects(reason); } final String val = Integer.toString(config.hashCode()); Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); @@ -1398,7 +1426,7 @@ public class ZenModeHelper { ZenLog.traceSetZenMode(zen, reason); mZenMode = zen; setZenModeSetting(mZenMode); - updateConsolidatedPolicy(reason); + updateAndApplyConsolidatedPolicyAndDeviceEffects(reason); boolean shouldApplyToRinger = setRingerMode && (zen != zenBefore || ( zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS && policyHashBefore != mConsolidatedPolicy.hashCode())); @@ -1459,25 +1487,56 @@ public class ZenModeHelper { } } - private void updateConsolidatedPolicy(String reason) { + private void updateAndApplyConsolidatedPolicyAndDeviceEffects(String reason) { synchronized (mConfigLock) { if (mConfig == null) return; ZenPolicy policy = new ZenPolicy(); + ZenDeviceEffects.Builder deviceEffectsBuilder = new ZenDeviceEffects.Builder(); if (mConfig.manualRule != null) { applyCustomPolicy(policy, mConfig.manualRule); + if (Flags.modesApi()) { + deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects); + } } for (ZenRule automaticRule : mConfig.automaticRules.values()) { if (automaticRule.isAutomaticActive()) { applyCustomPolicy(policy, automaticRule); + if (Flags.modesApi()) { + deviceEffectsBuilder.add(automaticRule.zenDeviceEffects); + } } } + Policy newPolicy = mConfig.toNotificationPolicy(policy); if (!Objects.equals(mConsolidatedPolicy, newPolicy)) { mConsolidatedPolicy = newPolicy; dispatchOnConsolidatedPolicyChanged(); ZenLog.traceSetConsolidatedZenPolicy(mConsolidatedPolicy, reason); } + + if (Flags.modesApi()) { + ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build(); + if (!deviceEffects.equals(mConsolidatedDeviceEffects)) { + mConsolidatedDeviceEffects = deviceEffects; + mHandler.postApplyDeviceEffects(); + } + } + } + } + + private void applyConsolidatedDeviceEffects() { + if (!Flags.modesApi()) { + return; + } + DeviceEffectsApplier applier; + ZenDeviceEffects effects; + synchronized (mConfigLock) { + applier = mDeviceEffectsApplier; + effects = mConsolidatedDeviceEffects; + } + if (applier != null) { + applier.apply(effects); } } @@ -1893,7 +1952,7 @@ public class ZenModeHelper { private void showZenUpgradeNotification(int zen) { final boolean isWatch = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WATCH); - final boolean showNotification = mIsBootComplete + final boolean showNotification = mIsSystemServicesReady && zen != Global.ZEN_MODE_OFF && !isWatch && Settings.Secure.getInt(mContext.getContentResolver(), @@ -2067,6 +2126,7 @@ public class ZenModeHelper { private static final int MSG_DISPATCH = 1; private static final int MSG_METRICS = 2; private static final int MSG_RINGER_AUDIO = 5; + private static final int MSG_APPLY_EFFECTS = 6; private static final long METRICS_PERIOD_MS = 6 * 60 * 60 * 1000; @@ -2089,6 +2149,11 @@ public class ZenModeHelper { sendMessage(obtainMessage(MSG_RINGER_AUDIO, shouldApplyToRinger)); } + private void postApplyDeviceEffects() { + removeMessages(MSG_APPLY_EFFECTS); + sendEmptyMessage(MSG_APPLY_EFFECTS); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -2101,6 +2166,10 @@ public class ZenModeHelper { case MSG_RINGER_AUDIO: boolean shouldApplyToRinger = (boolean) msg.obj; updateRingerAndAudio(shouldApplyToRinger); + break; + case MSG_APPLY_EFFECTS: + applyConsolidatedDeviceEffects(); + break; } } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java new file mode 100644 index 000000000000..5febd02a75a8 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java @@ -0,0 +1,115 @@ +/* + * 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.notification; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.app.UiModeManager; +import android.app.WallpaperManager; +import android.hardware.display.ColorDisplayManager; +import android.os.PowerManager; +import android.platform.test.flag.junit.SetFlagsRule; +import android.service.notification.ZenDeviceEffects; +import android.testing.TestableContext; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class DefaultDeviceEffectsApplierTest { + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private TestableContext mContext; + private DefaultDeviceEffectsApplier mApplier; + @Mock PowerManager mPowerManager; + @Mock ColorDisplayManager mColorDisplayManager; + @Mock UiModeManager mUiModeManager; + @Mock WallpaperManager mWallpaperManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = new TestableContext(InstrumentationRegistry.getContext(), null); + mContext.addMockSystemService(PowerManager.class, mPowerManager); + mContext.addMockSystemService(ColorDisplayManager.class, mColorDisplayManager); + mContext.addMockSystemService(UiModeManager.class, mUiModeManager); + mContext.addMockSystemService(WallpaperManager.class, mWallpaperManager); + + mApplier = new DefaultDeviceEffectsApplier(mContext); + } + + @Test + public void apply_appliesEffects() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + ZenDeviceEffects effects = new ZenDeviceEffects.Builder() + .setShouldSuppressAmbientDisplay(true) + .setShouldDimWallpaper(true) + .setShouldDisplayGrayscale(true) + .setShouldUseNightMode(true) + .build(); + mApplier.apply(effects); + + verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); + verify(mColorDisplayManager).setSaturationLevel(eq(0)); + verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); + verifyZeroInteractions(mUiModeManager); // Coming later; adding now so test fails then. :) + } + + @Test + public void apply_removesEffects() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build(); + mApplier.apply(noEffects); + + verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false)); + verify(mColorDisplayManager).setSaturationLevel(eq(100)); + verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f)); + verifyZeroInteractions(mUiModeManager); + } + + @Test + public void apply_missingSomeServices_okay() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mContext.addMockSystemService(ColorDisplayManager.class, null); + mContext.addMockSystemService(WallpaperManager.class, null); + + ZenDeviceEffects effects = new ZenDeviceEffects.Builder() + .setShouldSuppressAmbientDisplay(true) + .setShouldDimWallpaper(true) + .setShouldDisplayGrayscale(true) + .setShouldUseNightMode(true) + .build(); + mApplier.apply(effects); + + verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); + // (And no crash from missing services). + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index b1fdec911d86..ef6fcedf7339 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -77,9 +77,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.notNull; import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; @@ -112,6 +112,7 @@ import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.Condition; +import android.service.notification.DeviceEffectsApplier; import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ScheduleInfo; @@ -188,8 +189,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { .appendPath("test") .build(); - private static final Condition CONDITION = new Condition(CONDITION_ID, "", + private static final Condition CONDITION_TRUE = new Condition(CONDITION_ID, "", Condition.STATE_TRUE); + private static final Condition CONDITION_FALSE = new Condition(CONDITION_ID, "", + Condition.STATE_FALSE); private static final String TRIGGER_DESC = "Every Night, 10pm to 6am"; private static final int TYPE = TYPE_BEDTIME; private static final boolean ALLOW_MANUAL = true; @@ -201,6 +204,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { = NotificationManager.INTERRUPTION_FILTER_ALARMS; private static final boolean ENABLED = true; private static final int CREATION_TIME = 123; + private static final ZenDeviceEffects NO_EFFECTS = new ZenDeviceEffects.Builder().build(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -212,6 +216,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { private TestableLooper mTestableLooper; private ZenModeHelper mZenModeHelper; private ContentResolver mContentResolver; + @Mock DeviceEffectsApplier mDeviceEffectsApplier; @Mock AppOpsManager mAppOps; TestableFlagResolver mTestFlagResolver = new TestableFlagResolver(); ZenModeEventLoggerFake mZenModeEventLogger; @@ -238,8 +243,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { when(mPackageManager.getResourcesForApplication(anyString())).thenReturn( mResources); - when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOps); - when(mContext.getSystemService(NotificationManager.class)).thenReturn(mNotificationManager); + mContext.addMockSystemService(AppOpsManager.class, mAppOps); + mContext.addMockSystemService(NotificationManager.class, mNotificationManager); + mConditionProviders = new ConditionProviders(mContext, new UserProfiles(), AppGlobals.getPackageManager()); mConditionProviders.addSystemProvider(new CountdownConditionProvider()); @@ -609,7 +615,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // and we're setting zen mode on Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1); Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0); - mZenModeHelper.mIsBootComplete = true; + mZenModeHelper.mIsSystemServicesReady = true; mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS); @@ -624,7 +630,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // doesn't show upgrade notification if stored settings says don't show Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0); Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0); - mZenModeHelper.mIsBootComplete = true; + mZenModeHelper.mIsSystemServicesReady = true; mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS); verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG), @@ -636,7 +642,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // doesn't show upgrade notification since zen was already updated Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0); Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 1); - mZenModeHelper.mIsBootComplete = true; + mZenModeHelper.mIsSystemServicesReady = true; mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS); verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG), @@ -3060,7 +3066,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { rule.configurationActivity = CONFIG_ACTIVITY; rule.component = OWNER; rule.conditionId = CONDITION_ID; - rule.condition = CONDITION; + rule.condition = CONDITION_TRUE; rule.enabled = ENABLED; rule.creationTime = 123; rule.id = "id"; @@ -3350,6 +3356,84 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + public void testDeviceEffects_applied() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); + + ZenDeviceEffects effects = new ZenDeviceEffects.Builder() + .setShouldSuppressAmbientDisplay(true) + .setShouldDimWallpaper(true) + .build(); + String ruleId = addRuleWithEffects(effects); + verify(mDeviceEffectsApplier, never()).apply(any()); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false); + mTestableLooper.processAllMessages(); + + verify(mDeviceEffectsApplier).apply(eq(effects)); + } + + @Test + public void testDeviceEffects_onDeactivateRule_applied() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); + + ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(); + String ruleId = addRuleWithEffects(zde); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false); + mTestableLooper.processAllMessages(); + verify(mDeviceEffectsApplier).apply(eq(zde)); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, CUSTOM_PKG_UID, false); + mTestableLooper.processAllMessages(); + + verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS)); + } + + @Test + public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); + + ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(); + String ruleId = addRuleWithEffects(zde); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false); + mTestableLooper.processAllMessages(); + verify(mDeviceEffectsApplier).apply(eq(zde)); + + // Now create and activate a second rule that doesn't add any more effects. + String secondRuleId = addRuleWithEffects(zde); + mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, CUSTOM_PKG_UID, + false); + mTestableLooper.processAllMessages(); + + verifyNoMoreInteractions(mDeviceEffectsApplier); + } + + @Test + public void testDeviceEffects_activeBeforeApplierProvided_appliedWhenProvided() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(); + String ruleId = addRuleWithEffects(zde); + verify(mDeviceEffectsApplier, never()).apply(any()); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false); + mTestableLooper.processAllMessages(); + verify(mDeviceEffectsApplier, never()).apply(any()); + + mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); + verify(mDeviceEffectsApplier).apply(eq(zde)); + } + + private String addRuleWithEffects(ZenDeviceEffects effects) { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID) + .setDeviceEffects(effects) + .build(); + return mZenModeHelper.addAutomaticZenRule("pkg", rule, "", CUSTOM_PKG_UID, FROM_APP); + } + + @Test public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mZenModeHelper.mConfig.automaticRules.clear(); |