diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java index 56d3f0e445c..369c4f6dfaf 100644 --- a/src/com/android/settings/notification/NotificationAccessSettings.java +++ b/src/com/android/settings/notification/NotificationAccessSettings.java @@ -43,6 +43,7 @@ import android.widget.Toast; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.applications.AppInfoBase; @@ -63,8 +64,8 @@ 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"; + static final String ALLOWED_KEY = "allowed"; + static final String NOT_ALLOWED_KEY = "not_allowed"; private static final int MAX_CN_LENGTH = 500; private static final ManagedServiceSettings.Config CONFIG = @@ -80,9 +81,9 @@ public class NotificationAccessSettings extends EmptyTextSettings { .setEmptyText(R.string.no_notification_listeners) .build(); - private NotificationManager mNm; + @VisibleForTesting NotificationManager mNm; protected Context mContext; - private PackageManager mPm; + @VisibleForTesting PackageManager mPm; private DevicePolicyManager mDpm; private ServiceListing mServiceListing; private IconDrawableFactory mIconDrawableFactory; @@ -102,12 +103,6 @@ public class NotificationAccessSettings extends EmptyTextSettings { .setNoun(CONFIG.noun) .setSetting(CONFIG.setting) .setTag(CONFIG.tag) - .setValidator(info -> { - if (info.getComponentName().flattenToString().length() > MAX_CN_LENGTH) { - return false; - } - return true; - }) .build(); mServiceListing.addCallback(this::updateList); @@ -140,7 +135,8 @@ public class NotificationAccessSettings extends EmptyTextSettings { mServiceListing.setListening(false); } - private void updateList(List services) { + @VisibleForTesting + void updateList(List services) { final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); final int managedProfileId = Utils.getManagedProfileId(um, UserHandle.myUserId()); @@ -153,6 +149,11 @@ public class NotificationAccessSettings extends EmptyTextSettings { services.sort(new PackageItemInfo.DisplayNameComparator(mPm)); for (ServiceInfo service : services) { final ComponentName cn = new ComponentName(service.packageName, service.name); + boolean isAllowed = mNm.isNotificationListenerAccessGranted(cn); + if (!isAllowed && cn.flattenToString().length() > MAX_CN_LENGTH) { + continue; + } + CharSequence title = null; try { title = mPm.getApplicationInfoAsUser( @@ -200,7 +201,7 @@ public class NotificationAccessSettings extends EmptyTextSettings { return true; }); pref.setKey(cn.flattenToString()); - if (mNm.isNotificationListenerAccessGranted(cn)) { + if (isAllowed) { allowedCategory.addPreference(pref); } else { notAllowedCategory.addPreference(pref); diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index 7b7d41a5afa..dfb4a45bb38 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -124,6 +124,9 @@ public class NotificationBackend { static public CharSequence getDeviceList(ICompanionDeviceManager cdm, LocalBluetoothManager lbm, String pkg, int userId) { + if (cdm == null) { + return ""; + } boolean multiple = false; StringBuilder sb = new StringBuilder(); diff --git a/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java b/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java new file mode 100644 index 00000000000..e644c2975b7 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 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.notification; + +import static com.android.settings.notification.NotificationAccessSettings.ALLOWED_KEY; +import static com.android.settings.notification.NotificationAccessSettings.NOT_ALLOWED_KEY; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; + +import androidx.fragment.app.FragmentActivity; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import com.google.common.base.Strings; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +import java.util.ArrayList; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothUtils.class}) +public class NotificationAccessSettingsTest { + + private Context mContext; + private NotificationAccessSettings mAccessSettings; + @Mock + private NotificationManager mNotificationManager; + @Mock + private PackageManager mPackageManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + ShadowApplication shadowApp = ShadowApplication.getInstance(); + shadowApp.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + + mAccessSettings = new NotificationAccessSettings(); + FragmentActivity activity = Robolectric.buildActivity(FragmentActivity.class).setup().get(); + activity.getSupportFragmentManager().beginTransaction().add(mAccessSettings, null).commit(); + + when(mPackageManager.getApplicationInfoAsUser(any(), anyInt(), anyInt())).then( + (Answer) invocation -> { + ApplicationInfo appInfo = mock(ApplicationInfo.class); + when(appInfo.loadLabel(any())).thenReturn(invocation.getArgument(0)); + return appInfo; + }); + + mAccessSettings.mNm = mNotificationManager; + mAccessSettings.mPm = mPackageManager; + ShadowBluetoothUtils.sLocalBluetoothManager = mock(LocalBluetoothManager.class); + } + + @Test + public void updateList_enabledLongName_shown() { + ComponentName longCn = new ComponentName("test.pkg1", + Strings.repeat("Blah", 200) + "Service"); + ComponentName shortCn = new ComponentName("test.pkg2", "ReasonableService"); + ArrayList services = new ArrayList<>(); + services.add(newServiceInfo(longCn.getPackageName(), longCn.getClassName(), 1)); + services.add(newServiceInfo(shortCn.getPackageName(), shortCn.getClassName(), 2)); + when(mNotificationManager.isNotificationListenerAccessGranted(any())).thenReturn(true); + + mAccessSettings.updateList(services); + + PreferenceScreen screen = mAccessSettings.getPreferenceScreen(); + PreferenceCategory allowed = checkNotNull(screen.findPreference(ALLOWED_KEY)); + PreferenceCategory notAllowed = checkNotNull(screen.findPreference(NOT_ALLOWED_KEY)); + assertThat(allowed.getPreferenceCount()).isEqualTo(2); + assertThat(allowed.getPreference(0).getKey()).isEqualTo(longCn.flattenToString()); + assertThat(allowed.getPreference(1).getKey()).isEqualTo(shortCn.flattenToString()); + assertThat(notAllowed.getPreferenceCount()).isEqualTo(0); + } + + @Test + public void updateList_disabledLongName_notShown() { + ComponentName longCn = new ComponentName("test.pkg1", + Strings.repeat("Blah", 200) + "Service"); + ComponentName shortCn = new ComponentName("test.pkg2", "ReasonableService"); + ArrayList services = new ArrayList<>(); + services.add(newServiceInfo(longCn.getPackageName(), longCn.getClassName(), 1)); + services.add(newServiceInfo(shortCn.getPackageName(), shortCn.getClassName(), 2)); + when(mNotificationManager.isNotificationListenerAccessGranted(any())).thenReturn(false); + + mAccessSettings.updateList(services); + + PreferenceScreen screen = mAccessSettings.getPreferenceScreen(); + PreferenceCategory allowed = checkNotNull(screen.findPreference(ALLOWED_KEY)); + PreferenceCategory notAllowed = checkNotNull(screen.findPreference(NOT_ALLOWED_KEY)); + assertThat(allowed.getPreferenceCount()).isEqualTo(0); + assertThat(notAllowed.getPreferenceCount()).isEqualTo(1); + assertThat(notAllowed.getPreference(0).getKey()).isEqualTo(shortCn.flattenToString()); + } + + private static ServiceInfo newServiceInfo(String packageName, String serviceName, int uid) { + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.packageName = packageName; + serviceInfo.name = serviceName; + serviceInfo.applicationInfo = new ApplicationInfo(); + serviceInfo.applicationInfo.uid = uid; + return serviceInfo; + } +}