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
This commit is contained in:
Julia Reynolds
2022-02-22 11:08:08 -05:00
parent 1d15b24da2
commit f56aa26b2e
4 changed files with 75 additions and 25 deletions

View File

@@ -24,11 +24,7 @@ import android.os.UserManager;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.Log; import android.util.Log;
import android.util.Slog;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.Switch;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
@@ -127,8 +123,7 @@ public class AppStateNotificationBridge extends AppStateBaseBridge {
private void addBlockStatus(AppEntry entry, NotificationsSentState stats) { private void addBlockStatus(AppEntry entry, NotificationsSentState stats) {
if (stats != null) { if (stats != null) {
stats.blocked = mBackend.getNotificationsBanned(entry.info.packageName, entry.info.uid); stats.blocked = mBackend.getNotificationsBanned(entry.info.packageName, entry.info.uid);
stats.systemApp = mBackend.isSystemApp(mContext, entry.info); stats.blockable = mBackend.enableSwitch(mContext, entry.info);
stats.blockable = !stats.systemApp || (stats.systemApp && stats.blocked);
} }
} }
@@ -229,12 +224,14 @@ public class AppStateNotificationBridge extends AppStateBaseBridge {
return null; return null;
} }
return (buttonView, isChecked) -> { return (buttonView, isChecked) -> {
mBackend.setNotificationsEnabledForPackage(
entry.info.packageName, entry.info.uid, isChecked);
NotificationsSentState stats = getNotificationsSentState(entry); NotificationsSentState stats = getNotificationsSentState(entry);
if (stats != null) { if (stats != null) {
if (stats.blocked == isChecked) {
mBackend.setNotificationsEnabledForPackage(
entry.info.packageName, entry.info.uid, isChecked);
stats.blocked = !isChecked; stats.blocked = !isChecked;
} }
}
}; };
} }
@@ -329,7 +326,6 @@ public class AppStateNotificationBridge extends AppStateBaseBridge {
if (stats == null) { if (stats == null) {
return false; return false;
} }
return !stats.blocked; return !stats.blocked;
} }
@@ -344,6 +340,5 @@ public class AppStateNotificationBridge extends AppStateBaseBridge {
public int sentCount = 0; public int sentCount = 0;
public boolean blockable; public boolean blockable;
public boolean blocked; public boolean blocked;
public boolean systemApp;
} }
} }

View File

@@ -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.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
import static android.os.UserHandle.USER_SYSTEM; import static android.os.UserHandle.USER_SYSTEM;
import android.Manifest;
import android.app.INotificationManager; import android.app.INotificationManager;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationChannelGroup; import android.app.NotificationChannelGroup;
import android.app.NotificationHistory; import android.app.NotificationHistory;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.compat.CompatChanges;
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.companion.ICompanionDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -42,6 +47,7 @@ import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager; import android.content.pm.ShortcutManager;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.os.UserHandle; import android.os.UserHandle;
@@ -100,12 +106,6 @@ public class NotificationBackend {
return row; 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, public AppRow loadAppRow(Context context, PackageManager pm,
RoleManager roleManager, PackageInfo app) { RoleManager roleManager, PackageInfo app) {
final AppRow row = loadAppRow(context, pm, app.applicationInfo); final AppRow row = loadAppRow(context, pm, app.applicationInfo);
@@ -130,6 +130,15 @@ public class NotificationBackend {
|| roles.contains(RoleManager.ROLE_EMERGENCY)) { || roles.contains(RoleManager.ROLE_EMERGENCY)) {
row.systemApp = row.lockedImportance = true; 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 { } else {
row.systemApp = Utils.isSystemPackage(context.getResources(), pm, app); row.systemApp = Utils.isSystemPackage(context.getResources(), pm, app);
List<String> roles = rm.getHeldRolesFromController(app.packageName); List<String> roles = rm.getHeldRolesFromController(app.packageName);
@@ -192,14 +201,15 @@ public class NotificationBackend {
return sb.toString(); return sb.toString();
} }
public boolean isSystemApp(Context context, ApplicationInfo app) { public boolean enableSwitch(Context context, ApplicationInfo app) {
try { try {
PackageInfo info = context.getPackageManager().getPackageInfo( PackageInfo info = context.getPackageManager().getPackageInfo(
app.packageName, PackageManager.GET_SIGNATURES); app.packageName, PackageManager.GET_SIGNATURES);
RoleManager rm = context.getSystemService(RoleManager.class); RoleManager rm = context.getSystemService(RoleManager.class);
final AppRow row = new AppRow(); final AppRow row = new AppRow();
recordCanBeBlocked(context, context.getPackageManager(), rm, info, row); 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) { } catch (PackageManager.NameNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
} }

View File

@@ -99,7 +99,7 @@ public class AppStateNotificationBridgeTest {
when(mState.newSession(any())).thenReturn(mSession); when(mState.newSession(any())).thenReturn(mSession);
when(mState.getBackgroundLooper()).thenReturn(mock(Looper.class)); when(mState.getBackgroundLooper()).thenReturn(mock(Looper.class));
when(mBackend.getNotificationsBanned(anyString(), anyInt())).thenReturn(true); 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 // most tests assume no work profile
when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{}); when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{});
mContext = RuntimeEnvironment.application.getApplicationContext(); 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).avgSentDaily).isEqualTo(1);
assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentWeekly).isEqualTo(0); assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentWeekly).isEqualTo(0);
assertThat(((NotificationsSentState) apps.get(0).extraInfo).blocked).isTrue(); assertThat(((NotificationsSentState) apps.get(0).extraInfo).blocked).isTrue();
assertThat(((NotificationsSentState) apps.get(0).extraInfo).systemApp).isTrue();
assertThat(((NotificationsSentState) apps.get(0).extraInfo).blockable).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).avgSentDaily).isEqualTo(2);
assertThat(((NotificationsSentState) entry.extraInfo).avgSentWeekly).isEqualTo(0); assertThat(((NotificationsSentState) entry.extraInfo).avgSentWeekly).isEqualTo(0);
assertThat(((NotificationsSentState) entry.extraInfo).blocked).isTrue(); assertThat(((NotificationsSentState) entry.extraInfo).blocked).isTrue();
assertThat(((NotificationsSentState) entry.extraInfo).systemApp).isTrue();
assertThat(((NotificationsSentState) entry.extraInfo).blockable).isTrue(); assertThat(((NotificationsSentState) entry.extraInfo).blockable).isTrue();
} }
@@ -563,11 +561,11 @@ public class AppStateNotificationBridgeTest {
entry.extraInfo = new NotificationsSentState(); entry.extraInfo = new NotificationsSentState();
CompoundButton.OnCheckedChangeListener listener = mBridge.getSwitchOnCheckedListener(entry); CompoundButton.OnCheckedChangeListener listener = mBridge.getSwitchOnCheckedListener(entry);
listener.onCheckedChanged(toggle, true); listener.onCheckedChanged(toggle, false);
verify(mBackend).setNotificationsEnabledForPackage( verify(mBackend).setNotificationsEnabledForPackage(
entry.info.packageName, entry.info.uid, true); entry.info.packageName, entry.info.uid, false);
assertThat(((NotificationsSentState) entry.extraInfo).blocked).isFalse(); assertThat(((NotificationsSentState) entry.extraInfo).blocked).isTrue();
} }
@Test @Test

View File

@@ -37,7 +37,9 @@ 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.content.pm.PermissionInfo;
import android.net.MacAddress; import android.net.MacAddress;
import android.os.Build;
import android.os.Parcel; import android.os.Parcel;
import android.provider.Settings; import android.provider.Settings;
@@ -177,6 +179,51 @@ public class NotificationBackendTest {
assertFalse(appRow.lockedImportance); 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 @Test
public void testMarkAppRow_notDefaultPackage() { public void testMarkAppRow_notDefaultPackage() {
PackageInfo pi = new PackageInfo(); PackageInfo pi = new PackageInfo();