diff options
author | Christopher Tate <ctate@google.com> | 2020-11-18 17:22:45 -0800 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2021-04-08 16:40:05 +0000 |
commit | f59cdacd1dcaa8220c01a66a26db37fb00c75982 (patch) | |
tree | de2a9fed48b904f742866c022badba96ff5f461c | |
parent | aca94478ab543cb00679c6890f39ae7d1814d6ae (diff) | |
download | base-f59cdacd1dcaa8220c01a66a26db37fb00c75982.tar.gz |
DO NOT MERGE - Disallow deletion of channels with FGS notifications
Bug: 156090809
Test: atest CtsAppTestCases:NotificationManagerTest
Test: atest CtsAppTestCases:android.app.cts.ServiceTest
Change-Id: I1c2bb78d86f194585d273661cecf3419f51965df
Merged-In: I1c2bb78d86f194585d273661cecf3419f51965df
(cherry picked from commit cfd88a8e244c2f89a1a6502e9683deb17692491d)
5 files changed, 107 insertions, 5 deletions
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 1d5f49a879d5..df0d7107c7a2 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -403,6 +403,21 @@ public abstract class ActivityManagerInternal { public abstract List<ProcessMemoryState> getMemoryStateForProcesses(); /** + * Returns {@code true} if the given notification channel currently has a + * notification associated with a foreground service. This is an AMS check + * because that is the source of truth for the FGS state. + */ + public abstract boolean hasForegroundServiceNotification(String pkg, int userId, + String channelId); + + /** + * If the given app has any FGSs whose notifications are in the given channel, + * stop them. + */ + public abstract void stopForegroundServicesForChannel(String pkg, int userId, + String channelId); + + /** * This enforces {@code func} can only be called if either the caller is Recents activity or * has {@code permission}. */ diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b49d07f3e46c..43a3fc822b01 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -29,6 +29,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.function.Predicate; @@ -367,6 +368,45 @@ public final class ActiveServices { return smap != null ? smap.mStartingBackground.size() >= mMaxStartingBackground : false; } + boolean hasForegroundServiceNotificationLocked(String pkg, int userId, String channelId) { + final ServiceMap smap = mServiceMap.get(userId); + if (smap != null) { + for (int i = 0; i < smap.mServicesByName.size(); i++) { + final ServiceRecord sr = smap.mServicesByName.valueAt(i); + if (sr.appInfo.packageName.equals(pkg) && sr.isForeground) { + if (Objects.equals(sr.foregroundNoti.getChannelId(), channelId)) { + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, "Channel u" + userId + "/pkg=" + pkg + + "/channelId=" + channelId + + " has fg service notification"); + } + return true; + } + } + } + } + return false; + } + + void stopForegroundServicesForChannelLocked(String pkg, int userId, String channelId) { + final ServiceMap smap = mServiceMap.get(userId); + if (smap != null) { + for (int i = 0; i < smap.mServicesByName.size(); i++) { + final ServiceRecord sr = smap.mServicesByName.valueAt(i); + if (sr.appInfo.packageName.equals(pkg) && sr.isForeground) { + if (Objects.equals(sr.foregroundNoti.getChannelId(), channelId)) { + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, "Stopping FGS u" + userId + "/pkg=" + pkg + + "/channelId=" + channelId + + " for conversation channel clear"); + } + stopServiceLocked(sr); + } + } + } + } + } + private ServiceMap getServiceMapLocked(int callingUser) { ServiceMap smap = mServiceMap.get(callingUser); if (smap == null) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ae240bdd1c01..edbff464474a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -26837,6 +26837,22 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public boolean hasForegroundServiceNotification(String pkg, int userId, + String channelId) { + synchronized (ActivityManagerService.this) { + return mServices.hasForegroundServiceNotificationLocked(pkg, userId, channelId); + } + } + + @Override + public void stopForegroundServicesForChannel(String pkg, int userId, + String channelId) { + synchronized (ActivityManagerService.this) { + mServices.stopForegroundServicesForChannelLocked(pkg, userId, channelId); + } + } + + @Override public void cancelRecentsAnimation(boolean restoreHomeStackPosition) { ActivityManagerService.this.cancelRecentsAnimation(restoreHomeStackPosition); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c422f5e092bc..bfa1b5fc58c9 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -307,6 +307,7 @@ public class NotificationManagerService extends SystemService { private IActivityManager mAm; private ActivityManager mActivityManager; + private ActivityManagerInternal mAmi; private IPackageManager mPackageManager; private PackageManager mPackageManagerClient; AudioManager mAudioManager; @@ -1373,7 +1374,8 @@ public class NotificationManagerService extends SystemService { ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper, NotificationUsageStats usageStats, AtomicFile policyFile, ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am, - UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm) { + UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm, + ActivityManagerInternal ami) { Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, @@ -1390,6 +1392,7 @@ public class NotificationManagerService extends SystemService { mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); mCompanionManager = companionManager; mActivityManager = activityManager; + mAmi = ami; mDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); mDpm = dpm; @@ -1530,7 +1533,8 @@ public class NotificationManagerService extends SystemService { (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE), getGroupHelper(), ActivityManager.getService(), LocalServices.getService(UsageStatsManagerInternal.class), - LocalServices.getService(DevicePolicyManagerInternal.class)); + LocalServices.getService(DevicePolicyManagerInternal.class), + LocalServices.getService(ActivityManagerInternal.class)); // register for various Intents IntentFilter filter = new IntentFilter(); @@ -2277,15 +2281,32 @@ public class NotificationManagerService extends SystemService { return mRankingHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted); } + // Returns 'true' if the given channel has a notification associated + // with an active foreground service. + private void enforceDeletingChannelHasNoFgService(String pkg, int userId, + String channelId) { + if (mAmi.hasForegroundServiceNotification(pkg, userId, channelId)) { + // Would be a behavioral change to introduce a throw here, so + // we simply return without affecting the channel. + Slog.w(TAG, "Package u" + userId + "/" + pkg + + " may not delete notification channel '" + + channelId + "' with fg service"); + throw new SecurityException("Not allowed to delete channel " + channelId + + " with a foreground service"); + } + } + @Override public void deleteNotificationChannel(String pkg, String channelId) { checkCallerIsSystemOrSameApp(pkg); final int callingUid = Binder.getCallingUid(); + final int callingUser = UserHandle.getUserId(callingUid); if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { throw new IllegalArgumentException("Cannot delete default channel"); } + enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId); cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true, - UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null); + callingUser, REASON_CHANNEL_BANNED, null); mRankingHelper.deleteNotificationChannel(pkg, callingUid, channelId); mListeners.notifyNotificationChannelChanged(pkg, UserHandle.getUserHandleForUid(callingUid), @@ -2317,13 +2338,20 @@ public class NotificationManagerService extends SystemService { NotificationChannelGroup groupToDelete = mRankingHelper.getNotificationChannelGroup(groupId, pkg, callingUid); if (groupToDelete != null) { + // Preflight for allowability + final int userId = UserHandle.getUserId(callingUid); + List<NotificationChannel> groupChannels = groupToDelete.getChannels(); + for (int i = 0; i < groupChannels.size(); i++) { + enforceDeletingChannelHasNoFgService(pkg, userId, + groupChannels.get(i).getId()); + } List<NotificationChannel> deletedChannels = mRankingHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId); for (int i = 0; i < deletedChannels.size(); i++) { final NotificationChannel deletedChannel = deletedChannels.get(i); cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0, true, - UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, + userId, REASON_CHANNEL_BANNED, null); mListeners.notifyNotificationChannelChanged(pkg, UserHandle.getUserHandleForUid(callingUid), diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 8b719de98f04..049c9b1a8415 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -63,6 +63,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.Notification; @@ -169,6 +170,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private AudioManager mAudioManager; @Mock ActivityManager mActivityManager; + @Mock + ActivityManagerInternal mAmi; NotificationManagerService.WorkerHandler mHandler; @Mock Resources mResources; @@ -283,7 +286,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mListeners, mAssistants, mConditionProviders, mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAppUsageStats, - mock(DevicePolicyManagerInternal.class)); + mock(DevicePolicyManagerInternal.class), mAmi); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { throw e; |