From f56aa26b2e36bb2812e7409ce4f22ab0a76b4f00 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Tue, 22 Feb 2022 11:08:08 -0500 Subject: [PATCH] Disable app toggle for some T+ apps Specifically, apps that haven't requested the notif permission in their manifest, as we cannot grant/revoke the permission unless they do Test: NotificationBackendTest, AppStateNotificationBridgeTest Fixes: 218315122 Change-Id: Icd936de806d7642809ef6c79d2d169bd673c2659 --- .../AppStateNotificationBridge.java | 17 +++---- .../notification/NotificationBackend.java | 26 ++++++---- .../AppStateNotificationBridgeTest.java | 10 ++-- .../notification/NotificationBackendTest.java | 47 +++++++++++++++++++ 4 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/com/android/settings/applications/AppStateNotificationBridge.java b/src/com/android/settings/applications/AppStateNotificationBridge.java index 3bcf94f3445..964eae42d4c 100644 --- a/src/com/android/settings/applications/AppStateNotificationBridge.java +++ b/src/com/android/settings/applications/AppStateNotificationBridge.java @@ -24,11 +24,7 @@ import android.os.UserManager; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.Log; -import android.util.Slog; -import android.view.View; -import android.view.ViewGroup; import android.widget.CompoundButton; -import android.widget.Switch; import com.android.settings.R; import com.android.settings.Utils; @@ -127,8 +123,7 @@ public class AppStateNotificationBridge extends AppStateBaseBridge { private void addBlockStatus(AppEntry entry, NotificationsSentState stats) { if (stats != null) { stats.blocked = mBackend.getNotificationsBanned(entry.info.packageName, entry.info.uid); - stats.systemApp = mBackend.isSystemApp(mContext, entry.info); - stats.blockable = !stats.systemApp || (stats.systemApp && stats.blocked); + stats.blockable = mBackend.enableSwitch(mContext, entry.info); } } @@ -229,11 +224,13 @@ public class AppStateNotificationBridge extends AppStateBaseBridge { return null; } return (buttonView, isChecked) -> { - mBackend.setNotificationsEnabledForPackage( - entry.info.packageName, entry.info.uid, isChecked); NotificationsSentState stats = getNotificationsSentState(entry); if (stats != null) { - stats.blocked = !isChecked; + if (stats.blocked == isChecked) { + mBackend.setNotificationsEnabledForPackage( + entry.info.packageName, entry.info.uid, isChecked); + stats.blocked = !isChecked; + } } }; } @@ -329,7 +326,6 @@ public class AppStateNotificationBridge extends AppStateBaseBridge { if (stats == null) { return false; } - return !stats.blocked; } @@ -344,6 +340,5 @@ public class AppStateNotificationBridge extends AppStateBaseBridge { public int sentCount = 0; public boolean blockable; public boolean blocked; - public boolean systemApp; } } diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index dbc36d0a7ca..2ae91e21b44 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -22,15 +22,20 @@ import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER; import static android.os.UserHandle.USER_SYSTEM; +import android.Manifest; import android.app.INotificationManager; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationHistory; import android.app.NotificationManager; +import android.app.compat.CompatChanges; import android.app.role.RoleManager; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; import android.companion.ICompanionDeviceManager; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; +import android.compat.annotation.EnabledSince; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -42,6 +47,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -100,12 +106,6 @@ public class NotificationBackend { return row; } - public boolean isBlockable(Context context, ApplicationInfo info) { - final boolean blocked = getNotificationsBanned(info.packageName, info.uid); - final boolean systemApp = isSystemApp(context, info); - return !systemApp || (systemApp && blocked); - } - public AppRow loadAppRow(Context context, PackageManager pm, RoleManager roleManager, PackageInfo app) { final AppRow row = loadAppRow(context, pm, app.applicationInfo); @@ -130,6 +130,15 @@ public class NotificationBackend { || roles.contains(RoleManager.ROLE_EMERGENCY)) { row.systemApp = row.lockedImportance = true; } + // if the app targets T but has not requested the permission, we cannot change the + // permission state + if (app.applicationInfo.targetSdkVersion > Build.VERSION_CODES.S_V2) { + if (app.requestedPermissions == null || Arrays.stream(app.requestedPermissions) + .noneMatch(p -> p.equals(android.Manifest.permission.POST_NOTIFICATIONS))) { + row.lockedImportance = true; + } + } + } else { row.systemApp = Utils.isSystemPackage(context.getResources(), pm, app); List roles = rm.getHeldRolesFromController(app.packageName); @@ -192,14 +201,15 @@ public class NotificationBackend { return sb.toString(); } - public boolean isSystemApp(Context context, ApplicationInfo app) { + public boolean enableSwitch(Context context, ApplicationInfo app) { try { PackageInfo info = context.getPackageManager().getPackageInfo( app.packageName, PackageManager.GET_SIGNATURES); RoleManager rm = context.getSystemService(RoleManager.class); final AppRow row = new AppRow(); recordCanBeBlocked(context, context.getPackageManager(), rm, info, row); - return row.systemApp; + boolean systemBlockable = !row.systemApp || (row.systemApp && row.banned); + return systemBlockable && !row.lockedImportance; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } diff --git a/tests/robotests/src/com/android/settings/applications/AppStateNotificationBridgeTest.java b/tests/robotests/src/com/android/settings/applications/AppStateNotificationBridgeTest.java index 21a2947e547..b725fc30c02 100644 --- a/tests/robotests/src/com/android/settings/applications/AppStateNotificationBridgeTest.java +++ b/tests/robotests/src/com/android/settings/applications/AppStateNotificationBridgeTest.java @@ -99,7 +99,7 @@ public class AppStateNotificationBridgeTest { when(mState.newSession(any())).thenReturn(mSession); when(mState.getBackgroundLooper()).thenReturn(mock(Looper.class)); when(mBackend.getNotificationsBanned(anyString(), anyInt())).thenReturn(true); - when(mBackend.isSystemApp(any(), any())).thenReturn(true); + when(mBackend.enableSwitch(any(), any())).thenReturn(true); // most tests assume no work profile when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{}); mContext = RuntimeEnvironment.application.getApplicationContext(); @@ -245,7 +245,6 @@ public class AppStateNotificationBridgeTest { assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentDaily).isEqualTo(1); assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentWeekly).isEqualTo(0); assertThat(((NotificationsSentState) apps.get(0).extraInfo).blocked).isTrue(); - assertThat(((NotificationsSentState) apps.get(0).extraInfo).systemApp).isTrue(); assertThat(((NotificationsSentState) apps.get(0).extraInfo).blockable).isTrue(); } @@ -376,7 +375,6 @@ public class AppStateNotificationBridgeTest { assertThat(((NotificationsSentState) entry.extraInfo).avgSentDaily).isEqualTo(2); assertThat(((NotificationsSentState) entry.extraInfo).avgSentWeekly).isEqualTo(0); assertThat(((NotificationsSentState) entry.extraInfo).blocked).isTrue(); - assertThat(((NotificationsSentState) entry.extraInfo).systemApp).isTrue(); assertThat(((NotificationsSentState) entry.extraInfo).blockable).isTrue(); } @@ -563,11 +561,11 @@ public class AppStateNotificationBridgeTest { entry.extraInfo = new NotificationsSentState(); CompoundButton.OnCheckedChangeListener listener = mBridge.getSwitchOnCheckedListener(entry); - listener.onCheckedChanged(toggle, true); + listener.onCheckedChanged(toggle, false); verify(mBackend).setNotificationsEnabledForPackage( - entry.info.packageName, entry.info.uid, true); - assertThat(((NotificationsSentState) entry.extraInfo).blocked).isFalse(); + entry.info.packageName, entry.info.uid, false); + assertThat(((NotificationsSentState) entry.extraInfo).blocked).isTrue(); } @Test diff --git a/tests/robotests/src/com/android/settings/notification/NotificationBackendTest.java b/tests/robotests/src/com/android/settings/notification/NotificationBackendTest.java index 64cde5a6b62..a7ddec3c552 100644 --- a/tests/robotests/src/com/android/settings/notification/NotificationBackendTest.java +++ b/tests/robotests/src/com/android/settings/notification/NotificationBackendTest.java @@ -37,7 +37,9 @@ import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; import android.net.MacAddress; +import android.os.Build; import android.os.Parcel; import android.provider.Settings; @@ -177,6 +179,51 @@ public class NotificationBackendTest { assertFalse(appRow.lockedImportance); } + @Test + public void testMarkAppRow_targetsT_noPermissionRequest() throws Exception { + Secure.putIntForUser(RuntimeEnvironment.application.getContentResolver(), + Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 1, USER_SYSTEM); + + PackageInfo pi = new PackageInfo(); + pi.packageName = "test"; + pi.applicationInfo = new ApplicationInfo(); + pi.applicationInfo.packageName = "test"; + pi.applicationInfo.uid = 123; + pi.applicationInfo.targetSdkVersion= Build.VERSION_CODES.TIRAMISU; + pi.requestedPermissions = new String[] {"something"}; + + when(mInm.isPermissionFixed(pi.packageName, 0)).thenReturn(false); + + AppRow appRow = new NotificationBackend().loadAppRow(RuntimeEnvironment.application, + mock(PackageManager.class), mock(RoleManager.class), pi); + + assertFalse(appRow.systemApp); + assertTrue(appRow.lockedImportance); + } + + @Test + public void testMarkAppRow_targetsT_permissionRequest() throws Exception { + Secure.putIntForUser(RuntimeEnvironment.application.getContentResolver(), + Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 1, USER_SYSTEM); + + PackageInfo pi = new PackageInfo(); + pi.packageName = "test"; + pi.applicationInfo = new ApplicationInfo(); + pi.applicationInfo.packageName = "test"; + pi.applicationInfo.uid = 123; + pi.applicationInfo.targetSdkVersion= Build.VERSION_CODES.TIRAMISU; + pi.requestedPermissions = new String[] {"something", + android.Manifest.permission.POST_NOTIFICATIONS}; + + when(mInm.isPermissionFixed(pi.packageName, 0)).thenReturn(false); + + AppRow appRow = new NotificationBackend().loadAppRow(RuntimeEnvironment.application, + mock(PackageManager.class), mock(RoleManager.class), pi); + + assertFalse(appRow.systemApp); + assertFalse(appRow.lockedImportance); + } + @Test public void testMarkAppRow_notDefaultPackage() { PackageInfo pi = new PackageInfo();