diff options
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); + } } |