diff --git a/res/drawable/ic_notification_silence.xml b/res/drawable/ic_notification_silence.xml index 9da56dd0bbf..4b71d65dcc6 100644 --- a/res/drawable/ic_notification_silence.xml +++ b/res/drawable/ic_notification_silence.xml @@ -14,28 +14,13 @@ Copyright (C) 2019 The Android Open Source Project See the License for the specific language governing permissions and limitations under the License. --> - - - - - - - - - - - - - + android:viewportHeight="24" + android:tint="?android:attr/colorControlNormal"> + + diff --git a/res/drawable/ic_ongoing_notification.xml b/res/drawable/ic_ongoing_notification.xml new file mode 100644 index 00000000000..238b5170a48 --- /dev/null +++ b/res/drawable/ic_ongoing_notification.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/xml/notification_access_permission_details.xml b/res/xml/notification_access_permission_details.xml index 55a47f5361a..0b764301153 100644 --- a/res/xml/notification_access_permission_details.xml +++ b/res/xml/notification_access_permission_details.xml @@ -34,19 +34,23 @@ /> + android:icon="@drawable/ic_ongoing_notification" + settings:controller="com.android.settings.applications.specialaccess.notificationaccess.OngoingTypeFilterPreferenceController"/> /> + android:icon="@drawable/ic_promote_conversation" + settings:controller="com.android.settings.applications.specialaccess.notificationaccess.ConversationTypeFilterPreferenceController"/> /> + android:icon="@drawable/ic_notification_alert" + settings:controller="com.android.settings.applications.specialaccess.notificationaccess.AlertingTypeFilterPreferenceController"/> /> + android:icon="@drawable/ic_notification_silence" + settings:controller="com.android.settings.applications.specialaccess.notificationaccess.SilentTypeFilterPreferenceController"/> + settings:controller="com.android.settings.applications.specialaccess.notificationaccess.NotificationAccessScreenPreferenceController"> + + + + + diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/HeaderPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/HeaderPreferenceController.java index 94736e4c21f..1144f12132d 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/HeaderPreferenceController.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/HeaderPreferenceController.java @@ -16,6 +16,8 @@ package com.android.settings.applications.specialaccess.notificationaccess; +import android.companion.ICompanionDeviceManager; +import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -30,8 +32,10 @@ import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.notification.NotificationBackend; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.applications.AppUtils; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.widget.LayoutPreference; @@ -43,6 +47,10 @@ public class HeaderPreferenceController extends BasePreferenceController private PackageInfo mPackageInfo; private PackageManager mPm; private CharSequence mServiceName; + private ICompanionDeviceManager mCdm; + private LocalBluetoothManager mBm; + private ComponentName mCn; + private int mUserId; public HeaderPreferenceController(Context context, String key) { super(context, key); @@ -68,6 +76,26 @@ public class HeaderPreferenceController extends BasePreferenceController return this; } + public HeaderPreferenceController setCdm(ICompanionDeviceManager cdm) { + mCdm = cdm; + return this; + } + + public HeaderPreferenceController setBluetoothManager(LocalBluetoothManager bm) { + mBm = bm; + return this; + } + + public HeaderPreferenceController setCn(ComponentName cn) { + mCn = cn; + return this; + } + + public HeaderPreferenceController setUserId(int userId) { + mUserId = userId; + return this; + } + @Override public int getAvailabilityStatus() { return AVAILABLE; @@ -88,6 +116,8 @@ public class HeaderPreferenceController extends BasePreferenceController .getBadgedIcon(mPackageInfo.applicationInfo)) .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm)) .setSummary(mServiceName) + .setSecondSummary(new NotificationBackend().getDeviceList( + mCdm, mBm, mCn.getPackageName(), mUserId)) .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo)) .setPackageName(mPackageInfo.packageName) .setUid(mPackageInfo.applicationInfo.uid) diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java index a205b57433b..16aa07883e6 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java @@ -21,7 +21,7 @@ import static com.android.settings.applications.AppInfoBase.ARG_PACKAGE_NAME; import android.app.Activity; import android.app.NotificationManager; import android.app.settings.SettingsEnums; -import android.bluetooth.BluetoothAdapter; +import android.companion.ICompanionDeviceManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -30,6 +30,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Bundle; +import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -45,14 +46,13 @@ import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.manageapplications.ManageApplications; +import com.android.settings.bluetooth.Utils; import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.notification.NotificationBackend; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import java.util.Collection; import java.util.List; import java.util.Objects; @@ -99,7 +99,12 @@ public class NotificationAccessDetails extends DashboardFragment { .setFragment(this) .setPackageInfo(mPackageInfo) .setPm(context.getPackageManager()) - .setServiceName(mServiceName); + .setServiceName(mServiceName) + .setBluetoothManager(Utils.getLocalBtManager(context)) + .setCdm(ICompanionDeviceManager.Stub.asInterface( + ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE))) + .setCn(mComponentName) + .setUserId(mUserId); getPreferenceControllers().forEach(controllers -> { controllers.forEach(controller -> { if (controller instanceof TypeFilterPreferenceController) { diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java index 12d6295ef90..a642d9c5b33 100644 --- a/src/com/android/settings/notification/NotificationAccessSettings.java +++ b/src/com/android/settings/notification/NotificationAccessSettings.java @@ -20,12 +20,14 @@ import android.annotation.Nullable; import android.app.NotificationManager; import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; +import android.companion.ICompanionDeviceManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Bundle; +import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -35,6 +37,7 @@ import android.util.Log; import android.view.View; import android.widget.Toast; +import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import com.android.settings.R; @@ -57,6 +60,9 @@ import java.util.List; @SearchIndexable public class NotificationAccessSettings extends EmptyTextSettings { 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 = new ManagedServiceSettings.Config.Builder() .setTag(TAG) @@ -76,6 +82,7 @@ public class NotificationAccessSettings extends EmptyTextSettings { private DevicePolicyManager mDpm; private ServiceListing mServiceListing; private IconDrawableFactory mIconDrawableFactory; + private NotificationBackend mBackend = new NotificationBackend(); @Override public void onCreate(Bundle icicle) { @@ -93,7 +100,6 @@ public class NotificationAccessSettings extends EmptyTextSettings { .setTag(CONFIG.tag) .build(); mServiceListing.addCallback(this::updateList); - setPreferenceScreen(getPreferenceManager().createPreferenceScreen(mContext)); if (UserManager.get(mContext).isManagedProfile()) { // 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 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)); for (ServiceInfo service : services) { 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, UserHandle.getUserId(service.applicationInfo.uid))); pref.setKey(cn.flattenToString()); - pref.setSummary(mNm.isNotificationListenerAccessGranted(cn) - ? R.string.app_permission_summary_allowed - : R.string.app_permission_summary_not_allowed); + pref.setSummary(mBackend.getDeviceList(ICompanionDeviceManager.Stub.asInterface( + ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE)), + com.android.settings.bluetooth.Utils.getLocalBtManager(mContext), + service.packageName, + UserHandle.myUserId())); if (managedProfileId != UserHandle.USER_NULL && !mDpm.isNotificationListenerServicePermitted( service.packageName, managedProfileId)) { @@ -173,7 +185,11 @@ public class NotificationAccessSettings extends EmptyTextSettings { return true; }); pref.setKey(cn.flattenToString()); - screen.addPreference(pref); + if (mNm.isNotificationListenerAccessGranted(cn)) { + allowedCategory.addPreference(pref); + } else { + notAllowedCategory.addPreference(pref); + } } highlightPreferenceIfNeeded(); } diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index 8a7e737dbb8..4347ca55104 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -30,6 +30,7 @@ import android.app.NotificationManager; import android.app.role.RoleManager; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; +import android.companion.ICompanionDeviceManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -55,14 +56,18 @@ import androidx.annotation.VisibleForTesting; import com.android.settingslib.R; 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.utils.StringUtil; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; public class 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 associatedMacAddrs = cdm.getAssociations(pkg, userId); + if (associatedMacAddrs != null) { + for (String assocMac : associatedMacAddrs) { + final Collection 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) { try { PackageInfo info = context.getPackageManager().getPackageInfo( diff --git a/tests/robotests/src/com/android/settings/notification/NotificationBackendTest.java b/tests/robotests/src/com/android/settings/notification/NotificationBackendTest.java index cb5060914b8..17471b5d90c 100644 --- a/tests/robotests/src/com/android/settings/notification/NotificationBackendTest.java +++ b/tests/robotests/src/com/android/settings/notification/NotificationBackendTest.java @@ -26,24 +26,50 @@ import static org.mockito.Mockito.when; import android.app.role.RoleManager; 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.PackageInfo; import android.content.pm.PackageManager; import android.os.Parcel; 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.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; +import java.util.Collection; import java.util.List; @RunWith(RobolectricTestRunner.class) 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 public void testMarkAppRow_unblockablePackage() { AppRow appRow = new AppRow(); @@ -138,4 +164,69 @@ public class NotificationBackendTest { parcel.setDataPosition(0); return UsageEvents.CREATOR.createFromParcel(parcel); } + + @Test + public void getDeviceList_noAssociations() throws Exception { + when(mCdm.getAssociations(mCn.getPackageName(), 0)).thenReturn(null); + + Collection 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 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 macs = ImmutableList.of("00:00:00:00:00:10", "00:00:00:00:00:20"); + when(mCdm.getAssociations(mCn.getPackageName(), 0)).thenReturn(macs); + + Collection 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 macs = ImmutableList.of("00:00:00:00:00:10", "00:00:00:00:00:20"); + when(mCdm.getAssociations(mCn.getPackageName(), 0)).thenReturn(macs); + + Collection 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"); + } }