Update NLS listing page

- Organized by allowed/not allowed
- Include paired devices if there are any

Test: settings robotests
Fixes: 181125174
Change-Id: Id64ee0ebd9b40a92d54a03d92fec3ff0bb3b926d
This commit is contained in:
Julia Reynolds
2021-03-04 10:26:42 -05:00
parent ef6646458b
commit ddf1958bdc
6 changed files with 160 additions and 179 deletions

View File

@@ -21,4 +21,13 @@
android:key="notification_access_screen" android:key="notification_access_screen"
android:title="@string/manage_notification_access_title" android:title="@string/manage_notification_access_title"
settings:searchable="false" settings:searchable="false"
settings:controller="com.android.settings.applications.specialaccess.notificationaccess.NotificationAccessScreenPreferenceController" /> settings:controller="com.android.settings.applications.specialaccess.notificationaccess.NotificationAccessScreenPreferenceController">
<PreferenceCategory
android:key="allowed"
android:title="@string/app_permission_summary_allowed"/>
<PreferenceCategory
android:key="not_allowed"
android:title="@string/app_permission_summary_not_allowed"/>
</PreferenceScreen>

View File

@@ -21,9 +21,7 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.util.IconDrawableFactory; import android.util.IconDrawableFactory;
import android.util.Log;
import android.view.View; import android.view.View;
import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleObserver;
@@ -34,17 +32,13 @@ import com.android.settings.R;
import com.android.settings.core.BasePreferenceController; import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.widget.EntityHeaderController; import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.LayoutPreference;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
public class HeaderPreferenceController extends BasePreferenceController public class HeaderPreferenceController extends BasePreferenceController
implements PreferenceControllerMixin, LifecycleObserver { implements PreferenceControllerMixin, LifecycleObserver {
@@ -122,7 +116,8 @@ public class HeaderPreferenceController extends BasePreferenceController
.getBadgedIcon(mPackageInfo.applicationInfo)) .getBadgedIcon(mPackageInfo.applicationInfo))
.setLabel(mPackageInfo.applicationInfo.loadLabel(mPm)) .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm))
.setSummary(mServiceName) .setSummary(mServiceName)
.setSecondSummary(getDeviceList()) .setSecondSummary(new NotificationBackend().getDeviceList(
mCdm, mBm, mCn.getPackageName(), mUserId))
.setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo)) .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo))
.setPackageName(mPackageInfo.packageName) .setPackageName(mPackageInfo.packageName)
.setUid(mPackageInfo.applicationInfo.uid) .setUid(mPackageInfo.applicationInfo.uid)
@@ -139,33 +134,4 @@ public class HeaderPreferenceController extends BasePreferenceController
mHeaderController.styleActionBar(mFragment.getActivity()); mHeaderController.styleActionBar(mFragment.getActivity());
} }
} }
protected CharSequence getDeviceList() {
boolean multiple = false;
StringBuilder sb = new StringBuilder();
try {
List<String> associatedMacAddrs =
mCdm.getAssociations(mCn.getPackageName(), mUserId);
if (associatedMacAddrs != null) {
for (String assocMac : associatedMacAddrs) {
final Collection<CachedBluetoothDevice> cachedDevices =
mBm.getCachedDeviceManager().getCachedDevicesCopy();
for (CachedBluetoothDevice cachedBluetoothDevice : cachedDevices) {
if (Objects.equals(assocMac, cachedBluetoothDevice.getAddress())) {
if (multiple) {
sb.append(", ");
} else {
multiple = true;
}
sb.append(cachedBluetoothDevice.getName());
}
}
}
}
} catch (RemoteException e) {
Log.w(TAG, "Error calling CDM", e);
}
return sb.toString();
}
} }

View File

@@ -20,12 +20,14 @@ import android.annotation.Nullable;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.companion.ICompanionDeviceManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageItemInfo; import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo; import android.content.pm.ServiceInfo;
import android.os.Bundle; import android.os.Bundle;
import android.os.ServiceManager;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.provider.Settings; import android.provider.Settings;
@@ -35,6 +37,7 @@ import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Toast; import android.widget.Toast;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import com.android.settings.R; import com.android.settings.R;
@@ -57,6 +60,9 @@ import java.util.List;
@SearchIndexable @SearchIndexable
public class NotificationAccessSettings extends EmptyTextSettings { public class NotificationAccessSettings extends EmptyTextSettings {
private static final String TAG = "NotifAccessSettings"; private static final String TAG = "NotifAccessSettings";
private static final String ALLOWED_KEY = "allowed";
private static final String NOT_ALLOWED_KEY = "not_allowed";
private static final ManagedServiceSettings.Config CONFIG = private static final ManagedServiceSettings.Config CONFIG =
new ManagedServiceSettings.Config.Builder() new ManagedServiceSettings.Config.Builder()
.setTag(TAG) .setTag(TAG)
@@ -76,6 +82,7 @@ public class NotificationAccessSettings extends EmptyTextSettings {
private DevicePolicyManager mDpm; private DevicePolicyManager mDpm;
private ServiceListing mServiceListing; private ServiceListing mServiceListing;
private IconDrawableFactory mIconDrawableFactory; private IconDrawableFactory mIconDrawableFactory;
private NotificationBackend mBackend = new NotificationBackend();
@Override @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
@@ -93,7 +100,6 @@ public class NotificationAccessSettings extends EmptyTextSettings {
.setTag(CONFIG.tag) .setTag(CONFIG.tag)
.build(); .build();
mServiceListing.addCallback(this::updateList); mServiceListing.addCallback(this::updateList);
setPreferenceScreen(getPreferenceManager().createPreferenceScreen(mContext));
if (UserManager.get(mContext).isManagedProfile()) { if (UserManager.get(mContext).isManagedProfile()) {
// Apps in the work profile do not support notification listeners. // Apps in the work profile do not support notification listeners.
@@ -127,7 +133,11 @@ public class NotificationAccessSettings extends EmptyTextSettings {
final int managedProfileId = Utils.getManagedProfileId(um, UserHandle.myUserId()); final int managedProfileId = Utils.getManagedProfileId(um, UserHandle.myUserId());
final PreferenceScreen screen = getPreferenceScreen(); final PreferenceScreen screen = getPreferenceScreen();
screen.removeAll(); final PreferenceCategory allowedCategory = screen.findPreference(ALLOWED_KEY);
allowedCategory.removeAll();
final PreferenceCategory notAllowedCategory = screen.findPreference(NOT_ALLOWED_KEY);
notAllowedCategory.removeAll();
services.sort(new PackageItemInfo.DisplayNameComparator(mPm)); services.sort(new PackageItemInfo.DisplayNameComparator(mPm));
for (ServiceInfo service : services) { for (ServiceInfo service : services) {
final ComponentName cn = new ComponentName(service.packageName, service.name); final ComponentName cn = new ComponentName(service.packageName, service.name);
@@ -145,9 +155,11 @@ public class NotificationAccessSettings extends EmptyTextSettings {
pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo, pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo,
UserHandle.getUserId(service.applicationInfo.uid))); UserHandle.getUserId(service.applicationInfo.uid)));
pref.setKey(cn.flattenToString()); pref.setKey(cn.flattenToString());
pref.setSummary(mNm.isNotificationListenerAccessGranted(cn) pref.setSummary(mBackend.getDeviceList(ICompanionDeviceManager.Stub.asInterface(
? R.string.app_permission_summary_allowed ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE)),
: R.string.app_permission_summary_not_allowed); com.android.settings.bluetooth.Utils.getLocalBtManager(mContext),
service.packageName,
UserHandle.myUserId()));
if (managedProfileId != UserHandle.USER_NULL if (managedProfileId != UserHandle.USER_NULL
&& !mDpm.isNotificationListenerServicePermitted( && !mDpm.isNotificationListenerServicePermitted(
service.packageName, managedProfileId)) { service.packageName, managedProfileId)) {
@@ -173,7 +185,11 @@ public class NotificationAccessSettings extends EmptyTextSettings {
return true; return true;
}); });
pref.setKey(cn.flattenToString()); pref.setKey(cn.flattenToString());
screen.addPreference(pref); if (mNm.isNotificationListenerAccessGranted(cn)) {
allowedCategory.addPreference(pref);
} else {
notAllowedCategory.addPreference(pref);
}
} }
highlightPreferenceIfNeeded(); highlightPreferenceIfNeeded();
} }

View File

@@ -30,6 +30,7 @@ import android.app.NotificationManager;
import android.app.role.RoleManager; import android.app.role.RoleManager;
import android.app.usage.IUsageStatsManager; import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents; import android.app.usage.UsageEvents;
import android.companion.ICompanionDeviceManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -55,14 +56,18 @@ import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R; import com.android.settingslib.R;
import com.android.settingslib.Utils; import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.notification.ConversationIconFactory; import com.android.settingslib.notification.ConversationIconFactory;
import com.android.settingslib.utils.StringUtil; import com.android.settingslib.utils.StringUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
public class NotificationBackend { public class NotificationBackend {
private static final String TAG = "NotificationBackend"; private static final String TAG = "NotificationBackend";
@@ -138,6 +143,35 @@ public class NotificationBackend {
} }
} }
static public CharSequence getDeviceList(ICompanionDeviceManager cdm, LocalBluetoothManager lbm,
String pkg, int userId) {
boolean multiple = false;
StringBuilder sb = new StringBuilder();
try {
List<String> associatedMacAddrs = cdm.getAssociations(pkg, userId);
if (associatedMacAddrs != null) {
for (String assocMac : associatedMacAddrs) {
final Collection<CachedBluetoothDevice> cachedDevices =
lbm.getCachedDeviceManager().getCachedDevicesCopy();
for (CachedBluetoothDevice cachedBluetoothDevice : cachedDevices) {
if (Objects.equals(assocMac, cachedBluetoothDevice.getAddress())) {
if (multiple) {
sb.append(", ");
} else {
multiple = true;
}
sb.append(cachedBluetoothDevice.getName());
}
}
}
}
} catch (RemoteException e) {
Log.w(TAG, "Error calling CDM", e);
}
return sb.toString();
}
public boolean isSystemApp(Context context, ApplicationInfo app) { public boolean isSystemApp(Context context, ApplicationInfo app) {
try { try {
PackageInfo info = context.getPackageManager().getPackageInfo( PackageInfo info = context.getPackageManager().getPackageInfo(

View File

@@ -26,24 +26,50 @@ import static org.mockito.Mockito.when;
import android.app.role.RoleManager; import android.app.role.RoleManager;
import android.app.usage.UsageEvents; import android.app.usage.UsageEvents;
import android.bluetooth.BluetoothAdapter;
import android.companion.ICompanionDeviceManager;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Parcel; import android.os.Parcel;
import com.android.settings.notification.NotificationBackend.AppRow; import com.android.settings.notification.NotificationBackend.AppRow;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class NotificationBackendTest { public class NotificationBackendTest {
@Mock
LocalBluetoothManager mBm;
@Mock
ICompanionDeviceManager mCdm;
@Mock
CachedBluetoothDeviceManager mCbm;
ComponentName mCn = new ComponentName("a", "b");
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mBm.getCachedDeviceManager()).thenReturn(mCbm);
}
@Test @Test
public void testMarkAppRow_unblockablePackage() { public void testMarkAppRow_unblockablePackage() {
AppRow appRow = new AppRow(); AppRow appRow = new AppRow();
@@ -138,4 +164,69 @@ public class NotificationBackendTest {
parcel.setDataPosition(0); parcel.setDataPosition(0);
return UsageEvents.CREATOR.createFromParcel(parcel); return UsageEvents.CREATOR.createFromParcel(parcel);
} }
@Test
public void getDeviceList_noAssociations() throws Exception {
when(mCdm.getAssociations(mCn.getPackageName(), 0)).thenReturn(null);
Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
CachedBluetoothDevice cbd1 = mock(CachedBluetoothDevice.class);
when(cbd1.getAddress()).thenReturn("00:00:00:00:00:10");
when(cbd1.getName()).thenReturn("Device 1");
cachedDevices.add(cbd1);
when(mCbm.getCachedDevicesCopy()).thenReturn(cachedDevices);
BluetoothAdapter.getDefaultAdapter().enable();
assertThat(new NotificationBackend().getDeviceList(
mCdm, mBm, mCn.getPackageName(), 0).toString()).isEmpty();
}
@Test
public void getDeviceList_associationsButNoDevice() throws Exception {
List<String> macs = ImmutableList.of("00:00:00:00:00:10", "00:00:00:00:00:20");
when(mCdm.getAssociations(mCn.getPackageName(), 0)).thenReturn(macs);
when(mCbm.getCachedDevicesCopy()).thenReturn(new ArrayList<>());
assertThat(new NotificationBackend().getDeviceList(
mCdm, mBm, mCn.getPackageName(), 0).toString()).isEmpty();
}
@Test
public void getDeviceList_singleDevice() throws Exception {
List<String> macs = ImmutableList.of("00:00:00:00:00:10", "00:00:00:00:00:20");
when(mCdm.getAssociations(mCn.getPackageName(), 0)).thenReturn(macs);
Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
CachedBluetoothDevice cbd1 = mock(CachedBluetoothDevice.class);
when(cbd1.getAddress()).thenReturn(macs.get(0));
when(cbd1.getName()).thenReturn("Device 1");
cachedDevices.add(cbd1);
when(mCbm.getCachedDevicesCopy()).thenReturn(cachedDevices);
assertThat(new NotificationBackend().getDeviceList(
mCdm, mBm, mCn.getPackageName(), 0).toString()).isEqualTo("Device 1");
}
@Test
public void getDeviceList_multipleDevices() throws Exception {
List<String> macs = ImmutableList.of("00:00:00:00:00:10", "00:00:00:00:00:20");
when(mCdm.getAssociations(mCn.getPackageName(), 0)).thenReturn(macs);
Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
CachedBluetoothDevice cbd1 = mock(CachedBluetoothDevice.class);
when(cbd1.getAddress()).thenReturn(macs.get(0));
when(cbd1.getName()).thenReturn("Device 1");
cachedDevices.add(cbd1);
CachedBluetoothDevice cbd2 = mock(CachedBluetoothDevice.class);
when(cbd2.getAddress()).thenReturn(macs.get(1));
when(cbd2.getName()).thenReturn("Device 2");
cachedDevices.add(cbd2);
when(mCbm.getCachedDevicesCopy()).thenReturn(cachedDevices);
assertThat(new NotificationBackend().getDeviceList(
mCdm, mBm, mCn.getPackageName(), 0).toString()).isEqualTo("Device 1, Device 2");
}
} }

View File

@@ -1,135 +0,0 @@
/*
* 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.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.companion.ICompanionDeviceManager;
import android.content.ComponentName;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class HeaderPreferenceControllerTest {
private Context mContext;
private HeaderPreferenceController mController;
@Mock
LocalBluetoothManager mBm;
@Mock
ICompanionDeviceManager mCdm;
@Mock
CachedBluetoothDeviceManager mCbm;
ComponentName mCn = new ComponentName("a", "b");
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = ApplicationProvider.getApplicationContext();
when(mBm.getCachedDeviceManager()).thenReturn(mCbm);
mController = new HeaderPreferenceController(mContext, "key");
mController.setCn(mCn);
mController.setUserId(0);
mController.setBluetoothManager(mBm);
mController.setCdm(mCdm);
}
@Test
public void getDeviceList_noAssociations() throws Exception {
when(mCdm.getAssociations(mCn.getPackageName(), 0)).thenReturn(null);
Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
CachedBluetoothDevice cbd1 = mock(CachedBluetoothDevice.class);
when(cbd1.getAddress()).thenReturn("00:00:00:00:00:10");
when(cbd1.getName()).thenReturn("Device 1");
cachedDevices.add(cbd1);
when(mCbm.getCachedDevicesCopy()).thenReturn(cachedDevices);
BluetoothAdapter.getDefaultAdapter().enable();
assertThat(mController.getDeviceList().toString()).isEmpty();
}
@Test
public void getDeviceList_associationsButNoDevice() throws Exception {
List<String> macs = ImmutableList.of("00:00:00:00:00:10", "00:00:00:00:00:20");
when(mCdm.getAssociations(mCn.getPackageName(), 0)).thenReturn(macs);
when(mCbm.getCachedDevicesCopy()).thenReturn(new ArrayList<>());
assertThat(mController.getDeviceList().toString()).isEmpty();
}
@Test
public void getDeviceList_singleDevice() throws Exception {
List<String> macs = ImmutableList.of("00:00:00:00:00:10", "00:00:00:00:00:20");
when(mCdm.getAssociations(mCn.getPackageName(), 0)).thenReturn(macs);
Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
CachedBluetoothDevice cbd1 = mock(CachedBluetoothDevice.class);
when(cbd1.getAddress()).thenReturn(macs.get(0));
when(cbd1.getName()).thenReturn("Device 1");
cachedDevices.add(cbd1);
when(mCbm.getCachedDevicesCopy()).thenReturn(cachedDevices);
assertThat(mController.getDeviceList().toString()).isEqualTo("Device 1");
}
@Test
public void getDeviceList_multipleDevices() throws Exception {
List<String> macs = ImmutableList.of("00:00:00:00:00:10", "00:00:00:00:00:20");
when(mCdm.getAssociations(mCn.getPackageName(), 0)).thenReturn(macs);
Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
CachedBluetoothDevice cbd1 = mock(CachedBluetoothDevice.class);
when(cbd1.getAddress()).thenReturn(macs.get(0));
when(cbd1.getName()).thenReturn("Device 1");
cachedDevices.add(cbd1);
CachedBluetoothDevice cbd2 = mock(CachedBluetoothDevice.class);
when(cbd2.getAddress()).thenReturn(macs.get(1));
when(cbd2.getName()).thenReturn("Device 2");
cachedDevices.add(cbd2);
when(mCbm.getCachedDevicesCopy()).thenReturn(cachedDevices);
assertThat(mController.getDeviceList().toString()).isEqualTo("Device 1, Device 2");
}
}