Add package filtering to NLSes

Now users can prevent a given notification listener from seeing
notifications from particular apps.

Test: settings unit tests
Bug: 173052211
Change-Id: Ia868d6dc2da1ae7f75c0dca47034e28910584acd
This commit is contained in:
Julia Reynolds
2021-02-05 18:30:52 -05:00
parent 7c041874ce
commit 20814ed5c7
9 changed files with 658 additions and 7 deletions

View File

@@ -8774,6 +8774,22 @@
<string name="notif_type_alerting">Alerting notifications</string> <string name="notif_type_alerting">Alerting notifications</string>
<string name="notif_type_silent">Silent notifications</string> <string name="notif_type_silent">Silent notifications</string>
<!-- Per notification listener, launches a list of apps whose notifications this listener cannot see -->
<string name="notif_listener_excluded_title">Apps that are not bridged to this listener</string>
<!-- Per notification listener, when the listener can see notifications from all apps -->
<string name="notif_listener_excluded_summary_zero">All apps are bridged</string>
<!-- Per notification listener, a summary of how many apps this listener cannot see
notifications from -->
<plurals name="notif_listener_excluded_summary_nonzero">
<item quantity="one">%d app is not bridged</item>
<item quantity="other">%d apps are not bridged</item>
</plurals>
<!-- Per notification listener, a list of apps whose notifications this listener cannot see -->
<string name="notif_listener_excluded_app_title">Bridged apps</string>
<!-- Title for managing VR (virtual reality) helper services. [CHAR LIMIT=50] --> <!-- Title for managing VR (virtual reality) helper services. [CHAR LIMIT=50] -->
<string name="vr_listeners_title">VR helper services</string> <string name="vr_listeners_title">VR helper services</string>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="nonbridged_apps"
android:title="@string/notif_listener_excluded_app_title"
settings:controller="com.android.settings.applications.specialaccess.notificationaccess.BridgedAppsPreferenceController"
settings:searchable="false">
</PreferenceScreen>

View File

@@ -41,11 +41,11 @@
style="@style/SettingsMultiSelectListPreference" style="@style/SettingsMultiSelectListPreference"
settings:controller="com.android.settings.applications.specialaccess.notificationaccess.TypeFilterPreferenceController"/>/> settings:controller="com.android.settings.applications.specialaccess.notificationaccess.TypeFilterPreferenceController"/>/>
<PreferenceCategory <Preference
android:key="advanced" android:key="bridged_apps"
android:order="50" android:title="@string/notif_listener_excluded_app_title"
settings:initialExpandedChildrenCount="0"> android:fragment="com.android.settings.applications.specialaccess.notificationaccess.BridgedAppsSettings"
settings:searchable="false"
settings:controller="com.android.settings.applications.specialaccess.notificationaccess.BridgedAppsPreferenceController" />
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -0,0 +1,218 @@
/*
* Copyright (C) 2021 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.settings.applications.specialaccess.notificationaccess;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.VersionedPackage;
import android.os.UserHandle;
import android.service.notification.NotificationListenerFilter;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.applications.AppStateBaseBridge;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.AppFilter;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
public class BridgedAppsPreferenceController extends BasePreferenceController implements
LifecycleObserver, ApplicationsState.Callbacks,
AppStateBaseBridge.Callback {
private ApplicationsState mApplicationsState;
private ApplicationsState.Session mSession;
private AppFilter mFilter;
private PreferenceScreen mScreen;
private ComponentName mCn;
private int mUserId;
private NotificationBackend mNm;
private NotificationListenerFilter mNlf;
public BridgedAppsPreferenceController(Context context, String key) {
super(context, key);
}
public BridgedAppsPreferenceController setAppState(ApplicationsState appState) {
mApplicationsState = appState;
return this;
}
public BridgedAppsPreferenceController setCn(ComponentName cn) {
mCn = cn;
return this;
}
public BridgedAppsPreferenceController setUserId(int userId) {
mUserId = userId;
return this;
}
public BridgedAppsPreferenceController setNm(NotificationBackend nm) {
mNm = nm;
return this;
}
public BridgedAppsPreferenceController setFilter(AppFilter filter) {
mFilter = filter;
return this;
}
public BridgedAppsPreferenceController setSession(Lifecycle lifecycle) {
mSession = mApplicationsState.newSession(this, lifecycle);
return this;
}
@Override
public void displayPreference(PreferenceScreen screen) {
mScreen = screen;
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void onExtraInfoUpdated() {
rebuild();
}
@Override
public void onRunningStateChanged(boolean running) {
}
@Override
public void onPackageListChanged() {
rebuild();
}
@Override
public void onRebuildComplete(ArrayList<AppEntry> apps) {
if (apps == null) {
return;
}
mNlf = mNm.getListenerFilter(mCn, mUserId);
// Create apps key set for removing useless preferences
final Set<String> appsKeySet = new TreeSet<>();
// Add or update preferences
final int N = apps.size();
for (int i = 0; i < N; i++) {
final AppEntry entry = apps.get(i);
if (!shouldAddPreference(entry)) {
continue;
}
final String prefKey = entry.info.packageName + "|" + entry.info.uid;
appsKeySet.add(prefKey);
SwitchPreference preference = mScreen.findPreference(prefKey);
if (preference == null) {
preference = new SwitchPreference(mScreen.getContext());
preference.setIcon(entry.icon);
preference.setTitle(entry.label);
preference.setKey(prefKey);
mScreen.addPreference(preference);
}
preference.setOrder(i);
preference.setChecked(mNlf.isPackageAllowed(
new VersionedPackage(entry.info.packageName, entry.info.uid)));
preference.setOnPreferenceChangeListener(this::onPreferenceChange);
}
// Remove preferences that are no longer existing in the updated list of apps
removeUselessPrefs(appsKeySet);
}
@Override
public void onPackageIconChanged() {
rebuild();
}
@Override
public void onPackageSizeChanged(String packageName) {
}
@Override
public void onAllSizesComputed() {
}
@Override
public void onLauncherInfoChanged() {
}
@Override
public void onLoadEntriesCompleted() {
rebuild();
}
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference instanceof SwitchPreference) {
String packageName = preference.getKey().substring(0, preference.getKey().indexOf("|"));
int uid = Integer.parseInt(preference.getKey().substring(
preference.getKey().indexOf("|") + 1));
boolean allowlisted = newValue == Boolean.TRUE;
mNlf = mNm.getListenerFilter(mCn, mUserId);
if (allowlisted) {
mNlf.removePackage(new VersionedPackage(packageName, uid));
} else {
mNlf.addPackage(new VersionedPackage(packageName, uid));
}
mNm.setListenerFilter(mCn, mUserId, mNlf);
return true;
}
return false;
}
public void rebuild() {
final ArrayList<AppEntry> apps = mSession.rebuild(mFilter,
ApplicationsState.ALPHA_COMPARATOR);
if (apps != null) {
onRebuildComplete(apps);
}
}
private void removeUselessPrefs(final Set<String> appsKeySet) {
final int prefCount = mScreen.getPreferenceCount();
String prefKey;
if (prefCount > 0) {
for (int i = prefCount - 1; i >= 0; i--) {
Preference pref = mScreen.getPreference(i);
prefKey = pref.getKey();
if (!appsKeySet.contains(prefKey)) {
mScreen.removePreference(pref);
}
}
}
}
@VisibleForTesting
static boolean shouldAddPreference(AppEntry app) {
return app != null && UserHandle.isApp(app.info.uid);
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2021 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.settings.applications.specialaccess.notificationaccess;
import static com.android.settings.applications.AppInfoBase.ARG_PACKAGE_NAME;
import android.app.Application;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppFilter;
public class BridgedAppsSettings extends DashboardFragment {
private static final String TAG = "BridgedAppsSettings";
private static final int MENU_SHOW_SYSTEM = Menu.FIRST + 42;
private static final String EXTRA_SHOW_SYSTEM = "show_system";
private boolean mShowSystem;
private AppFilter mFilter;
private int mUserId;
private ComponentName mComponentName;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mShowSystem = icicle != null && icicle.getBoolean(EXTRA_SHOW_SYSTEM);
use(BridgedAppsPreferenceController.class).setNm(new NotificationBackend());
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE,
mShowSystem ? R.string.menu_hide_system : R.string.menu_show_system);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_SHOW_SYSTEM:
mShowSystem = !mShowSystem;
item.setTitle(mShowSystem ? R.string.menu_hide_system : R.string.menu_show_system);
mFilter = mShowSystem ? ApplicationsState.FILTER_ALL_ENABLED
: ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER;
use(BridgedAppsPreferenceController.class).setFilter(mFilter).rebuild();
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mFilter = mShowSystem ? ApplicationsState.FILTER_ALL_ENABLED
: ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER;
final Bundle args = getArguments();
Intent intent = (args == null) ?
getIntent() : (Intent) args.getParcelable("intent");
String cn = args.getString(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME);
if (cn != null) {
mComponentName = ComponentName.unflattenFromString(cn);
}
if (intent != null && intent.hasExtra(Intent.EXTRA_USER_HANDLE)) {
mUserId = ((UserHandle) intent.getParcelableExtra(
Intent.EXTRA_USER_HANDLE)).getIdentifier();
} else {
mUserId = UserHandle.myUserId();
}
use(BridgedAppsPreferenceController.class)
.setAppState(ApplicationsState.getInstance(
(Application) context.getApplicationContext()))
.setCn(mComponentName)
.setUserId(mUserId)
.setSession(getSettingsLifecycle())
.setFilter(mFilter)
.rebuild();
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.NOTIFICATION_ACCESS_BRIDGED_APPS;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.notification_access_bridged_apps_settings;
}
}

View File

@@ -32,15 +32,19 @@ import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.provider.Settings; import android.provider.Settings;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService;
import android.util.Log; import android.util.Log;
import android.util.Slog; import android.util.Slog;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.applications.manageapplications.ManageApplications;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.notification.NotificationBackend; import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils;
@@ -52,6 +56,8 @@ import java.util.Objects;
public class NotificationAccessDetails extends DashboardFragment { public class NotificationAccessDetails extends DashboardFragment {
private static final String TAG = "NotifAccessDetails"; private static final String TAG = "NotifAccessDetails";
private NotificationBackend mNm = new NotificationBackend();
private NotificationListenerFilter mNlf;
private ComponentName mComponentName; private ComponentName mComponentName;
private CharSequence mServiceName; private CharSequence mServiceName;
protected PackageInfo mPackageInfo; protected PackageInfo mPackageInfo;
@@ -131,6 +137,33 @@ public class NotificationAccessDetails extends DashboardFragment {
if (!refreshUi()) { if (!refreshUi()) {
setIntentAndFinish(true /* appChanged */); setIntentAndFinish(true /* appChanged */);
} }
Preference apps = getPreferenceScreen().findPreference(
use(BridgedAppsPreferenceController.class).getPreferenceKey());
if (apps != null) {
mNlf = mNm.getListenerFilter(mComponentName, mUserId);
int nonBridgedCount = mNlf.getDisallowedPackages().size();
apps.setSummary(nonBridgedCount == 0 ?
getString(R.string.notif_listener_excluded_summary_zero)
: getResources().getQuantityString(
R.plurals.notif_listener_excluded_summary_nonzero,
nonBridgedCount, nonBridgedCount));
apps.setOnPreferenceClickListener(preference -> {
final Bundle args = new Bundle();
args.putString(AppInfoBase.ARG_PACKAGE_NAME, mPackageName);
args.putString(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME,
mComponentName.flattenToString());
new SubSettingLauncher(getContext())
.setDestination(BridgedAppsSettings.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.setTitleRes(R.string.notif_listener_excluded_app_title)
.setArguments(args)
.setUserHandle(UserHandle.of(mUserId))
.launch();
return true;
});
}
} }
protected void setIntentAndFinish(boolean appChanged) { protected void setIntentAndFinish(boolean appChanged) {

View File

@@ -49,6 +49,7 @@ import android.service.notification.NotificationListenerFilter;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.IconDrawableFactory; import android.util.IconDrawableFactory;
import android.util.Log; import android.util.Log;
import android.util.Slog;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;

View File

@@ -28,7 +28,7 @@
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application> <application android:debuggable="true">
<uses-library android:name="android.test.runner" /> <uses-library android:name="android.test.runner" />
<activity android:name="com.android.settings.tests.BluetoothRequestPermissionTest" <activity android:name="com.android.settings.tests.BluetoothRequestPermissionTest"
android:exported="true" android:exported="true"

View File

@@ -0,0 +1,221 @@
/*
* Copyright (C) 2021 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.settings.applications.specialaccess.notificationaccess;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.VersionedPackage;
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.service.notification.NotificationListenerFilter;
import android.util.ArraySet;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.applications.ApplicationsState;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@RunWith(AndroidJUnit4.class)
public class BridgedAppsPreferenceControllerTest {
private Context mContext;
private BridgedAppsPreferenceController mController;
@Mock
NotificationBackend mNm;
ComponentName mCn = new ComponentName("a", "b");
PreferenceScreen mScreen;
@Mock
ApplicationsState mAppState;
private ApplicationsState.AppEntry mAppEntry;
private ApplicationsState.AppEntry mAppEntry2;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = ApplicationProvider.getApplicationContext();
if (Looper.myLooper() == null) {
Looper.prepare();
}
PreferenceManager preferenceManager = new PreferenceManager(mContext);
mScreen = preferenceManager.createPreferenceScreen(mContext);
ApplicationInfo ai = new ApplicationInfo();
ai.packageName = "pkg";
ai.uid = 12300;
ai.sourceDir = "";
ApplicationInfo ai2 = new ApplicationInfo();
ai2.packageName = "another";
ai2.uid = 18800;
ai2.sourceDir = "";
mAppEntry = new ApplicationsState.AppEntry(mContext, ai, 0);
mAppEntry2 = new ApplicationsState.AppEntry(mContext, ai2, 1);
mAppEntry.info = ai;
mAppEntry.label = "hi";
Drawable icon = mock(Drawable.class);
mAppEntry.icon = icon;
mController = new BridgedAppsPreferenceController(mContext, "key");
mController.setCn(mCn);
mController.setNm(mNm);
mController.setUserId(0);
mController.setAppState(mAppState);
mController.displayPreference(mScreen);
}
@Test
public void onRebuildComplete_AddsToScreen() {
when(mNm.isNotificationListenerAccessGranted(mCn)).thenReturn(true);
when(mNm.getListenerFilter(mCn, 0)).thenReturn(new NotificationListenerFilter());
ArrayList<ApplicationsState.AppEntry> entries = new ArrayList<>();
entries.add(mAppEntry);
entries.add(mAppEntry2);
mController.onRebuildComplete(entries);
assertThat(mScreen.getPreferenceCount()).isEqualTo(2);
}
@Test
public void onRebuildComplete_doesNotReaddToScreen() {
when(mNm.isNotificationListenerAccessGranted(mCn)).thenReturn(true);
when(mNm.getListenerFilter(mCn, 0)).thenReturn(new NotificationListenerFilter());
SwitchPreference p = mock(SwitchPreference.class);
when(p.getKey()).thenReturn("pkg|12300");
mScreen.addPreference(p);
ArrayList<ApplicationsState.AppEntry> entries = new ArrayList<>();
entries.add(mAppEntry);
entries.add(mAppEntry2);
mController.onRebuildComplete(entries);
assertThat(mScreen.getPreferenceCount()).isEqualTo(2);
}
@Test
public void onRebuildComplete_removesExtras() {
when(mNm.isNotificationListenerAccessGranted(mCn)).thenReturn(true);
when(mNm.getListenerFilter(mCn, 0)).thenReturn(new NotificationListenerFilter());
Preference p = mock(Preference.class);
when(p.getKey()).thenReturn("pkg|123");
mScreen.addPreference(p);
ArrayList<ApplicationsState.AppEntry> entries = new ArrayList<>();
entries.add(mAppEntry);
entries.add(mAppEntry2);
mController.onRebuildComplete(entries);
assertThat((Preference) mScreen.findPreference("pkg|123")).isNull();
}
@Test
public void onRebuildComplete_buildsSetting() {
when(mNm.isNotificationListenerAccessGranted(mCn)).thenReturn(true);
when(mNm.getListenerFilter(mCn, 0)).thenReturn(new NotificationListenerFilter());
ArrayList<ApplicationsState.AppEntry> entries = new ArrayList<>();
entries.add(mAppEntry);
mController.onRebuildComplete(entries);
SwitchPreference actual = mScreen.findPreference("pkg|12300");
assertThat(actual.isChecked()).isTrue();
assertThat(actual.getTitle()).isEqualTo("hi");
assertThat(actual.getIcon()).isEqualTo(mAppEntry.icon);
}
@Test
public void onPreferenceChange_false() {
VersionedPackage vp = new VersionedPackage("pkg", 10567);
ArraySet<VersionedPackage> vps = new ArraySet<>();
vps.add(vp);
NotificationListenerFilter nlf = new NotificationListenerFilter(FLAG_FILTER_TYPE_ONGOING
| FLAG_FILTER_TYPE_CONVERSATIONS, vps);
when(mNm.isNotificationListenerAccessGranted(mCn)).thenReturn(true);
when(mNm.getListenerFilter(mCn, 0)).thenReturn(nlf);
SwitchPreference pref = new SwitchPreference(mContext);
pref.setKey("pkg|567");
mController.onPreferenceChange(pref, false);
ArgumentCaptor<NotificationListenerFilter> captor =
ArgumentCaptor.forClass(NotificationListenerFilter.class);
verify(mNm).setListenerFilter(eq(mCn), eq(0), captor.capture());
assertThat(captor.getValue().getDisallowedPackages()).contains(
new VersionedPackage("pkg", 567));
assertThat(captor.getValue().getDisallowedPackages()).contains(
new VersionedPackage("pkg", 10567));
}
@Test
public void onPreferenceChange_true() {
VersionedPackage vp = new VersionedPackage("pkg", 567);
VersionedPackage vp2 = new VersionedPackage("pkg", 10567);
ArraySet<VersionedPackage> vps = new ArraySet<>();
vps.add(vp);
vps.add(vp2);
NotificationListenerFilter nlf = new NotificationListenerFilter(FLAG_FILTER_TYPE_ONGOING
| FLAG_FILTER_TYPE_CONVERSATIONS, vps);
when(mNm.isNotificationListenerAccessGranted(mCn)).thenReturn(true);
when(mNm.getListenerFilter(mCn, 0)).thenReturn(nlf);
SwitchPreference pref = new SwitchPreference(mContext);
pref.setKey("pkg|567");
mController.onPreferenceChange(pref, true);
ArgumentCaptor<NotificationListenerFilter> captor =
ArgumentCaptor.forClass(NotificationListenerFilter.class);
verify(mNm).setListenerFilter(eq(mCn), eq(0), captor.capture());
assertThat(captor.getValue().getDisallowedPackages().size()).isEqualTo(1);
assertThat(captor.getValue().getDisallowedPackages()).contains(
new VersionedPackage("pkg", 10567));
}
}