summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityManagerInternal.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java10
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java40
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java16
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java35
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java1
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java7
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java4
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;