diff options
9 files changed, 149 insertions, 6 deletions
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 8508c2c95666..87b41f9c53f7 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -347,6 +347,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. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 6552fe671794..82fb57649fbd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -33,6 +33,7 @@ import android.graphics.Color; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Parcelable; @@ -83,6 +84,16 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi public static final int STATE_DOT = 1; public static final int STATE_HIDDEN = 2; + /** + * Maximum allowed byte count for an icon bitmap + * @see android.graphics.RecordingCanvas.MAX_BITMAP_SIZE + */ + private static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024; // 100 MB + /** + * Maximum allowed width or height for an icon drawable, if we can't get byte count + */ + private static final int MAX_IMAGE_SIZE = 5000; + private static final String TAG = "StatusBarIconView"; private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT = new FloatProperty<StatusBarIconView>("iconAppearAmount") { @@ -377,6 +388,22 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon); return false; } + + if (drawable instanceof BitmapDrawable && ((BitmapDrawable) drawable).getBitmap() != null) { + // If it's a bitmap we can check the size directly + int byteCount = ((BitmapDrawable) drawable).getBitmap().getByteCount(); + if (byteCount > MAX_BITMAP_SIZE) { + Log.w(TAG, "Drawable is too large (" + byteCount + " bytes) " + mIcon); + return false; + } + } else if (drawable.getIntrinsicWidth() > MAX_IMAGE_SIZE + || drawable.getIntrinsicHeight() > MAX_IMAGE_SIZE) { + // Otherwise, check dimensions + Log.w(TAG, "Drawable is too large (" + drawable.getIntrinsicWidth() + "x" + + drawable.getIntrinsicHeight() + ") " + mIcon); + return false; + } + if (withClear) { setImageDrawable(null); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java index 9971e0cf81a3..edafa6549027 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java @@ -35,6 +35,7 @@ import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.Icon; import android.os.UserHandle; @@ -123,4 +124,13 @@ public class StatusBarIconViewTest extends SysuiTestCase { assertEquals("Transparent backgrounds should fallback to drawable color", color, mIconView.getStaticDrawableColor()); } + + @Test + public void testGiantImageNotAllowed() { + Bitmap largeBitmap = Bitmap.createBitmap(6000, 6000, Bitmap.Config.ARGB_8888); + Icon icon = Icon.createWithBitmap(largeBitmap); + StatusBarIcon largeIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage", + icon, 0, 0, ""); + assertFalse(mIconView.set(largeIcon)); + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 5d72828964c7..60ba53223e26 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -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) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index eabb68e89e77..aa879f9ee728 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -18508,6 +18508,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 registerProcessObserver(IProcessObserver processObserver) { ActivityManagerService.this.registerProcessObserver(processObserver); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index dbd7573f475f..0bb89b776070 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -354,6 +354,7 @@ public class NotificationManagerService extends SystemService { private IActivityManager mAm; private ActivityManager mActivityManager; + private ActivityManagerInternal mAmi; private IPackageManager mPackageManager; private PackageManager mPackageManagerClient; AudioManager mAudioManager; @@ -1594,7 +1595,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, @@ -1613,6 +1614,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; @@ -1776,7 +1778,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(); @@ -2710,15 +2713,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), @@ -2750,13 +2770,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), diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index d571990b758f..8de0d661e005 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -6329,6 +6329,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public List<String> getAllPackages() { + enforceSystemOrRootOrShell("getAllPackages is limited to privileged callers"); final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandle.getUserId(callingUid); synchronized (mPackages) { 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 504e53197680..9750cfc804fa 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -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; @@ -209,6 +210,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); @@ -332,6 +335,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()); @@ -390,7 +395,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); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java index f37ff1177fe9..7f7a9ce85cdc 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java @@ -33,6 +33,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; |