Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b8129578 authored by Matías Hernández's avatar Matías Hernández Committed by Android (Google) Code Review
Browse files

Merge "Avoid mixups between different CPSes in ZenModeConditions" into tm-dev

parents eaf6e716 5a51348f
Loading
Loading
Loading
Loading
+8 −1
Original line number Original line Diff line number Diff line
@@ -282,6 +282,13 @@ public class ConditionProviders extends ManagedServices {
        return rt;
        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) {
    private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) {
        if (id == null || component == null) return null;
        if (id == null || component == null) return null;
        final int N = mRecords.size();
        final int N = mRecords.size();
@@ -432,7 +439,7 @@ public class ConditionProviders extends ManagedServices {
        return info == null ? null : (IConditionProvider) info.service;
        return info == null ? null : (IConditionProvider) info.service;
    }
    }


    private static class ConditionRecord {
    static class ConditionRecord {
        public final Uri id;
        public final Uri id;
        public final ComponentName component;
        public final ComponentName component;
        public Condition condition;
        public Condition condition;
+34 −18
Original line number Original line Diff line number Diff line
@@ -22,9 +22,10 @@ import android.service.notification.Condition;
import android.service.notification.IConditionProvider;
import android.service.notification.IConditionProvider;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ZenRule;
import android.service.notification.ZenModeConfig.ZenRule;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.Log;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;


@@ -42,7 +43,7 @@ public class ZenModeConditions implements ConditionProviders.Callback {
    private final ConditionProviders mConditionProviders;
    private final ConditionProviders mConditionProviders;


    @VisibleForTesting
    @VisibleForTesting
    protected final ArrayMap<Uri, ComponentName> mSubscriptions = new ArrayMap<>();
    protected final ArraySet<Pair<Uri, ComponentName>> mSubscriptions = new ArraySet<>();


    public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) {
    public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) {
        mHelper = helper;
        mHelper = helper;
@@ -71,11 +72,11 @@ public class ZenModeConditions implements ConditionProviders.Callback {
            if (DEBUG) Log.d(TAG, "evaluateConfig: clearing manual rule");
            if (DEBUG) Log.d(TAG, "evaluateConfig: clearing manual rule");
            config.manualRule = null;
            config.manualRule = null;
        }
        }
        final ArraySet<Uri> current = new ArraySet<>();
        final ArraySet<Pair<Uri, ComponentName>> current = new ArraySet<>();
        evaluateRule(config.manualRule, current, null, processSubscriptions);
        evaluateRule(config.manualRule, current, null, processSubscriptions, true);
        for (ZenRule automaticRule : config.automaticRules.values()) {
        for (ZenRule automaticRule : config.automaticRules.values()) {
            if (automaticRule.component != null) {
            if (automaticRule.component != null) {
                evaluateRule(automaticRule, current, trigger, processSubscriptions);
                evaluateRule(automaticRule, current, trigger, processSubscriptions, false);
                updateSnoozing(automaticRule);
                updateSnoozing(automaticRule);
            }
            }
        }
        }
@@ -83,11 +84,11 @@ public class ZenModeConditions implements ConditionProviders.Callback {
        synchronized (mSubscriptions) {
        synchronized (mSubscriptions) {
            final int N = mSubscriptions.size();
            final int N = mSubscriptions.size();
            for (int i = N - 1; i >= 0; i--) {
            for (int i = N - 1; i >= 0; i--) {
                final Uri id = mSubscriptions.keyAt(i);
                final Pair<Uri, ComponentName> subscription = mSubscriptions.valueAt(i);
                final ComponentName component = mSubscriptions.valueAt(i);
                if (processSubscriptions) {
                if (processSubscriptions) {
                    if (!current.contains(id)) {
                    if (!current.contains(subscription)) {
                        mConditionProviders.unsubscribeIfNecessary(component, id);
                        mConditionProviders.unsubscribeIfNecessary(subscription.second,
                                subscription.first);
                        mSubscriptions.removeAt(i);
                        mSubscriptions.removeAt(i);
                    }
                    }
                }
                }
@@ -120,12 +121,23 @@ public class ZenModeConditions implements ConditionProviders.Callback {
    }
    }


    // Only valid for CPS backed rules
    // Only valid for CPS backed rules
    private void evaluateRule(ZenRule rule, ArraySet<Uri> current, ComponentName trigger,
    private void evaluateRule(ZenRule rule, ArraySet<Pair<Uri, ComponentName>> current,
            boolean processSubscriptions) {
            ComponentName trigger, boolean processSubscriptions, boolean isManual) {
        if (rule == null || rule.conditionId == null) return;
        if (rule == null || rule.conditionId == null) return;
        if (rule.configurationActivity != null) return;
        if (rule.configurationActivity != null) return;
        final Uri id = rule.conditionId;
        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;
        boolean isSystemCondition = false;
        if (isSystemRule) {
            for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) {
            for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) {
                if (sp.isValidConditionId(id)) {
                if (sp.isValidConditionId(id)) {
                    mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface());
                    mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface());
@@ -133,6 +145,8 @@ public class ZenModeConditions implements ConditionProviders.Callback {
                    isSystemCondition = true;
                    isSystemCondition = true;
                }
                }
            }
            }
        }

        // ensure that we have a record of the rule if it's backed by an currently alive CPS
        // ensure that we have a record of the rule if it's backed by an currently alive CPS
        if (!isSystemCondition) {
        if (!isSystemCondition) {
            final IConditionProvider cp = mConditionProviders.findConditionProvider(rule.component);
            final IConditionProvider cp = mConditionProviders.findConditionProvider(rule.component);
@@ -147,8 +161,10 @@ public class ZenModeConditions implements ConditionProviders.Callback {
            rule.enabled = false;
            rule.enabled = false;
            return;
            return;
        }
        }

        Pair<Uri, ComponentName> uriAndCps = new Pair<>(id, rule.component);
        if (current != null) {
        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
        // If the rule is bound by a CPS and the CPS is alive, tell them about the rule
@@ -157,7 +173,7 @@ public class ZenModeConditions implements ConditionProviders.Callback {
            if (DEBUG) Log.d(TAG, "Subscribing to " + rule.component);
            if (DEBUG) Log.d(TAG, "Subscribing to " + rule.component);
            if (mConditionProviders.subscribeIfNecessary(rule.component, rule.conditionId)) {
            if (mConditionProviders.subscribeIfNecessary(rule.component, rule.conditionId)) {
                synchronized (mSubscriptions) {
                synchronized (mSubscriptions) {
                    mSubscriptions.put(rule.conditionId, rule.component);
                    mSubscriptions.add(uriAndCps);
                }
                }
            } else {
            } else {
                rule.condition = null;
                rule.condition = null;
+163 −0
Original line number Original line Diff line number Diff line
/*
 * 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) { }
    }
}
+30 −12
Original line number Original line Diff line number Diff line
@@ -68,8 +68,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;


import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppGlobals;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.AutomaticZenRule;
@@ -78,6 +76,7 @@ import android.app.NotificationManager.Policy;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Context;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ResolveInfo;
@@ -89,13 +88,15 @@ import android.media.AudioManagerInternal;
import android.media.AudioSystem;
import android.media.AudioSystem;
import android.media.VolumePolicy;
import android.media.VolumePolicy;
import android.net.Uri;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Process;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.DNDModeProto;
import android.service.notification.DNDModeProto;
import android.service.notification.IConditionProvider;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ScheduleInfo;
import android.service.notification.ZenModeConfig.ScheduleInfo;
import android.service.notification.ZenPolicy;
import android.service.notification.ZenPolicy;
@@ -1825,16 +1826,21 @@ public class ZenModeHelperTest extends UiServiceTestCase {


    @Test
    @Test
    public void testRulesWithSameUri() {
    public void testRulesWithSameUri() {
        Uri sharedUri = ZenModeConfig.toScheduleConditionId(new ScheduleInfo());
        // Needs a "valid" CPS otherwise ZenModeConditions will balk and clear rule.condition
        AutomaticZenRule zenRule = new AutomaticZenRule("name",
        ComponentName packageCpsName = new ComponentName(mContext,
                new ComponentName("android", "ScheduleConditionProvider"),
                PackageConditionProviderService.class);
                sharedUri,
        ConditionProviderService packageCps = new PackageConditionProviderService();
                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
        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, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
        String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
        String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
        AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
        AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", packageCpsName,
                new ComponentName("android", "ScheduleConditionProvider"),
                sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
                sharedUri,
                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
        String id2 = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule2, "test");
        String id2 = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule2, "test");


        Condition condition = new Condition(sharedUri, "", STATE_TRUE);
        Condition condition = new Condition(sharedUri, "", STATE_TRUE);
@@ -2182,4 +2188,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
            return parser.nextTag();
            return parser.nextTag();
        }
        }
    }
    }

    private static class PackageConditionProviderService extends ConditionProviderService {

        @Override
        public void onConnected() { }

        @Override
        public void onSubscribe(Uri conditionId) { }

        @Override
        public void onUnsubscribe(Uri conditionId) { }
    }
}
}