Merge "DO NOT MERGE - Disallow deletion of channels with FGS notifications" into rvc-qpr-dev

This commit is contained in:
Chris Tate
2021-04-06 16:22:21 +00:00
committed by Android (Google) Code Review
6 changed files with 114 additions and 11 deletions

View File

@@ -377,6 +377,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, @UserIdInt 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, @UserIdInt int userId,
String channelId);
/**
* Registers the specified {@code processObserver} to be notified of future changes to
* process state.

View File

@@ -119,6 +119,7 @@ import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
@@ -433,6 +434,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) {

View File

@@ -19728,6 +19728,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);

View File

@@ -403,6 +403,7 @@ public class NotificationManagerService extends SystemService {
private IActivityManager mAm;
private ActivityTaskManagerInternal mAtm;
private ActivityManager mActivityManager;
private ActivityManagerInternal mAmi;
private IPackageManager mPackageManager;
private PackageManager mPackageManagerClient;
AudioManager mAudioManager;
@@ -1876,7 +1877,7 @@ public class NotificationManagerService extends SystemService {
DevicePolicyManagerInternal dpm, IUriGrantsManager ugm,
UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager,
NotificationHistoryManager historyManager, StatsManager statsManager,
TelephonyManager telephonyManager) {
TelephonyManager telephonyManager, ActivityManagerInternal ami) {
mHandler = handler;
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -1897,6 +1898,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;
@@ -2119,7 +2121,8 @@ public class NotificationManagerService extends SystemService {
new NotificationHistoryManager(getContext(), handler),
mStatsManager = (StatsManager) getContext().getSystemService(
Context.STATS_MANAGER),
getContext().getSystemService(TelephonyManager.class));
getContext().getSystemService(TelephonyManager.class),
LocalServices.getService(ActivityManagerInternal.class));
// register for various Intents
IntentFilter filter = new IntentFilter();
@@ -3405,15 +3408,30 @@ public class NotificationManagerService extends SystemService {
pkg, uid, channelId, conversationId, true, 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)) {
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),
@@ -3426,19 +3444,23 @@ public class NotificationManagerService extends SystemService {
public void deleteConversationNotificationChannels(String pkg, int uid,
String conversationId) {
checkCallerIsSystem();
final int callingUid = Binder.getCallingUid();
List<NotificationChannel> channels =
mPreferencesHelper.getNotificationChannelsByConversationId(
pkg, uid, conversationId);
if (!channels.isEmpty()) {
// Preflight for fg service notifications in these channels: do nothing
// unless they're all eligible
final int appUserId = UserHandle.getUserId(uid);
for (NotificationChannel nc : channels) {
final String channelId = nc.getId();
mAmi.stopForegroundServicesForChannel(pkg, appUserId, channelId);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true,
UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, nc.getId());
appUserId, REASON_CHANNEL_BANNED, null);
mPreferencesHelper.deleteNotificationChannel(pkg, uid, channelId);
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(callingUid),
UserHandle.getUserHandleForUid(uid),
mPreferencesHelper.getNotificationChannel(
pkg, callingUid, nc.getId(), true),
pkg, uid, channelId, true),
NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
}
handleSavePolicyFile();
@@ -3469,13 +3491,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),

View File

@@ -505,7 +505,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mGroupHelper, mAm, mAtm, mAppUsageStats,
mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
mAppOpsManager, mUm, mHistoryManager, mStatsManager,
mock(TelephonyManager.class));
mock(TelephonyManager.class),
mock(ActivityManagerInternal.class));
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mService.setAudioManager(mAudioManager);

View File

@@ -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;
@@ -156,7 +157,8 @@ public class RoleObserverTest extends UiServiceTestCase {
mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class),
mock(UriGrantsManagerInternal.class),
mock(AppOpsManager.class), mUm, mock(NotificationHistoryManager.class),
mock(StatsManager.class), mock(TelephonyManager.class));
mock(StatsManager.class), mock(TelephonyManager.class),
mock(ActivityManagerInternal.class));
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;