diff options
4 files changed, 233 insertions, 30 deletions
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index 3f2c2228e453..ddea0ed7c453 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -290,6 +290,13 @@ public class ConditionProviders extends ManagedServices { return rt; } + @VisibleForTesting + ConditionRecord getRecord(Uri id, ComponentName component) { + synchronized (mMutex) { + return getRecordLocked(id, component, false); + } + } + private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) { if (id == null || component == null) return null; final int N = mRecords.size(); @@ -492,7 +499,7 @@ public class ConditionProviders extends ManagedServices { return removed; } - private static class ConditionRecord { + static class ConditionRecord { public final Uri id; public final ComponentName component; public Condition condition; diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java index d44baeb58a28..250be7a3f69c 100644 --- a/services/core/java/com/android/server/notification/ZenModeConditions.java +++ b/services/core/java/com/android/server/notification/ZenModeConditions.java @@ -25,9 +25,10 @@ import android.service.notification.Condition; import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ZenRule; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -45,7 +46,7 @@ public class ZenModeConditions implements ConditionProviders.Callback { private final ConditionProviders mConditionProviders; @VisibleForTesting - protected final ArrayMap<Uri, ComponentName> mSubscriptions = new ArrayMap<>(); + protected final ArraySet<Pair<Uri, ComponentName>> mSubscriptions = new ArraySet<>(); public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) { mHelper = helper; @@ -78,7 +79,7 @@ public class ZenModeConditions implements ConditionProviders.Callback { if (DEBUG) Log.d(TAG, "evaluateConfig: clearing manual rule"); config.manualRule = null; } - final ArraySet<Uri> current = new ArraySet<>(); + final ArraySet<Pair<Uri, ComponentName>> current = new ArraySet<>(); evaluateRule(config.manualRule, current, null, processSubscriptions, true); for (ZenRule automaticRule : config.automaticRules.values()) { if (automaticRule.component != null) { @@ -90,11 +91,11 @@ public class ZenModeConditions implements ConditionProviders.Callback { synchronized (mSubscriptions) { final int N = mSubscriptions.size(); for (int i = N - 1; i >= 0; i--) { - final Uri id = mSubscriptions.keyAt(i); - final ComponentName component = mSubscriptions.valueAt(i); + final Pair<Uri, ComponentName> subscription = mSubscriptions.valueAt(i); if (processSubscriptions) { - if (!current.contains(id)) { - mConditionProviders.unsubscribeIfNecessary(component, id); + if (!current.contains(subscription)) { + mConditionProviders.unsubscribeIfNecessary(subscription.second, + subscription.first); mSubscriptions.removeAt(i); } } @@ -131,19 +132,32 @@ public class ZenModeConditions implements ConditionProviders.Callback { } // Only valid for CPS backed rules - private void evaluateRule(ZenRule rule, ArraySet<Uri> current, ComponentName trigger, - boolean processSubscriptions, boolean isManual) { + private void evaluateRule(ZenRule rule, ArraySet<Pair<Uri, ComponentName>> current, + ComponentName trigger, boolean processSubscriptions, boolean isManual) { if (rule == null || rule.conditionId == null) return; if (rule.configurationActivity != null) return; final Uri id = rule.conditionId; + boolean isSystemRule = isManual || ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getPkg()); + + if (!isSystemRule + && rule.component != null + && ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.component.getPackageName())) { + Slog.w(TAG, "Rule " + rule.id + " belongs to package " + rule.getPkg() + + " but has component=" + rule.component + " which is not allowed!"); + return; + } + boolean isSystemCondition = false; - for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) { - if (sp.isValidConditionId(id)) { - mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface()); - rule.component = sp.getComponent(); - isSystemCondition = true; + if (isSystemRule) { + for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) { + if (sp.isValidConditionId(id)) { + mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface()); + rule.component = sp.getComponent(); + isSystemCondition = true; + } } } + // ensure that we have a record of the rule if it's backed by an currently alive CPS if (!isSystemCondition) { final IConditionProvider cp = mConditionProviders.findConditionProvider(rule.component); @@ -160,8 +174,10 @@ public class ZenModeConditions implements ConditionProviders.Callback { } return; } + + Pair<Uri, ComponentName> uriAndCps = new Pair<>(id, rule.component); if (current != null) { - current.add(id); + current.add(uriAndCps); } // If the rule is bound by a CPS and the CPS is alive, tell them about the rule @@ -170,7 +186,7 @@ public class ZenModeConditions implements ConditionProviders.Callback { if (DEBUG) Log.d(TAG, "Subscribing to " + rule.component); if (mConditionProviders.subscribeIfNecessary(rule.component, rule.conditionId)) { synchronized (mSubscriptions) { - mSubscriptions.put(rule.conditionId, rule.component); + mSubscriptions.add(uriAndCps); } } else { rule.condition = null; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConditionsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConditionsTest.java new file mode 100644 index 000000000000..a53b7e011a80 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConditionsTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2025 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 com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; + +import android.content.ComponentName; +import android.content.ServiceConnection; +import android.content.pm.IPackageManager; +import android.net.Uri; +import android.os.Build; +import android.service.notification.ConditionProviderService; +import android.service.notification.IConditionProvider; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ZenRule; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.server.UiServiceTestCase; +import com.android.server.notification.ConditionProviders.ConditionRecord; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Set; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class ZenModeConditionsTest extends UiServiceTestCase { + + private static final ComponentName SCHEDULE_CPS = new ComponentName("android", + "com.android.server.notification.ScheduleConditionProvider"); + private static final Uri SCHEDULE_CPS_CONDITION_ID = Uri.parse( + "condition://android/schedule?days=1.2.3&start=3.0&end=5.0&exitAtAlarm=true"); + + private static final String PACKAGE = "com.some.package"; + private static final ComponentName PACKAGE_CPS = new ComponentName(PACKAGE, + PACKAGE + ".TheConditionProviderService"); + + private ZenModeConditions mZenModeConditions; + private ConditionProviders mConditionProviders; + + @Before + public void setUp() { + mConditionProviders = new ConditionProviders(mContext, new ManagedServices.UserProfiles(), + mock(IPackageManager.class)); + mZenModeConditions = new ZenModeConditions(mock(ZenModeHelper.class), mConditionProviders); + ((Set<?>) mConditionProviders.getSystemProviders()).clear(); // Hack, remove built-in CPSes + + ScheduleConditionProvider scheduleConditionProvider = new ScheduleConditionProvider(); + mConditionProviders.addSystemProvider(scheduleConditionProvider); + + ConditionProviderService packageConditionProvider = new PackageConditionProviderService(); + mConditionProviders.registerGuestService(mConditionProviders.new ManagedServiceInfo( + (IConditionProvider) packageConditionProvider.onBind(null), PACKAGE_CPS, + mContext.getUserId(), false, mock(ServiceConnection.class), + Build.VERSION_CODES.TIRAMISU, 44)); + } + + @Test + public void evaluateRule_systemRuleWithSystemConditionProvider_evaluates() { + ZenRule systemRule = newSystemZenRule("1", SCHEDULE_CPS, SCHEDULE_CPS_CONDITION_ID); + ZenModeConfig config = configWithRules(systemRule); + + mZenModeConditions.evaluateConfig(config, null, /* processSubscriptions= */ true); + + ConditionRecord conditionRecord = mConditionProviders.getRecord(SCHEDULE_CPS_CONDITION_ID, + SCHEDULE_CPS); + assertThat(conditionRecord).isNotNull(); + assertThat(conditionRecord.subscribed).isTrue(); + } + + @Test + public void evaluateConfig_packageRuleWithSystemConditionProvider_ignored() { + ZenRule packageRule = newPackageZenRule(PACKAGE, SCHEDULE_CPS, SCHEDULE_CPS_CONDITION_ID); + ZenModeConfig config = configWithRules(packageRule); + + mZenModeConditions.evaluateConfig(config, null, /* processSubscriptions= */ true); + + assertThat(mConditionProviders.getRecord(SCHEDULE_CPS_CONDITION_ID, SCHEDULE_CPS)) + .isNull(); + } + + @Test + public void evaluateConfig_packageRuleWithPackageCpsButSystemLikeConditionId_usesPackageCps() { + ZenRule packageRule = newPackageZenRule(PACKAGE, PACKAGE_CPS, + SCHEDULE_CPS_CONDITION_ID); + ZenModeConfig config = configWithRules(packageRule); + + mZenModeConditions.evaluateConfig(config, /* trigger= */ PACKAGE_CPS, + /* processSubscriptions= */ true); + + ConditionRecord packageCpsRecord = mConditionProviders.getRecord(SCHEDULE_CPS_CONDITION_ID, + PACKAGE_CPS); + assertThat(packageCpsRecord).isNotNull(); + assertThat(packageCpsRecord.subscribed).isTrue(); + + ConditionRecord systemCpsRecord = mConditionProviders.getRecord(SCHEDULE_CPS_CONDITION_ID, + SCHEDULE_CPS); + assertThat(systemCpsRecord).isNull(); + } + + private static ZenModeConfig configWithRules(ZenRule... zenRules) { + ZenModeConfig config = new ZenModeConfig(); + for (ZenRule zenRule : zenRules) { + config.automaticRules.put(zenRule.id, zenRule); + } + return config; + } + + private static ZenRule newSystemZenRule(String id, ComponentName component, Uri conditionId) { + ZenRule systemRule = new ZenRule(); + systemRule.id = id; + systemRule.name = "System Rule " + id; + systemRule.pkg = ZenModeConfig.SYSTEM_AUTHORITY; + systemRule.component = component; + systemRule.conditionId = conditionId; + return systemRule; + } + + private static ZenRule newPackageZenRule(String packageName, ComponentName component, + Uri conditionId) { + ZenRule packageRule = new ZenRule(); + packageRule.id = "id " + packageName; + packageRule.name = "Package Rule " + packageName; + packageRule.pkg = packageName; + packageRule.component = component; + packageRule.conditionId = conditionId; + return packageRule; + } + + private static class PackageConditionProviderService extends ConditionProviderService { + + @Override + public void onConnected() { } + + @Override + public void onSubscribe(Uri conditionId) { } + + @Override + public void onUnsubscribe(Uri conditionId) { } + } +} 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 bfce647356ec..3c5d08a9d3de 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -142,6 +142,7 @@ import android.app.backup.BackupRestoreEventLogger; import android.app.compat.CompatChanges; import android.content.ComponentName; import android.content.Context; +import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -155,6 +156,7 @@ import android.media.AudioManagerInternal; import android.media.AudioSystem; import android.media.VolumePolicy; import android.net.Uri; +import android.os.Build; import android.os.Parcel; import android.os.SimpleClock; import android.os.UserHandle; @@ -165,7 +167,9 @@ 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.ConditionProviderService; import android.service.notification.DeviceEffectsApplier; +import android.service.notification.IConditionProvider; import android.service.notification.SystemZenRules; import android.service.notification.ZenAdapters; import android.service.notification.ZenDeviceEffects; @@ -2704,21 +2708,22 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testRulesWithSameUri() { - // needs to be a valid schedule info object for the subscription to happen properly - ScheduleInfo scheduleInfo = new ScheduleInfo(); - scheduleInfo.days = new int[]{1, 2}; - scheduleInfo.endHour = 1; - Uri sharedUri = ZenModeConfig.toScheduleConditionId(scheduleInfo); - AutomaticZenRule zenRule = new AutomaticZenRule("name", - new ComponentName(mPkg, "ScheduleConditionProvider"), - sharedUri, - NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + // Needs a "valid" CPS otherwise ZenModeConditions will balk and clear rule.condition + ComponentName packageCpsName = new ComponentName(mContext, + PackageConditionProviderService.class); + ConditionProviderService packageCps = new PackageConditionProviderService(); + mConditionProviders.registerGuestService(mConditionProviders.new ManagedServiceInfo( + (IConditionProvider) packageCps.onBind(null), packageCpsName, + mContext.getUserId(), false, mock(ServiceConnection.class), + Build.VERSION_CODES.TIRAMISU, 44)); + Uri sharedUri = Uri.parse("packageConditionId"); + + AutomaticZenRule zenRule = new AutomaticZenRule("name", packageCpsName, + sharedUri, INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID); - AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", - new ComponentName(mPkg, "ScheduleConditionProvider"), - sharedUri, - NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", packageCpsName, + sharedUri, INTERRUPTION_FILTER_PRIORITY, true); String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, zenRule2, ORIGIN_SYSTEM, "test", SYSTEM_UID); @@ -7914,6 +7919,18 @@ public class ZenModeHelperTest extends UiServiceTestCase { } } + private static class PackageConditionProviderService extends ConditionProviderService { + + @Override + public void onConnected() { } + + @Override + public void onSubscribe(Uri conditionId) { } + + @Override + public void onUnsubscribe(Uri conditionId) { } + } + private static class TestClock extends SimpleClock { private long mNowMillis = 441644400000L; |