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
This commit is contained in:
committed by
Baligh Uddin
parent
a0f134bd11
commit
88380a00d5
@@ -345,6 +345,21 @@ public abstract class ActivityManagerInternal {
|
||||
*/
|
||||
public abstract boolean hasRunningForegroundService(int uid, int foregroundServiceType);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Registers the specified {@code processObserver} to be notified of future changes to
|
||||
* process state.
|
||||
|
||||
@@ -105,6 +105,7 @@ import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -379,6 +380,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.mServicesByInstanceName.size(); i++) {
|
||||
final ServiceRecord sr = smap.mServicesByInstanceName.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.mServicesByInstanceName.size(); i++) {
|
||||
final ServiceRecord sr = smap.mServicesByInstanceName.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) {
|
||||
|
||||
@@ -18522,6 +18522,22 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@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 registerProcessObserver(IProcessObserver processObserver) {
|
||||
ActivityManagerService.this.registerProcessObserver(processObserver);
|
||||
|
||||
@@ -353,6 +353,7 @@ public class NotificationManagerService extends SystemService {
|
||||
|
||||
private IActivityManager mAm;
|
||||
private ActivityManager mActivityManager;
|
||||
private ActivityManagerInternal mAmi;
|
||||
private IPackageManager mPackageManager;
|
||||
private PackageManager mPackageManagerClient;
|
||||
AudioManager mAudioManager;
|
||||
@@ -1607,7 +1608,7 @@ public class NotificationManagerService extends SystemService {
|
||||
ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
|
||||
UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm,
|
||||
IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps,
|
||||
UserManager userManager) {
|
||||
UserManager userManager, ActivityManagerInternal ami) {
|
||||
Resources resources = getContext().getResources();
|
||||
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
|
||||
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
|
||||
@@ -1626,6 +1627,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;
|
||||
@@ -1794,7 +1796,8 @@ public class NotificationManagerService extends SystemService {
|
||||
UriGrantsManager.getService(),
|
||||
LocalServices.getService(UriGrantsManagerInternal.class),
|
||||
(AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE),
|
||||
getContext().getSystemService(UserManager.class));
|
||||
getContext().getSystemService(UserManager.class),
|
||||
LocalServices.getService(ActivityManagerInternal.class));
|
||||
|
||||
// register for various Intents
|
||||
IntentFilter filter = new IntentFilter();
|
||||
@@ -2728,15 +2731,32 @@ public class NotificationManagerService extends SystemService {
|
||||
return mPreferencesHelper.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);
|
||||
mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, channelId);
|
||||
mListeners.notifyNotificationChannelChanged(pkg,
|
||||
UserHandle.getUserHandleForUid(callingUid),
|
||||
@@ -2768,13 +2788,20 @@ public class NotificationManagerService extends SystemService {
|
||||
NotificationChannelGroup groupToDelete =
|
||||
mPreferencesHelper.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 =
|
||||
mPreferencesHelper.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),
|
||||
|
||||
@@ -73,6 +73,7 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.AutomaticZenRule;
|
||||
import android.app.IActivityManager;
|
||||
@@ -210,6 +211,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
|
||||
NotificationManagerService.WorkerHandler mHandler;
|
||||
@Mock
|
||||
Resources mResources;
|
||||
@Mock
|
||||
ActivityManagerInternal mAmi;
|
||||
|
||||
private NotificationChannel mTestNotificationChannel = new NotificationChannel(
|
||||
TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
|
||||
@@ -355,6 +358,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
|
||||
LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
|
||||
LocalServices.removeServiceForTest(WindowManagerInternal.class);
|
||||
LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
|
||||
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
|
||||
LocalServices.addService(ActivityManagerInternal.class, mAmi);
|
||||
|
||||
doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
|
||||
|
||||
@@ -413,7 +418,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
|
||||
mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
|
||||
mGroupHelper, mAm, mAppUsageStats,
|
||||
mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
|
||||
mAppOpsManager, mUm);
|
||||
mAppOpsManager, mUm, mAmi);
|
||||
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
|
||||
|
||||
mService.setAudioManager(mAudioManager);
|
||||
|
||||
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.IActivityManager;
|
||||
import android.app.IUriGrantsManager;
|
||||
@@ -140,7 +141,8 @@ public class RoleObserverTest extends UiServiceTestCase {
|
||||
mock(UsageStatsManagerInternal.class),
|
||||
mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class),
|
||||
mock(UriGrantsManagerInternal.class),
|
||||
mock(AppOpsManager.class), mUm);
|
||||
mock(AppOpsManager.class), mUm,
|
||||
mock(ActivityManagerInternal.class));
|
||||
} catch (SecurityException e) {
|
||||
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
|
||||
throw e;
|
||||
|
||||
Reference in New Issue
Block a user