Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomViewMemorySizeExceededException.kt 0 → 100644 +19 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification /** Thrown when a custom notification view exceeds memory limit. */ class CustomViewMemorySizeExceededException(error: String) : InflationException(error) packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +45 −27 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.statusbar.notification.CustomViewMemorySizeExceededException; import com.android.systemui.statusbar.notification.collection.BundleEntry; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; Loading Loading @@ -256,6 +257,22 @@ public class PreparationCoordinator implements Coordinator { new NotifInflationErrorListener() { @Override public void onNotifInflationError(NotificationEntry entry, Exception e) { // If the notification views exceeded their memory restriction, we strip // it down to the basic template and reinflate it in that basic form. if (e instanceof CustomViewMemorySizeExceededException // Prevent endless loop if no custom views are present. && entry.containsCustomViews()) { // "lighten" strips out all notification custom views, large bitmaps and // other extras. entry.getSbn().getNotification().lightenPayload(); // Clear the error state and trigger reinflation of changed notification. mNotifErrorManager.clearInflationError(entry); mNotifCollectionListener.onEntryUpdated(entry); mNotifInflationErrorFilter.invalidateList( "reinflate for MemorySizeExceeded for " + logKey(entry)); return; } mViewBarn.removeViewForEntry(entry); mInflationStates.put(entry, STATE_ERROR); try { Loading @@ -273,7 +290,8 @@ public class PreparationCoordinator implements Coordinator { } catch (RemoteException ex) { // System server is dead, nothing to do about that } mNotifInflationErrorFilter.invalidateList("onNotifInflationError for " + logKey(entry)); mNotifInflationErrorFilter.invalidateList( "onNotifInflationError for " + logKey(entry)); } @Override Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +36 −6 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import android.widget.RemoteViews; import com.android.app.tracing.TraceUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ImageMessageConsumer; import com.android.server.notification.Flags; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.NotifInflation; import com.android.systemui.media.controls.util.MediaFeatureFlag; Loading @@ -54,6 +55,7 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.ConversationNotificationProcessor; import com.android.systemui.statusbar.notification.CustomViewMemorySizeExceededException; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NmSummarizationUiFlag; import com.android.systemui.statusbar.notification.NotificationUtils; Loading Loading @@ -834,6 +836,34 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Override public void onViewApplied(View v) { if (Flags.notificationCustomViewUriRestriction()) { onViewAppliedUsingException(v); } else { onViewAppliedLegacy(v); } } private void onViewAppliedUsingException(View v) { try { validateView(v, entry, row.getResources()); if (isNewView) { applyCallback.setResultView(v); } else if (existingWrapper != null) { existingWrapper.onReinflated(); } } catch (InflationException e) { runningInflations.remove(inflationId); handleInflationError(runningInflations, e, row, entry, callback, logger, "applied invalid view"); return; } runningInflations.remove(inflationId); finishIfDone(result, isMinimized, reInflateFlags, remoteViewCache, runningInflations, callback, entry, row, logger); } private void onViewAppliedLegacy(View v) { String invalidReason = isValidView(v, entry, row.getResources()); if (invalidReason != null) { handleInflationError(runningInflations, new InflationException(invalidReason), Loading Loading @@ -912,12 +942,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder return "inflated notification does not meet minimum height requirement"; } if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) { if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) { return "inflated notification does not meet maximum memory size requirement"; } } return null; } Loading Loading @@ -966,6 +990,12 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (invalidReason != null) { throw new InflationException(invalidReason); } if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) { if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) { throw new CustomViewMemorySizeExceededException("Custom view memory size exceeded"); } } } private static void handleInflationError( Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +59 −11 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.widget.RemoteViews.OnViewAppliedListener import com.android.app.tracing.TraceUtils import com.android.internal.annotations.VisibleForTesting import com.android.internal.widget.ImageMessageConsumer import com.android.server.notification.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.NotifInflation import com.android.systemui.res.R Loading @@ -47,6 +48,7 @@ import com.android.systemui.statusbar.InflationTask import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_OTP import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.notification.ConversationNotificationProcessor import com.android.systemui.statusbar.notification.CustomViewMemorySizeExceededException import com.android.systemui.statusbar.notification.InflationException import com.android.systemui.statusbar.notification.NmSummarizationUiFlag import com.android.systemui.statusbar.notification.collection.NotificationEntry Loading Loading @@ -525,7 +527,7 @@ constructor( entry, row, remoteViewClickHandler, this /* callback */, /* callback= */ this, logger, ) } Loading @@ -543,7 +545,10 @@ constructor( Log.e(TAG, "couldn't inflate view for notification $ident", e) callback?.handleInflationException( if (NotificationBundleUi.isEnabled) entry else row.entryLegacy, InflationException("Couldn't inflate contentViews$e"), when (e) { is InflationException -> e else -> InflationException("Couldn't inflate contentViews: $e") }, ) // Cancel any image loading tasks, not useful any more Loading Loading @@ -1318,6 +1323,14 @@ constructor( } override fun onViewApplied(v: View) { if (Flags.notificationCustomViewUriRestriction()) { onViewAppliedUsingException(v) } else { onViewAppliedLegacy(v) } } private fun onViewAppliedLegacy(v: View) { val invalidReason = isValidView(v, entry, row.resources) if (invalidReason != null) { handleInflationError( Loading Loading @@ -1351,11 +1364,46 @@ constructor( ) } private fun onViewAppliedUsingException(v: View) { try { validateView(v, entry, row.resources) if (isNewView) { applyCallback.setResultView(v) } else { existingWrapper?.onReinflated() } } catch (e: InflationException) { runningInflations.remove(inflationId) handleInflationError( runningInflations, e, row, entry, callback, logger, "applied invalid view", ) return } runningInflations.remove(inflationId) finishIfDone( result, isMinimized, reInflateFlags, remoteViewCache, runningInflations, callback, entry, row, logger, ) } override fun onError(e: Exception) { // Uh oh the async inflation failed. Due to some bugs (see b/38190555), this // could // actually also be a system issue, so let's try on the UI thread again to // be safe. // could actually also be a system issue, so let's try on the UI thread // again to be safe. try { val newView = if (isNewView) { Loading Loading @@ -1424,12 +1472,6 @@ constructor( return "inflated notification does not meet minimum height requirement" } if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) { if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) { return "inflated notification does not meet maximum memory size requirement" } } return null } Loading Loading @@ -1482,6 +1524,12 @@ constructor( if (invalidReason != null) { throw InflationException(invalidReason) } if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) { if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) { throw CustomViewMemorySizeExceededException("Custom view memory size exceeded") } } } private fun handleInflationError( Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +67 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE; import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer; import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; Loading @@ -39,10 +41,12 @@ import static java.util.Objects.requireNonNull; import android.app.Flags; import android.database.ContentObserver; import android.graphics.Bitmap; import android.os.Handler; import android.os.RemoteException; import android.platform.test.annotations.EnableFlags; import android.testing.TestableLooper; import android.widget.RemoteViews; import androidx.annotation.NonNull; import androidx.test.ext.junit.runners.AndroidJUnit4; Loading @@ -53,6 +57,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.notification.CustomViewMemorySizeExceededException; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.ListEntry; Loading Loading @@ -84,6 +89,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; Loading Loading @@ -226,6 +232,67 @@ public class PreparationCoordinatorTest extends SysuiTestCase { assertTrue(mInflationErrorFilter.shouldFilterOut(mEntry, 0)); } @Test public void testMemorySizeExceeded_reinflatesStandardTemplate() { NotificationEntryBuilder eb = getNotificationEntryBuilder() .setParent(ROOT_ENTRY); eb.modifyNotification(mContext) .setLargeIcon(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888)) .setCustomContentView( new RemoteViews(mContext.getPackageName(), android.R.layout.list_content)) .build(); NotificationEntry entry = eb.build(); // Preconditions check. assertThat(entry.getSbn().getNotification().getLargeIcon()).isNotNull(); assertThat(entry.getSbn().getNotification().contentView).isNotNull(); mCollectionListener.onEntryInit(entry); mErrorManager.setInflationError(entry, new CustomViewMemorySizeExceededException("Exception")); Mockito.reset(mNotifInflater); // Trigger reinflation. mBeforeFilterListener.onBeforeFinalizeFilter(List.of(entry)); // Verify that the notification was stripped. assertThat(entry.getSbn().getNotification().getLargeIcon()).isNull(); assertThat(entry.getSbn().getNotification().contentView).isNull(); // We'll reinflate the notification so DO NOT call the NMS with error report. verifyNoMoreInteractions(mService); // Notification should be reinflated. verify(mNotifInflater).inflateViews(eq(entry), any(), any()); // And NOT skipped by error filter. assertThat(mInflationErrorFilter.shouldFilterOut(entry, 0)).isFalse(); } // Prevent endless reinflations if the notification doesn't have custom views. @Test public void testMemorySizeExceeded_dontReinflateNotificationsWithoutCustomViews() throws RemoteException { NotificationEntryBuilder eb = getNotificationEntryBuilder() .setParent(ROOT_ENTRY); eb.modifyNotification(mContext) .setLargeIcon(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888)) .build(); NotificationEntry entry = eb.build(); // Preconditions check. assertThat(entry.getSbn().getNotification().getLargeIcon()).isNotNull(); Exception exception = new CustomViewMemorySizeExceededException("Exception"); mCollectionListener.onEntryInit(entry); mErrorManager.setInflationError(entry, exception); Mockito.reset(mNotifInflater); // Verify that NMS was signaled with error and no reinflation was attempted. verify(mService).onNotificationError( eq(mEntry.getSbn().getPackageName()), eq(mEntry.getSbn().getTag()), eq(mEntry.getSbn().getId()), eq(mEntry.getSbn().getUid()), eq(mEntry.getSbn().getInitialPid()), eq(exception.getMessage()), eq(mEntry.getSbn().getUser().getIdentifier())); verifyNoMoreInteractions(mNotifInflater); } @Test @EnableFlags(Flags.FLAG_NOTIFICATIONS_REDESIGN_APP_ICONS) public void testPurgesAppIconProviderCache() { Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomViewMemorySizeExceededException.kt 0 → 100644 +19 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification /** Thrown when a custom notification view exceeds memory limit. */ class CustomViewMemorySizeExceededException(error: String) : InflationException(error)
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +45 −27 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.statusbar.notification.CustomViewMemorySizeExceededException; import com.android.systemui.statusbar.notification.collection.BundleEntry; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; Loading Loading @@ -256,6 +257,22 @@ public class PreparationCoordinator implements Coordinator { new NotifInflationErrorListener() { @Override public void onNotifInflationError(NotificationEntry entry, Exception e) { // If the notification views exceeded their memory restriction, we strip // it down to the basic template and reinflate it in that basic form. if (e instanceof CustomViewMemorySizeExceededException // Prevent endless loop if no custom views are present. && entry.containsCustomViews()) { // "lighten" strips out all notification custom views, large bitmaps and // other extras. entry.getSbn().getNotification().lightenPayload(); // Clear the error state and trigger reinflation of changed notification. mNotifErrorManager.clearInflationError(entry); mNotifCollectionListener.onEntryUpdated(entry); mNotifInflationErrorFilter.invalidateList( "reinflate for MemorySizeExceeded for " + logKey(entry)); return; } mViewBarn.removeViewForEntry(entry); mInflationStates.put(entry, STATE_ERROR); try { Loading @@ -273,7 +290,8 @@ public class PreparationCoordinator implements Coordinator { } catch (RemoteException ex) { // System server is dead, nothing to do about that } mNotifInflationErrorFilter.invalidateList("onNotifInflationError for " + logKey(entry)); mNotifInflationErrorFilter.invalidateList( "onNotifInflationError for " + logKey(entry)); } @Override Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +36 −6 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import android.widget.RemoteViews; import com.android.app.tracing.TraceUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ImageMessageConsumer; import com.android.server.notification.Flags; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.NotifInflation; import com.android.systemui.media.controls.util.MediaFeatureFlag; Loading @@ -54,6 +55,7 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.ConversationNotificationProcessor; import com.android.systemui.statusbar.notification.CustomViewMemorySizeExceededException; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NmSummarizationUiFlag; import com.android.systemui.statusbar.notification.NotificationUtils; Loading Loading @@ -834,6 +836,34 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Override public void onViewApplied(View v) { if (Flags.notificationCustomViewUriRestriction()) { onViewAppliedUsingException(v); } else { onViewAppliedLegacy(v); } } private void onViewAppliedUsingException(View v) { try { validateView(v, entry, row.getResources()); if (isNewView) { applyCallback.setResultView(v); } else if (existingWrapper != null) { existingWrapper.onReinflated(); } } catch (InflationException e) { runningInflations.remove(inflationId); handleInflationError(runningInflations, e, row, entry, callback, logger, "applied invalid view"); return; } runningInflations.remove(inflationId); finishIfDone(result, isMinimized, reInflateFlags, remoteViewCache, runningInflations, callback, entry, row, logger); } private void onViewAppliedLegacy(View v) { String invalidReason = isValidView(v, entry, row.getResources()); if (invalidReason != null) { handleInflationError(runningInflations, new InflationException(invalidReason), Loading Loading @@ -912,12 +942,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder return "inflated notification does not meet minimum height requirement"; } if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) { if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) { return "inflated notification does not meet maximum memory size requirement"; } } return null; } Loading Loading @@ -966,6 +990,12 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (invalidReason != null) { throw new InflationException(invalidReason); } if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) { if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) { throw new CustomViewMemorySizeExceededException("Custom view memory size exceeded"); } } } private static void handleInflationError( Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +59 −11 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.widget.RemoteViews.OnViewAppliedListener import com.android.app.tracing.TraceUtils import com.android.internal.annotations.VisibleForTesting import com.android.internal.widget.ImageMessageConsumer import com.android.server.notification.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.NotifInflation import com.android.systemui.res.R Loading @@ -47,6 +48,7 @@ import com.android.systemui.statusbar.InflationTask import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_OTP import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.notification.ConversationNotificationProcessor import com.android.systemui.statusbar.notification.CustomViewMemorySizeExceededException import com.android.systemui.statusbar.notification.InflationException import com.android.systemui.statusbar.notification.NmSummarizationUiFlag import com.android.systemui.statusbar.notification.collection.NotificationEntry Loading Loading @@ -525,7 +527,7 @@ constructor( entry, row, remoteViewClickHandler, this /* callback */, /* callback= */ this, logger, ) } Loading @@ -543,7 +545,10 @@ constructor( Log.e(TAG, "couldn't inflate view for notification $ident", e) callback?.handleInflationException( if (NotificationBundleUi.isEnabled) entry else row.entryLegacy, InflationException("Couldn't inflate contentViews$e"), when (e) { is InflationException -> e else -> InflationException("Couldn't inflate contentViews: $e") }, ) // Cancel any image loading tasks, not useful any more Loading Loading @@ -1318,6 +1323,14 @@ constructor( } override fun onViewApplied(v: View) { if (Flags.notificationCustomViewUriRestriction()) { onViewAppliedUsingException(v) } else { onViewAppliedLegacy(v) } } private fun onViewAppliedLegacy(v: View) { val invalidReason = isValidView(v, entry, row.resources) if (invalidReason != null) { handleInflationError( Loading Loading @@ -1351,11 +1364,46 @@ constructor( ) } private fun onViewAppliedUsingException(v: View) { try { validateView(v, entry, row.resources) if (isNewView) { applyCallback.setResultView(v) } else { existingWrapper?.onReinflated() } } catch (e: InflationException) { runningInflations.remove(inflationId) handleInflationError( runningInflations, e, row, entry, callback, logger, "applied invalid view", ) return } runningInflations.remove(inflationId) finishIfDone( result, isMinimized, reInflateFlags, remoteViewCache, runningInflations, callback, entry, row, logger, ) } override fun onError(e: Exception) { // Uh oh the async inflation failed. Due to some bugs (see b/38190555), this // could // actually also be a system issue, so let's try on the UI thread again to // be safe. // could actually also be a system issue, so let's try on the UI thread // again to be safe. try { val newView = if (isNewView) { Loading Loading @@ -1424,12 +1472,6 @@ constructor( return "inflated notification does not meet minimum height requirement" } if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) { if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) { return "inflated notification does not meet maximum memory size requirement" } } return null } Loading Loading @@ -1482,6 +1524,12 @@ constructor( if (invalidReason != null) { throw InflationException(invalidReason) } if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) { if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) { throw CustomViewMemorySizeExceededException("Custom view memory size exceeded") } } } private fun handleInflationError( Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +67 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE; import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer; import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; Loading @@ -39,10 +41,12 @@ import static java.util.Objects.requireNonNull; import android.app.Flags; import android.database.ContentObserver; import android.graphics.Bitmap; import android.os.Handler; import android.os.RemoteException; import android.platform.test.annotations.EnableFlags; import android.testing.TestableLooper; import android.widget.RemoteViews; import androidx.annotation.NonNull; import androidx.test.ext.junit.runners.AndroidJUnit4; Loading @@ -53,6 +57,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.notification.CustomViewMemorySizeExceededException; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.ListEntry; Loading Loading @@ -84,6 +89,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; Loading Loading @@ -226,6 +232,67 @@ public class PreparationCoordinatorTest extends SysuiTestCase { assertTrue(mInflationErrorFilter.shouldFilterOut(mEntry, 0)); } @Test public void testMemorySizeExceeded_reinflatesStandardTemplate() { NotificationEntryBuilder eb = getNotificationEntryBuilder() .setParent(ROOT_ENTRY); eb.modifyNotification(mContext) .setLargeIcon(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888)) .setCustomContentView( new RemoteViews(mContext.getPackageName(), android.R.layout.list_content)) .build(); NotificationEntry entry = eb.build(); // Preconditions check. assertThat(entry.getSbn().getNotification().getLargeIcon()).isNotNull(); assertThat(entry.getSbn().getNotification().contentView).isNotNull(); mCollectionListener.onEntryInit(entry); mErrorManager.setInflationError(entry, new CustomViewMemorySizeExceededException("Exception")); Mockito.reset(mNotifInflater); // Trigger reinflation. mBeforeFilterListener.onBeforeFinalizeFilter(List.of(entry)); // Verify that the notification was stripped. assertThat(entry.getSbn().getNotification().getLargeIcon()).isNull(); assertThat(entry.getSbn().getNotification().contentView).isNull(); // We'll reinflate the notification so DO NOT call the NMS with error report. verifyNoMoreInteractions(mService); // Notification should be reinflated. verify(mNotifInflater).inflateViews(eq(entry), any(), any()); // And NOT skipped by error filter. assertThat(mInflationErrorFilter.shouldFilterOut(entry, 0)).isFalse(); } // Prevent endless reinflations if the notification doesn't have custom views. @Test public void testMemorySizeExceeded_dontReinflateNotificationsWithoutCustomViews() throws RemoteException { NotificationEntryBuilder eb = getNotificationEntryBuilder() .setParent(ROOT_ENTRY); eb.modifyNotification(mContext) .setLargeIcon(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888)) .build(); NotificationEntry entry = eb.build(); // Preconditions check. assertThat(entry.getSbn().getNotification().getLargeIcon()).isNotNull(); Exception exception = new CustomViewMemorySizeExceededException("Exception"); mCollectionListener.onEntryInit(entry); mErrorManager.setInflationError(entry, exception); Mockito.reset(mNotifInflater); // Verify that NMS was signaled with error and no reinflation was attempted. verify(mService).onNotificationError( eq(mEntry.getSbn().getPackageName()), eq(mEntry.getSbn().getTag()), eq(mEntry.getSbn().getId()), eq(mEntry.getSbn().getUid()), eq(mEntry.getSbn().getInitialPid()), eq(exception.getMessage()), eq(mEntry.getSbn().getUser().getIdentifier())); verifyNoMoreInteractions(mNotifInflater); } @Test @EnableFlags(Flags.FLAG_NOTIFICATIONS_REDESIGN_APP_ICONS) public void testPurgesAppIconProviderCache() { Loading