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

Commit af7deaba authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Make smart suggestion generation configurable"

parents 31d0756c 22b8d9eb
Loading
Loading
Loading
Loading
+28 −63
Original line number Original line Diff line number Diff line
@@ -27,20 +27,15 @@ import android.app.ActivityThread;
import android.app.INotificationManager;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannel;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageManager;
import android.database.ContentObserver;
import android.ext.services.notification.AgingHelper.Callback;
import android.ext.services.notification.AgingHelper.Callback;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Bundle;
import android.os.Environment;
import android.os.Environment;
import android.os.Handler;
import android.os.SystemProperties;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Adjustment;
import android.service.notification.NotificationAssistantService;
import android.service.notification.NotificationAssistantService;
import android.service.notification.NotificationStats;
import android.service.notification.NotificationStats;
@@ -92,8 +87,6 @@ public class Assistant extends NotificationAssistantService {
        PREJUDICAL_DISMISSALS.add(REASON_LISTENER_CANCEL);
        PREJUDICAL_DISMISSALS.add(REASON_LISTENER_CANCEL);
    }
    }


    private float mDismissToViewRatioLimit;
    private int mStreakLimit;
    private SmartActionsHelper mSmartActionsHelper;
    private SmartActionsHelper mSmartActionsHelper;
    private NotificationCategorizer mNotificationCategorizer;
    private NotificationCategorizer mNotificationCategorizer;
    private AgingHelper mAgingHelper;
    private AgingHelper mAgingHelper;
@@ -107,7 +100,11 @@ public class Assistant extends NotificationAssistantService {
    private Ranking mFakeRanking = null;
    private Ranking mFakeRanking = null;
    private AtomicFile mFile = null;
    private AtomicFile mFile = null;
    private IPackageManager mPackageManager;
    private IPackageManager mPackageManager;
    protected SettingsObserver mSettingsObserver;

    @VisibleForTesting
    protected AssistantSettings.Factory mSettingsFactory = AssistantSettings.FACTORY;
    @VisibleForTesting
    protected AssistantSettings mSettings;


    public Assistant() {
    public Assistant() {
    }
    }
@@ -118,7 +115,8 @@ public class Assistant extends NotificationAssistantService {
        // Contexts are correctly hooked up by the creation step, which is required for the observer
        // Contexts are correctly hooked up by the creation step, which is required for the observer
        // to be hooked up/initialized.
        // to be hooked up/initialized.
        mPackageManager = ActivityThread.getPackageManager();
        mPackageManager = ActivityThread.getPackageManager();
        mSettingsObserver = new SettingsObserver(mHandler);
        mSettings = mSettingsFactory.createAndRegister(mHandler,
                getApplicationContext().getContentResolver(), getUserId(), this::updateThresholds);
        mSmartActionsHelper = new SmartActionsHelper();
        mSmartActionsHelper = new SmartActionsHelper();
        mNotificationCategorizer = new NotificationCategorizer();
        mNotificationCategorizer = new NotificationCategorizer();
        mAgingHelper = new AgingHelper(getContext(),
        mAgingHelper = new AgingHelper(getContext(),
@@ -216,11 +214,11 @@ public class Assistant extends NotificationAssistantService {
        if (!isForCurrentUser(sbn)) {
        if (!isForCurrentUser(sbn)) {
            return null;
            return null;
        }
        }
        NotificationEntry entry = new NotificationEntry(
        NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, channel);
                ActivityThread.getPackageManager(), sbn, channel);
        ArrayList<Notification.Action> actions =
        ArrayList<Notification.Action> actions =
                mSmartActionsHelper.suggestActions(this, entry);
                mSmartActionsHelper.suggestActions(this, entry, mSettings);
        ArrayList<CharSequence> replies = mSmartActionsHelper.suggestReplies(this, entry);
        ArrayList<CharSequence> replies =
                mSmartActionsHelper.suggestReplies(this, entry, mSettings);
        return createEnqueuedNotificationAdjustment(entry, actions, replies);
        return createEnqueuedNotificationAdjustment(entry, actions, replies);
    }
    }


@@ -239,8 +237,7 @@ public class Assistant extends NotificationAssistantService {
        if (!smartReplies.isEmpty()) {
        if (!smartReplies.isEmpty()) {
            signals.putCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES, smartReplies);
            signals.putCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES, smartReplies);
        }
        }
        if (Settings.Secure.getInt(getContentResolver(),
        if (mSettings.mNewInterruptionModel) {
                Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1) == 1) {
            if (mNotificationCategorizer.shouldSilence(entry)) {
            if (mNotificationCategorizer.shouldSilence(entry)) {
                final int importance = entry.getImportance() < IMPORTANCE_LOW
                final int importance = entry.getImportance() < IMPORTANCE_LOW
                        ? entry.getImportance() : IMPORTANCE_LOW;
                        ? entry.getImportance() : IMPORTANCE_LOW;
@@ -459,6 +456,11 @@ public class Assistant extends NotificationAssistantService {
        mPackageManager = pm;
        mPackageManager = pm;
    }
    }


    @VisibleForTesting
    public void setSmartActionsHelper(SmartActionsHelper smartActionsHelper) {
        mSmartActionsHelper = smartActionsHelper;
    }

    @VisibleForTesting
    @VisibleForTesting
    public ChannelImpressions getImpressions(String key) {
    public ChannelImpressions getImpressions(String key) {
        synchronized (mkeyToImpressions) {
        synchronized (mkeyToImpressions) {
@@ -475,10 +477,20 @@ public class Assistant extends NotificationAssistantService {


    private ChannelImpressions createChannelImpressionsWithThresholds() {
    private ChannelImpressions createChannelImpressionsWithThresholds() {
        ChannelImpressions impressions = new ChannelImpressions();
        ChannelImpressions impressions = new ChannelImpressions();
        impressions.updateThresholds(mDismissToViewRatioLimit, mStreakLimit);
        impressions.updateThresholds(mSettings.mDismissToViewRatioLimit, mSettings.mStreakLimit);
        return impressions;
        return impressions;
    }
    }


    private void updateThresholds() {
        // Update all existing channel impression objects with any new limits/thresholds.
        synchronized (mkeyToImpressions) {
            for (ChannelImpressions channelImpressions: mkeyToImpressions.values()) {
                channelImpressions.updateThresholds(
                        mSettings.mDismissToViewRatioLimit, mSettings.mStreakLimit);
            }
        }
    }

    protected final class AgingCallback implements Callback {
    protected final class AgingCallback implements Callback {
        @Override
        @Override
        public void sendAdjustment(String key, int newImportance) {
        public void sendAdjustment(String key, int newImportance) {
@@ -495,51 +507,4 @@ public class Assistant extends NotificationAssistantService {
        }
        }
    }
    }


    /**
     * Observer for updates on blocking helper threshold values.
     */
    protected final class SettingsObserver extends ContentObserver {
        private final Uri STREAK_LIMIT_URI =
                Settings.Global.getUriFor(Settings.Global.BLOCKING_HELPER_STREAK_LIMIT);
        private final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI =
                Settings.Global.getUriFor(
                        Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT);

        public SettingsObserver(Handler handler) {
            super(handler);
            ContentResolver resolver = getApplicationContext().getContentResolver();
            resolver.registerContentObserver(
                    DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, getUserId());
            resolver.registerContentObserver(STREAK_LIMIT_URI, false, this, getUserId());

            // Update all uris on creation.
            update(null);
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            update(uri);
        }

        private void update(Uri uri) {
            ContentResolver resolver = getApplicationContext().getContentResolver();
            if (uri == null || DISMISS_TO_VIEW_RATIO_LIMIT_URI.equals(uri)) {
                mDismissToViewRatioLimit = Settings.Global.getFloat(
                        resolver, Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
                        ChannelImpressions.DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT);
            }
            if (uri == null || STREAK_LIMIT_URI.equals(uri)) {
                mStreakLimit = Settings.Global.getInt(
                        resolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
                        ChannelImpressions.DEFAULT_STREAK_LIMIT);
            }

            // Update all existing channel impression objects with any new limits/thresholds.
            synchronized (mkeyToImpressions) {
                for (ChannelImpressions channelImpressions: mkeyToImpressions.values()) {
                    channelImpressions.updateThresholds(mDismissToViewRatioLimit, mStreakLimit);
                }
            }
        }
    }
}
}
+140 −0
Original line number Original line Diff line number Diff line
/**
 * Copyright (C) 2018 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.ext.services.notification;

import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import android.util.KeyValueListParser;

import com.android.internal.annotations.VisibleForTesting;

/**
 * Observes the settings for {@link Assistant}.
 */
final class AssistantSettings extends ContentObserver {
    public static Factory FACTORY = AssistantSettings::createAndRegister;
    private static final boolean DEFAULT_GENERATE_REPLIES = true;
    private static final boolean DEFAULT_GENERATE_ACTIONS = true;
    private static final int DEFAULT_NEW_INTERRUPTION_MODEL_INT = 1;

    private static final Uri STREAK_LIMIT_URI =
            Settings.Global.getUriFor(Settings.Global.BLOCKING_HELPER_STREAK_LIMIT);
    private static final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI =
            Settings.Global.getUriFor(
                    Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT);
    private static final Uri SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI =
            Settings.Global.getUriFor(
                    Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS);
    private static final Uri NOTIFICATION_NEW_INTERRUPTION_MODEL_URI =
            Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL);

    private static final String KEY_GENERATE_REPLIES = "generate_replies";
    private static final String KEY_GENERATE_ACTIONS = "generate_actions";

    private final KeyValueListParser mParser = new KeyValueListParser(',');
    private final ContentResolver mResolver;
    private final int mUserId;

    @VisibleForTesting
    protected final Runnable mOnUpdateRunnable;

    // Actuall configuration settings.
    float mDismissToViewRatioLimit;
    int mStreakLimit;
    boolean mGenerateReplies = DEFAULT_GENERATE_REPLIES;
    boolean mGenerateActions = DEFAULT_GENERATE_ACTIONS;
    boolean mNewInterruptionModel;

    private AssistantSettings(Handler handler, ContentResolver resolver, int userId,
            Runnable onUpdateRunnable) {
        super(handler);
        mResolver = resolver;
        mUserId = userId;
        mOnUpdateRunnable = onUpdateRunnable;
    }

    private static AssistantSettings createAndRegister(
            Handler handler, ContentResolver resolver, int userId, Runnable onUpdateRunnable) {
        AssistantSettings assistantSettings =
                new AssistantSettings(handler, resolver, userId, onUpdateRunnable);
        assistantSettings.register();
        return assistantSettings;
    }

    /**
     * Creates an instance but doesn't register it as an observer.
     */
    @VisibleForTesting
    protected static AssistantSettings createForTesting(
            Handler handler, ContentResolver resolver, int userId, Runnable onUpdateRunnable) {
        return new AssistantSettings(handler, resolver, userId, onUpdateRunnable);
    }

    private void register() {
        mResolver.registerContentObserver(
                DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, mUserId);
        mResolver.registerContentObserver(STREAK_LIMIT_URI, false, this, mUserId);
        mResolver.registerContentObserver(
                SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI, false, this, mUserId);

        // Update all uris on creation.
        update(null);
    }

    @Override
    public void onChange(boolean selfChange, Uri uri) {
        update(uri);
    }

    private void update(Uri uri) {
        if (uri == null || DISMISS_TO_VIEW_RATIO_LIMIT_URI.equals(uri)) {
            mDismissToViewRatioLimit = Settings.Global.getFloat(
                    mResolver, Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
                    ChannelImpressions.DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT);
        }
        if (uri == null || STREAK_LIMIT_URI.equals(uri)) {
            mStreakLimit = Settings.Global.getInt(
                    mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
                    ChannelImpressions.DEFAULT_STREAK_LIMIT);
        }
        if (uri == null || SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI.equals(uri)) {
            mParser.setString(
                    Settings.Global.getString(mResolver,
                            Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
            mGenerateReplies =
                    mParser.getBoolean(KEY_GENERATE_REPLIES, DEFAULT_GENERATE_REPLIES);
            mGenerateActions =
                    mParser.getBoolean(KEY_GENERATE_ACTIONS, DEFAULT_GENERATE_ACTIONS);
        }
        if (uri == null || NOTIFICATION_NEW_INTERRUPTION_MODEL_URI.equals(uri)) {
            int mNewInterruptionModelInt = Settings.Secure.getInt(
                    mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL,
                    DEFAULT_NEW_INTERRUPTION_MODEL_INT);
            mNewInterruptionModel = mNewInterruptionModelInt == 1;
        }

        mOnUpdateRunnable.run();
    }

    public interface Factory {
        AssistantSettings createAndRegister(Handler handler, ContentResolver resolver, int userId,
                Runnable onUpdateRunnable);
    }
}
+10 −4
Original line number Original line Diff line number Diff line
@@ -69,8 +69,11 @@ public class SmartActionsHelper {
     * from notification text / message, we can replace most of the code here by consuming that API.
     * from notification text / message, we can replace most of the code here by consuming that API.
     */
     */
    @NonNull
    @NonNull
    ArrayList<Notification.Action> suggestActions(
    ArrayList<Notification.Action> suggestActions(@Nullable Context context,
            @Nullable Context context, @NonNull NotificationEntry entry) {
            @NonNull NotificationEntry entry, @NonNull AssistantSettings settings) {
        if (!settings.mGenerateActions) {
            return EMPTY_ACTION_LIST;
        }
        if (!isEligibleForActionAdjustment(entry)) {
        if (!isEligibleForActionAdjustment(entry)) {
            return EMPTY_ACTION_LIST;
            return EMPTY_ACTION_LIST;
        }
        }
@@ -86,8 +89,11 @@ public class SmartActionsHelper {
                getMostSalientActionText(entry.getNotification()), MAX_SMART_ACTIONS);
                getMostSalientActionText(entry.getNotification()), MAX_SMART_ACTIONS);
    }
    }


    ArrayList<CharSequence> suggestReplies(
    ArrayList<CharSequence> suggestReplies(@Nullable Context context,
            @Nullable Context context, @NonNull NotificationEntry entry) {
            @NonNull NotificationEntry entry, @NonNull AssistantSettings settings) {
        if (!settings.mGenerateReplies) {
            return EMPTY_REPLY_LIST;
        }
        if (!isEligibleForReplyAdjustment(entry)) {
        if (!isEligibleForReplyAdjustment(entry)) {
            return EMPTY_REPLY_LIST;
            return EMPTY_REPLY_LIST;
        }
        }
+162 −0
Original line number Original line Diff line number Diff line
/**
 * Copyright (C) 2018 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.ext.services.notification;

import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.content.ContentResolver;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.testing.TestableContext;

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 AssistantSettingsTest {
    private static final int USER_ID = 5;

    @Rule
    public final TestableContext mContext =
            new TestableContext(InstrumentationRegistry.getContext(), null);

    @Mock Runnable mOnUpdateRunnable;

    private ContentResolver mResolver;
    private AssistantSettings mAssistantSettings;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mResolver = mContext.getContentResolver();
        Handler handler = new Handler(Looper.getMainLooper());

        // To bypass real calls to global settings values, set the Settings values here.
        Settings.Global.putFloat(mResolver,
                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f);
        Settings.Global.putInt(mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2);
        Settings.Global.putString(mResolver,
                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
                "generate_replies=true,generate_actions=true");
        Settings.Secure.putInt(mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1);

        mAssistantSettings = AssistantSettings.createForTesting(
                handler, mResolver, USER_ID, mOnUpdateRunnable);
    }

    @Test
    public void testGenerateRepliesDisabled() {
        Settings.Global.putString(mResolver,
                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
                "generate_replies=false");

        // Notify for the settings values we updated.
        mAssistantSettings.onChange(false,
                Settings.Global.getUriFor(
                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));


        assertFalse(mAssistantSettings.mGenerateReplies);
    }

    @Test
    public void testGenerateRepliesEnabled() {
        Settings.Global.putString(mResolver,
                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_replies=true");

        // Notify for the settings values we updated.
        mAssistantSettings.onChange(false,
                Settings.Global.getUriFor(
                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));

        assertTrue(mAssistantSettings.mGenerateReplies);
    }

    @Test
    public void testGenerateActionsDisabled() {
        Settings.Global.putString(mResolver,
                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=false");

        // Notify for the settings values we updated.
        mAssistantSettings.onChange(false,
                Settings.Global.getUriFor(
                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));

        assertTrue(mAssistantSettings.mGenerateReplies);
    }

    @Test
    public void testGenerateActionsEnabled() {
        Settings.Global.putString(mResolver,
                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=true");

        // Notify for the settings values we updated.
        mAssistantSettings.onChange(false,
                Settings.Global.getUriFor(
                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));

        assertTrue(mAssistantSettings.mGenerateReplies);
    }

    @Test
    public void testStreakLimit() {
        verify(mOnUpdateRunnable, never()).run();

        // Update settings value.
        int newStreakLimit = 4;
        Settings.Global.putInt(mResolver,
                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, newStreakLimit);

        // Notify for the settings value we updated.
        mAssistantSettings.onChange(false, Settings.Global.getUriFor(
                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT));

        assertEquals(newStreakLimit, mAssistantSettings.mStreakLimit);
        verify(mOnUpdateRunnable).run();
    }

    @Test
    public void testDismissToViewRatioLimit() {
        verify(mOnUpdateRunnable, never()).run();

        // Update settings value.
        float newDismissToViewRatioLimit = 3f;
        Settings.Global.putFloat(mResolver,
                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
                newDismissToViewRatioLimit);

        // Notify for the settings value we updated.
        mAssistantSettings.onChange(false, Settings.Global.getUriFor(
                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT));

        assertEquals(newDismissToViewRatioLimit, mAssistantSettings.mDismissToViewRatioLimit, 1e-6);
        verify(mOnUpdateRunnable).run();
    }
}
+18 −23
Original line number Original line Diff line number Diff line
@@ -33,13 +33,11 @@ import android.app.Application;
import android.app.INotificationManager;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannel;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageManager;
import android.os.Build;
import android.os.Build;
import android.os.UserHandle;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Adjustment;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.Ranking;
@@ -86,8 +84,7 @@ public class AssistantTest extends ServiceTestCase<Assistant> {


    @Mock INotificationManager mNoMan;
    @Mock INotificationManager mNoMan;
    @Mock AtomicFile mFile;
    @Mock AtomicFile mFile;
    @Mock
    @Mock IPackageManager mPackageManager;
    IPackageManager mPackageManager;


    Assistant mAssistant;
    Assistant mAssistant;
    Application mApplication;
    Application mApplication;
@@ -108,20 +105,26 @@ public class AssistantTest extends ServiceTestCase<Assistant> {
                new Intent("android.service.notification.NotificationAssistantService");
                new Intent("android.service.notification.NotificationAssistantService");
        startIntent.setPackage("android.ext.services");
        startIntent.setPackage("android.ext.services");


        // To bypass real calls to global settings values, set the Settings values here.
        Settings.Global.putFloat(mContext.getContentResolver(),
                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f);
        Settings.Global.putInt(mContext.getContentResolver(),
                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2);
        mApplication = (Application) InstrumentationRegistry.getInstrumentation().
        mApplication = (Application) InstrumentationRegistry.getInstrumentation().
                getTargetContext().getApplicationContext();
                getTargetContext().getApplicationContext();
        // Force the test to use the correct application instead of trying to use a mock application
        // Force the test to use the correct application instead of trying to use a mock application
        setApplication(mApplication);
        setApplication(mApplication);
        bindService(startIntent);

        setupService();
        mAssistant = getService();
        mAssistant = getService();

        // Override the AssistantSettings factory.
        mAssistant.mSettingsFactory = AssistantSettings::createForTesting;

        bindService(startIntent);

        mAssistant.mSettings.mDismissToViewRatioLimit = 0.8f;
        mAssistant.mSettings.mStreakLimit = 2;
        mAssistant.mSettings.mNewInterruptionModel = true;
        mAssistant.setNoMan(mNoMan);
        mAssistant.setNoMan(mNoMan);
        mAssistant.setFile(mFile);
        mAssistant.setFile(mFile);
        mAssistant.setPackageManager(mPackageManager);
        mAssistant.setPackageManager(mPackageManager);

        ApplicationInfo info = mock(ApplicationInfo.class);
        ApplicationInfo info = mock(ApplicationInfo.class);
        when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt()))
        when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt()))
                .thenReturn(info);
                .thenReturn(info);
@@ -408,6 +411,8 @@ public class AssistantTest extends ServiceTestCase<Assistant> {
        mAssistant.writeXml(serializer);
        mAssistant.writeXml(serializer);


        Assistant assistant = new Assistant();
        Assistant assistant = new Assistant();
        // onCreate is not invoked, so settings won't be initialised, unless we do it here.
        assistant.mSettings = mAssistant.mSettings;
        assistant.readXml(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())));
        assistant.readXml(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())));


        assertEquals(ci1, assistant.getImpressions(key1));
        assertEquals(ci1, assistant.getImpressions(key1));
@@ -417,8 +422,6 @@ public class AssistantTest extends ServiceTestCase<Assistant> {


    @Test
    @Test
    public void testSettingsProviderUpdate() {
    public void testSettingsProviderUpdate() {
        ContentResolver resolver = mApplication.getContentResolver();

        // Set up channels
        // Set up channels
        String key = mAssistant.getKey("pkg1", 1, "channel1");
        String key = mAssistant.getKey("pkg1", 1, "channel1");
        ChannelImpressions ci = new ChannelImpressions();
        ChannelImpressions ci = new ChannelImpressions();
@@ -435,19 +438,11 @@ public class AssistantTest extends ServiceTestCase<Assistant> {
        assertEquals(false, ci.shouldTriggerBlock());
        assertEquals(false, ci.shouldTriggerBlock());


        // Update settings values.
        // Update settings values.
        float newDismissToViewRatioLimit = 0f;
        mAssistant.mSettings.mDismissToViewRatioLimit = 0f;
        int newStreakLimit = 0;
        mAssistant.mSettings.mStreakLimit = 0;
        Settings.Global.putFloat(resolver,
                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
                newDismissToViewRatioLimit);
        Settings.Global.putInt(resolver,
                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, newStreakLimit);


        // Notify for the settings values we updated.
        // Notify for the settings values we updated.
        mAssistant.mSettingsObserver.onChange(false, Settings.Global.getUriFor(
        mAssistant.mSettings.mOnUpdateRunnable.run();
                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT));
        mAssistant.mSettingsObserver.onChange(false, Settings.Global.getUriFor(
                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT));


        // With the new threshold, the blocking helper should be triggered.
        // With the new threshold, the blocking helper should be triggered.
        assertEquals(true, ci.shouldTriggerBlock());
        assertEquals(true, ci.shouldTriggerBlock());