summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/res/res/values/config.xml8
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java59
-rw-r--r--services/core/java/com/android/server/notification/NotificationUsageStats.java18
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java100
5 files changed, 179 insertions, 10 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f515b2dd45f0..ce52680329f4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4210,6 +4210,14 @@
one bar higher than they actually are -->
<bool name="config_inflateSignalStrength">false</bool>
+ <!-- Trigger a warning for notifications with RemoteView objects that are larger in bytes than
+ this value (default 1MB)-->
+ <integer name="config_notificationWarnRemoteViewSizeBytes">1000000</integer>
+
+ <!-- Strip notification RemoteView objects that are larger in bytes than this value (also log)
+ (default 2MB) -->
+ <integer name="config_notificationStripRemoteViewSizeBytes">2000000</integer>
+
<!-- Sharesheet: define a max number of targets per application for new shortcuts-based direct share introduced in Q -->
<integer name="config_maxShortcutTargetsPerApp">3</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6c908aac1ee8..9c0ef78123a0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3823,5 +3823,9 @@
<java-symbol type="string" name="config_defaultSupervisionProfileOwnerComponent" />
<java-symbol type="bool" name="config_inflateSignalStrength" />
+
+ <java-symbol type="integer" name="config_notificationWarnRemoteViewSizeBytes" />
+ <java-symbol type="integer" name="config_notificationStripRemoteViewSizeBytes" />
+
<java-symbol type="string" name="config_factoryResetPackage" />
</resources>
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f032fa6959ef..819a1d9d2648 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -207,6 +207,7 @@ import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.widget.RemoteViews;
import android.widget.Toast;
import com.android.internal.R;
@@ -463,6 +464,9 @@ public class NotificationManagerService extends SystemService {
private boolean mIsAutomotive;
private boolean mNotificationEffectsEnabledForAutomotive;
+ private int mWarnRemoteViewsSizeBytes;
+ private int mStripRemoteViewsSizeBytes;
+
private MetricsLogger mMetricsLogger;
private TriPredicate<String, Integer, String> mAllowedManagedServicePackages;
@@ -1723,6 +1727,11 @@ public class NotificationManagerService extends SystemService {
mZenModeHelper.setPriorityOnlyDndExemptPackages(getContext().getResources().getStringArray(
com.android.internal.R.array.config_priorityOnlyDndExemptPackages));
+
+ mWarnRemoteViewsSizeBytes = getContext().getResources().getInteger(
+ com.android.internal.R.integer.config_notificationWarnRemoteViewSizeBytes);
+ mStripRemoteViewsSizeBytes = getContext().getResources().getInteger(
+ com.android.internal.R.integer.config_notificationStripRemoteViewSizeBytes);
}
@Override
@@ -4712,7 +4721,7 @@ public class NotificationManagerService extends SystemService {
// Fix the notification as best we can.
try {
- fixNotification(notification, pkg, userId);
+ fixNotification(notification, pkg, tag, id, userId);
} catch (NameNotFoundException e) {
Slog.e(TAG, "Cannot create a context for sending app", e);
@@ -4817,8 +4826,8 @@ public class NotificationManagerService extends SystemService {
}
@VisibleForTesting
- protected void fixNotification(Notification notification, String pkg, int userId)
- throws NameNotFoundException {
+ protected void fixNotification(Notification notification, String pkg, String tag, int id,
+ int userId) throws NameNotFoundException {
final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
(userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
@@ -4841,6 +4850,50 @@ public class NotificationManagerService extends SystemService {
": Use of fullScreenIntent requires the USE_FULL_SCREEN_INTENT permission");
}
}
+
+ // Remote views? Are they too big?
+ checkRemoteViews(pkg, tag, id, notification);
+ }
+
+ private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
+ if (removeRemoteView(pkg, tag, id, notification.contentView)) {
+ notification.contentView = null;
+ }
+ if (removeRemoteView(pkg, tag, id, notification.bigContentView)) {
+ notification.bigContentView = null;
+ }
+ if (removeRemoteView(pkg, tag, id, notification.headsUpContentView)) {
+ notification.headsUpContentView = null;
+ }
+ if (notification.publicVersion != null) {
+ if (removeRemoteView(pkg, tag, id, notification.publicVersion.contentView)) {
+ notification.publicVersion.contentView = null;
+ }
+ if (removeRemoteView(pkg, tag, id, notification.publicVersion.bigContentView)) {
+ notification.publicVersion.bigContentView = null;
+ }
+ if (removeRemoteView(pkg, tag, id, notification.publicVersion.headsUpContentView)) {
+ notification.publicVersion.headsUpContentView = null;
+ }
+ }
+ }
+
+ private boolean removeRemoteView(String pkg, String tag, int id, RemoteViews contentView) {
+ if (contentView == null) {
+ return false;
+ }
+ final int contentViewSize = contentView.estimateMemoryUsage();
+ if (contentViewSize > mWarnRemoteViewsSizeBytes
+ && contentViewSize < mStripRemoteViewsSizeBytes) {
+ Slog.w(TAG, "RemoteViews too large on tag: " + tag + " id: " + id
+ + " this might be stripped in a future release");
+ }
+ if (contentViewSize >= mStripRemoteViewsSizeBytes) {
+ mUsageStats.registerImageRemoved(pkg);
+ Slog.w(TAG, "Removed too large RemoteViews on tag: " + tag + " id: " + id);
+ return true;
+ }
+ return false;
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index dd393b8846f0..fe3d0eb3e469 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -41,7 +41,6 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.PrintWriter;
-import java.lang.Math;
import java.util.ArrayDeque;
import java.util.Calendar;
import java.util.GregorianCalendar;
@@ -263,6 +262,17 @@ public class NotificationUsageStats {
}
}
+ /**
+ * Call this when RemoteViews object has been removed from a notification because the images
+ * it contains are too big (even after rescaling).
+ */
+ public synchronized void registerImageRemoved(String packageName) {
+ AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(packageName);
+ for (AggregatedStats stats : aggregatedStatsArray) {
+ stats.numImagesRemoved++;
+ }
+ }
+
// Locked by this.
private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) {
return getAggregatedStatsLocked(record.sbn.getPackageName());
@@ -405,6 +415,7 @@ public class NotificationUsageStats {
public int numAlertViolations;
public int numQuotaViolations;
public long mLastAccessTime;
+ public int numImagesRemoved;
public AggregatedStats(Context context, String key) {
this.key = key;
@@ -529,6 +540,7 @@ public class NotificationUsageStats {
maybeCount("note_over_rate", (numRateViolations - previous.numRateViolations));
maybeCount("note_over_alert_rate", (numAlertViolations - previous.numAlertViolations));
maybeCount("note_over_quota", (numQuotaViolations - previous.numQuotaViolations));
+ maybeCount("note_images_removed", (numImagesRemoved - previous.numImagesRemoved));
noisyImportance.maybeCount(previous.noisyImportance);
quietImportance.maybeCount(previous.quietImportance);
finalImportance.maybeCount(previous.finalImportance);
@@ -562,6 +574,7 @@ public class NotificationUsageStats {
previous.numRateViolations = numRateViolations;
previous.numAlertViolations = numAlertViolations;
previous.numQuotaViolations = numQuotaViolations;
+ previous.numImagesRemoved = numImagesRemoved;
noisyImportance.update(previous.noisyImportance);
quietImportance.update(previous.quietImportance);
finalImportance.update(previous.finalImportance);
@@ -667,6 +680,8 @@ public class NotificationUsageStats {
output.append("numAlertViolations=").append(numAlertViolations).append("\n");
output.append(indentPlusTwo);
output.append("numQuotaViolations=").append(numQuotaViolations).append("\n");
+ output.append(indentPlusTwo);
+ output.append("numImagesRemoved=").append(numImagesRemoved).append("\n");
output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n");
output.append(indentPlusTwo).append(quietImportance.toString()).append("\n");
output.append(indentPlusTwo).append(finalImportance.toString()).append("\n");
@@ -709,6 +724,7 @@ public class NotificationUsageStats {
maybePut(dump, "numQuotaLViolations", numQuotaViolations);
maybePut(dump, "notificationEnqueueRate", getEnqueueRate());
maybePut(dump, "numAlertViolations", numAlertViolations);
+ maybePut(dump, "numImagesRemoved", numImagesRemoved);
noisyImportance.maybePut(dump, previous.noisyImportance);
quietImportance.maybePut(dump, previous.quietImportance);
finalImportance.maybePut(dump, previous.finalImportance);
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 9ecf19806f7f..f14e8d216cab 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -132,6 +132,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Xml;
+import android.widget.RemoteViews;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
@@ -174,6 +175,7 @@ import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -348,12 +350,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mHandler = mService.new WorkerHandler(mTestableLooper.getLooper());
// MockPackageManager - default returns ApplicationInfo with matching calling UID
mContext.setMockPackageManager(mPackageManagerClient);
- final ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.uid = mUid;
+
when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt()))
- .thenReturn(applicationInfo);
+ .thenAnswer((Answer<ApplicationInfo>) invocation -> {
+ Object[] args = invocation.getArguments();
+ return getApplicationInfo((String) args[0], mUid);
+ });
when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
- .thenReturn(applicationInfo);
+ .thenAnswer((Answer<ApplicationInfo>) invocation -> {
+ Object[] args = invocation.getArguments();
+ return getApplicationInfo((String) args[0], mUid);
+ });
when(mPackageManagerClient.getPackageUidAsUser(any(), anyInt())).thenReturn(mUid);
final LightsManager mockLightsManager = mock(LightsManager.class);
when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class));
@@ -389,7 +396,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
-
mService.init(mTestableLooper.getLooper(),
mPackageManager, mPackageManagerClient, mockLightsManager,
mListeners, mAssistants, mConditionProviders,
@@ -413,12 +419,32 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@After
public void tearDown() throws Exception {
- mFile.delete();
+ if (mFile != null) mFile.delete();
clearDeviceConfig();
InstrumentationRegistry.getInstrumentation()
.getUiAutomation().dropShellPermissionIdentity();
}
+ private ApplicationInfo getApplicationInfo(String pkg, int uid) {
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = uid;
+ switch (pkg) {
+ case PKG_N_MR1:
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+ break;
+ case PKG_O:
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
+ break;
+ case PKG_P:
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
+ break;
+ default:
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ break;
+ }
+ return applicationInfo;
+ }
+
public void waitForIdle() {
mTestableLooper.processAllMessages();
}
@@ -5122,4 +5148,66 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertEquals(1, notifsAfter.length);
assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0);
}
+
+ @Test
+ public void testRemoveLargeRemoteViews() throws Exception {
+ int removeSize = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_notificationStripRemoteViewSizeBytes);
+
+ RemoteViews rv = mock(RemoteViews.class);
+ when(rv.estimateMemoryUsage()).thenReturn(removeSize);
+ when(rv.clone()).thenReturn(rv);
+ RemoteViews rv1 = mock(RemoteViews.class);
+ when(rv1.estimateMemoryUsage()).thenReturn(removeSize);
+ when(rv1.clone()).thenReturn(rv1);
+ RemoteViews rv2 = mock(RemoteViews.class);
+ when(rv2.estimateMemoryUsage()).thenReturn(removeSize);
+ when(rv2.clone()).thenReturn(rv2);
+ RemoteViews rv3 = mock(RemoteViews.class);
+ when(rv3.estimateMemoryUsage()).thenReturn(removeSize);
+ when(rv3.clone()).thenReturn(rv3);
+ RemoteViews rv4 = mock(RemoteViews.class);
+ when(rv4.estimateMemoryUsage()).thenReturn(removeSize);
+ when(rv4.clone()).thenReturn(rv4);
+ // note: different!
+ RemoteViews rv5 = mock(RemoteViews.class);
+ when(rv5.estimateMemoryUsage()).thenReturn(removeSize - 1);
+ when(rv5.clone()).thenReturn(rv5);
+
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomContentView(rv)
+ .setCustomBigContentView(rv1)
+ .setCustomHeadsUpContentView(rv2)
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomContentView(rv3)
+ .setCustomBigContentView(rv4)
+ .setCustomHeadsUpContentView(rv5)
+ .setPublicVersion(np)
+ .build();
+
+ assertNotNull(np.contentView);
+ assertNotNull(np.bigContentView);
+ assertNotNull(np.headsUpContentView);
+
+ assertTrue(n.publicVersion.extras.containsKey(Notification.EXTRA_CONTAINS_CUSTOM_VIEW));
+ assertNotNull(n.publicVersion.contentView);
+ assertNotNull(n.publicVersion.bigContentView);
+ assertNotNull(n.publicVersion.headsUpContentView);
+
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ assertNull(n.contentView);
+ assertNull(n.bigContentView);
+ assertNotNull(n.headsUpContentView);
+ assertNull(n.publicVersion.contentView);
+ assertNull(n.publicVersion.bigContentView);
+ assertNull(n.publicVersion.headsUpContentView);
+
+ verify(mUsageStats, times(5)).registerImageRemoved(PKG);
+ }
}