summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/notification/ConditionProviders.java9
-rw-r--r--services/core/java/com/android/server/notification/ZenModeConditions.java48
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeConditionsTest.java163
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java43
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;