Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f59cdacd authored by Christopher Tate's avatar Christopher Tate Committed by android-build-team Robot
Browse files

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 cfd88a8e)
parent aca94478
Loading
Loading
Loading
Loading
+15 −0
Original line number Original line Diff line number Diff line
@@ -402,6 +402,21 @@ public abstract class ActivityManagerInternal {
     */
     */
    public abstract List<ProcessMemoryState> getMemoryStateForProcesses();
    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
     * This enforces {@code func} can only be called if either the caller is Recents activity or
     * has {@code permission}.
     * has {@code permission}.
+40 −0
Original line number Original line Diff line number Diff line
@@ -29,6 +29,7 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Iterator;
import java.util.List;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Predicate;


@@ -367,6 +368,45 @@ public final class ActiveServices {
        return smap != null ? smap.mStartingBackground.size() >= mMaxStartingBackground : false;
        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) {
    private ServiceMap getServiceMapLocked(int callingUser) {
        ServiceMap smap = mServiceMap.get(callingUser);
        ServiceMap smap = mServiceMap.get(callingUser);
        if (smap == null) {
        if (smap == null) {
+16 −0
Original line number Original line Diff line number Diff line
@@ -26836,6 +26836,22 @@ public class ActivityManagerService extends IActivityManager.Stub
            return getRecentTasks().isRecentsComponentHomeActivity(userId);
            return getRecentTasks().isRecentsComponentHomeActivity(userId);
        }
        }
        @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
        @Override
        public void cancelRecentsAnimation(boolean restoreHomeStackPosition) {
        public void cancelRecentsAnimation(boolean restoreHomeStackPosition) {
            ActivityManagerService.this.cancelRecentsAnimation(restoreHomeStackPosition);
            ActivityManagerService.this.cancelRecentsAnimation(restoreHomeStackPosition);
+32 −4
Original line number Original line Diff line number Diff line
@@ -307,6 +307,7 @@ public class NotificationManagerService extends SystemService {


    private IActivityManager mAm;
    private IActivityManager mAm;
    private ActivityManager mActivityManager;
    private ActivityManager mActivityManager;
    private ActivityManagerInternal mAmi;
    private IPackageManager mPackageManager;
    private IPackageManager mPackageManager;
    private PackageManager mPackageManagerClient;
    private PackageManager mPackageManagerClient;
    AudioManager mAudioManager;
    AudioManager mAudioManager;
@@ -1373,7 +1374,8 @@ public class NotificationManagerService extends SystemService {
            ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
            ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
            NotificationUsageStats usageStats, AtomicFile policyFile,
            NotificationUsageStats usageStats, AtomicFile policyFile,
            ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
            ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
            UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm) {
            UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm,
            ActivityManagerInternal ami) {
        Resources resources = getContext().getResources();
        Resources resources = getContext().getResources();
        mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
        mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
                Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
                Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -1390,6 +1392,7 @@ public class NotificationManagerService extends SystemService {
        mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
        mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
        mCompanionManager = companionManager;
        mCompanionManager = companionManager;
        mActivityManager = activityManager;
        mActivityManager = activityManager;
        mAmi = ami;
        mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
        mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
                ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
                ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
        mDpm = dpm;
        mDpm = dpm;
@@ -1530,7 +1533,8 @@ public class NotificationManagerService extends SystemService {
                (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE),
                (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE),
                getGroupHelper(), ActivityManager.getService(),
                getGroupHelper(), ActivityManager.getService(),
                LocalServices.getService(UsageStatsManagerInternal.class),
                LocalServices.getService(UsageStatsManagerInternal.class),
                LocalServices.getService(DevicePolicyManagerInternal.class));
                LocalServices.getService(DevicePolicyManagerInternal.class),
                LocalServices.getService(ActivityManagerInternal.class));


        // register for various Intents
        // register for various Intents
        IntentFilter filter = new IntentFilter();
        IntentFilter filter = new IntentFilter();
@@ -2277,15 +2281,32 @@ public class NotificationManagerService extends SystemService {
            return mRankingHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted);
            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
        @Override
        public void deleteNotificationChannel(String pkg, String channelId) {
        public void deleteNotificationChannel(String pkg, String channelId) {
            checkCallerIsSystemOrSameApp(pkg);
            checkCallerIsSystemOrSameApp(pkg);
            final int callingUid = Binder.getCallingUid();
            final int callingUid = Binder.getCallingUid();
            final int callingUser = UserHandle.getUserId(callingUid);
            if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
            if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
                throw new IllegalArgumentException("Cannot delete default channel");
                throw new IllegalArgumentException("Cannot delete default channel");
            }
            }
            enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId);
            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
            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);
            mRankingHelper.deleteNotificationChannel(pkg, callingUid, channelId);
            mListeners.notifyNotificationChannelChanged(pkg,
            mListeners.notifyNotificationChannelChanged(pkg,
                    UserHandle.getUserHandleForUid(callingUid),
                    UserHandle.getUserHandleForUid(callingUid),
@@ -2317,13 +2338,20 @@ public class NotificationManagerService extends SystemService {
            NotificationChannelGroup groupToDelete =
            NotificationChannelGroup groupToDelete =
                    mRankingHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
                    mRankingHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
            if (groupToDelete != null) {
            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 =
                List<NotificationChannel> deletedChannels =
                        mRankingHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
                        mRankingHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
                for (int i = 0; i < deletedChannels.size(); i++) {
                for (int i = 0; i < deletedChannels.size(); i++) {
                    final NotificationChannel deletedChannel = deletedChannels.get(i);
                    final NotificationChannel deletedChannel = deletedChannels.get(i);
                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
                            true,
                            true,
                            UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
                            userId, REASON_CHANNEL_BANNED,
                            null);
                            null);
                    mListeners.notifyNotificationChannelChanged(pkg,
                    mListeners.notifyNotificationChannelChanged(pkg,
                            UserHandle.getUserHandleForUid(callingUid),
                            UserHandle.getUserHandleForUid(callingUid),
+4 −1
Original line number Original line Diff line number Diff line
@@ -63,6 +63,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;


import android.app.ActivityManager;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.Notification;
@@ -169,6 +170,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    private AudioManager mAudioManager;
    private AudioManager mAudioManager;
    @Mock
    @Mock
    ActivityManager mActivityManager;
    ActivityManager mActivityManager;
    @Mock
    ActivityManagerInternal mAmi;
    NotificationManagerService.WorkerHandler mHandler;
    NotificationManagerService.WorkerHandler mHandler;
    @Mock
    @Mock
    Resources mResources;
    Resources mResources;
@@ -283,7 +286,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                    mListeners, mAssistants, mConditionProviders,
                    mListeners, mAssistants, mConditionProviders,
                    mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
                    mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
                    mGroupHelper, mAm, mAppUsageStats,
                    mGroupHelper, mAm, mAppUsageStats,
                    mock(DevicePolicyManagerInternal.class));
                    mock(DevicePolicyManagerInternal.class), mAmi);
        } catch (SecurityException e) {
        } catch (SecurityException e) {
            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
                throw e;
                throw e;