Add inflation error manager
Move hasInflationError into a state manager class and put relevant
error logging and filtering logic as listeners on the state.
Test: manual
Test: atest SystemUITests
Change-Id: I669eabbb06248c0866aba6323342eb2c4fd73db5
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index 3fa1954..b5c81b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -151,10 +151,6 @@
.append(" ");
}
- if (notifEntry.hasInflationError()) {
- rksb.append("(!)hasInflationError ");
- }
-
if (notifEntry.getDismissState() != NOT_DISMISSED) {
rksb.append("dismissState=")
.append(notifEntry.getDismissState())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 9272e51b..83f56cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -18,9 +18,7 @@
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-import android.os.RemoteException;
import android.service.notification.NotificationStats;
-import android.service.notification.StatusBarNotification;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
@@ -29,6 +27,7 @@
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
import javax.inject.Inject;
@@ -44,6 +43,7 @@
private final IStatusBarService mStatusBarService;
private final NotifCollection mNotifCollection;
+ private final NotifInflationErrorManager mNotifErrorManager;
private NotificationRowBinderImpl mNotificationRowBinder;
private InflationCallback mExternalInflationCallback;
@@ -51,9 +51,11 @@
@Inject
public NotifInflaterImpl(
IStatusBarService statusBarService,
- NotifCollection notifCollection) {
+ NotifCollection notifCollection,
+ NotifInflationErrorManager errorManager) {
mStatusBarService = statusBarService;
mNotifCollection = notifCollection;
+ mNotifErrorManager = errorManager;
}
/**
@@ -81,7 +83,6 @@
@Override
public void inflateViews(NotificationEntry entry) {
try {
- entry.setHasInflationError(false);
requireBinder().inflateViews(entry, getDismissCallback(entry));
} catch (InflationException e) {
// logged in mInflationCallback.handleInflationException
@@ -131,25 +132,12 @@
public void handleInflationException(
NotificationEntry entry,
Exception e) {
- entry.setHasInflationError(true);
- try {
- final StatusBarNotification sbn = entry.getSbn();
- // report notification inflation errors back up
- // to notification delegates
- mStatusBarService.onNotificationError(
- sbn.getPackageName(),
- sbn.getTag(),
- sbn.getId(),
- sbn.getUid(),
- sbn.getInitialPid(),
- e.getMessage(),
- sbn.getUserId());
- } catch (RemoteException ex) {
- }
+ mNotifErrorManager.setInflationError(entry, e);
}
@Override
public void onAsyncInflationFinished(NotificationEntry entry) {
+ mNotifErrorManager.clearInflationError(entry);
if (mExternalInflationCallback != null) {
mExternalInflationCallback.onInflationFinished(entry);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 006d40d..f482d37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -122,9 +122,6 @@
*/
@CancellationReason int mCancellationReason = REASON_NOT_CANCELED;
- /** @see #hasInflationError() */
- private boolean mHasInflationError;
-
/** @see #getDismissState() */
@NonNull private DismissState mDismissState = DismissState.NOT_DISMISSED;
@@ -274,23 +271,6 @@
*/
/**
- * Whether this notification had an error when attempting to inflate. This is only used in
- * the NewNotifPipeline
- */
- public boolean hasInflationError() {
- return mHasInflationError;
- }
-
- /**
- * Set whether the notification has an error while inflating.
- *
- * TODO: Move this into an inflation error manager class.
- */
- public void setHasInflationError(boolean hasError) {
- mHasInflationError = hasError;
- }
-
- /**
* Set if the user has dismissed this notif but we haven't yet heard back from system server to
* confirm the dismissal.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 1e5946a..1c8fdac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -16,12 +16,18 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import android.os.RemoteException;
+import android.service.notification.StatusBarNotification;
+
+import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
import java.util.ArrayList;
import java.util.List;
@@ -34,7 +40,7 @@
* Aborts inflation when a notification is removed.
*
* If a notification is not done inflating, this coordinator will filter the notification out
- * from the NotifListBuilder.
+ * from the {@link ShadeListBuilder}.
*/
@Singleton
public class PreparationCoordinator implements Coordinator {
@@ -42,15 +48,22 @@
private final PreparationCoordinatorLogger mLogger;
private final NotifInflater mNotifInflater;
+ private final NotifInflationErrorManager mNotifErrorManager;
private final List<NotificationEntry> mPendingNotifications = new ArrayList<>();
+ private final IStatusBarService mStatusBarService;
@Inject
public PreparationCoordinator(
PreparationCoordinatorLogger logger,
- NotifInflaterImpl notifInflater) {
+ NotifInflaterImpl notifInflater,
+ NotifInflationErrorManager errorManager,
+ IStatusBarService service) {
mLogger = logger;
mNotifInflater = notifInflater;
mNotifInflater.setInflationCallback(mInflationCallback);
+ mNotifErrorManager = errorManager;
+ mNotifErrorManager.addInflationErrorListener(mInflationErrorListener);
+ mStatusBarService = service;
}
@Override
@@ -84,8 +97,7 @@
*/
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
- if (entry.hasInflationError()) {
- mPendingNotifications.remove(entry);
+ if (mNotifErrorManager.hasInflationError(entry)) {
return true;
}
return false;
@@ -112,6 +124,34 @@
}
};
+ private final NotifInflationErrorManager.NotifInflationErrorListener mInflationErrorListener =
+ new NotifInflationErrorManager.NotifInflationErrorListener() {
+ @Override
+ public void onNotifInflationError(NotificationEntry entry, Exception e) {
+ mPendingNotifications.remove(entry);
+ try {
+ final StatusBarNotification sbn = entry.getSbn();
+ // report notification inflation errors back up
+ // to notification delegates
+ mStatusBarService.onNotificationError(
+ sbn.getPackageName(),
+ sbn.getTag(),
+ sbn.getId(),
+ sbn.getUid(),
+ sbn.getInitialPid(),
+ e.getMessage(),
+ sbn.getUserId());
+ } catch (RemoteException ex) {
+ }
+ mNotifInflationErrorFilter.invalidateList();
+ }
+
+ @Override
+ public void onNotifInflationErrorCleared(NotificationEntry entry) {
+ mNotifInflationErrorFilter.invalidateList();
+ }
+ };
+
private void inflateEntry(NotificationEntry entry, String reason) {
abortInflation(entry, reason);
mPendingNotifications.add(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
new file mode 100644
index 0000000..a3ca084
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 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.row;
+
+import androidx.annotation.NonNull;
+import androidx.collection.ArraySet;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * A manager handling the error state of a notification when it encounters an exception while
+ * inflating. We don't want to show these notifications to the user but may want to keep them
+ * around for logging purposes.
+ */
+@Singleton
+public class NotifInflationErrorManager {
+
+ Set<NotificationEntry> mErroredNotifs = new ArraySet<>();
+ List<NotifInflationErrorListener> mListeners = new ArrayList<>();
+
+ @Inject
+ public NotifInflationErrorManager() { }
+
+ /**
+ * Mark the notification as errored out due to encountering an exception while inflating.
+ *
+ * @param e the exception encountered while inflating
+ */
+ public void setInflationError(NotificationEntry entry, Exception e) {
+ mErroredNotifs.add(entry);
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onNotifInflationError(entry, e);
+ }
+ }
+
+ /**
+ * Notification inflated successfully and is no longer errored out.
+ */
+ public void clearInflationError(NotificationEntry entry) {
+ if (mErroredNotifs.contains(entry)) {
+ mErroredNotifs.remove(entry);
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onNotifInflationErrorCleared(entry);
+ }
+ }
+ }
+
+ /**
+ * Whether or not the notification encountered an exception while inflating.
+ */
+ public boolean hasInflationError(@NonNull NotificationEntry entry) {
+ return mErroredNotifs.contains(entry);
+ }
+
+ /**
+ * Add listener for changes in inflation error state.
+ */
+ public void addInflationErrorListener(NotifInflationErrorListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Listener for changes in notification inflation error state.
+ */
+ public interface NotifInflationErrorListener {
+
+ /**
+ * Called when notification encounters an inflation exception.
+ *
+ * @param e the exception encountered while inflating
+ */
+ void onNotifInflationError(NotificationEntry entry, Exception e);
+
+ /**
+ * Called when notification inflation error is cleared.
+ */
+ default void onNotifInflationErrorCleared(NotificationEntry entry) {}
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index f124179..f783245 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -18,12 +18,8 @@
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
-import android.os.RemoteException;
-import android.service.notification.StatusBarNotification;
-
import androidx.annotation.NonNull;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
@@ -41,14 +37,14 @@
@Singleton
public class RowContentBindStage extends BindStage<RowContentBindParams> {
private final NotificationRowContentBinder mBinder;
- private final IStatusBarService mStatusBarService;
+ private final NotifInflationErrorManager mNotifInflationErrorManager;
@Inject
RowContentBindStage(
NotificationRowContentBinder binder,
- IStatusBarService statusBarService) {
+ NotifInflationErrorManager errorManager) {
mBinder = binder;
- mStatusBarService = statusBarService;
+ mNotifInflationErrorManager = errorManager;
}
@Override
@@ -78,24 +74,12 @@
InflationCallback inflationCallback = new InflationCallback() {
@Override
public void handleInflationException(NotificationEntry entry, Exception e) {
- entry.setHasInflationError(true);
- try {
- final StatusBarNotification sbn = entry.getSbn();
- mStatusBarService.onNotificationError(
- sbn.getPackageName(),
- sbn.getTag(),
- sbn.getId(),
- sbn.getUid(),
- sbn.getInitialPid(),
- e.getMessage(),
- sbn.getUserId());
- } catch (RemoteException ex) {
- }
+ mNotifInflationErrorManager.setInflationError(entry, e);
}
@Override
public void onAsyncInflationFinished(NotificationEntry entry) {
- entry.setHasInflationError(false);
+ mNotifInflationErrorManager.clearInflationError(entry);
getStageParams(entry).clearDirtyContentViews();
callback.onStageFinished(entry);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
new file mode 100644
index 0000000..61a4fbe
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 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.collection.coordinator;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class PreparationCoordinatorTest extends SysuiTestCase {
+ private static final String TEST_MESSAGE = "TEST_MESSAGE";
+
+ private PreparationCoordinator mCoordinator;
+ private NotifFilter mInflationErrorFilter;
+ private NotifInflationErrorManager mErrorManager;
+ private NotificationEntry mEntry;
+ private Exception mInflationError;
+
+ @Mock
+ private NotifPipeline mNotifPipeline;
+ @Mock private IStatusBarService mService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mEntry = new NotificationEntryBuilder().build();
+ mInflationError = new Exception(TEST_MESSAGE);
+ mErrorManager = new NotifInflationErrorManager();
+
+ mCoordinator = new PreparationCoordinator(
+ mock(PreparationCoordinatorLogger.class),
+ mock(NotifInflaterImpl.class),
+ mErrorManager,
+ mService);
+
+ ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
+ mCoordinator.attach(mNotifPipeline);
+ verify(mNotifPipeline, times(2)).addPreRenderFilter(filterCaptor.capture());
+ List<NotifFilter> filters = filterCaptor.getAllValues();
+ mInflationErrorFilter = filters.get(0);
+ }
+
+ @Test
+ public void testErrorLogsToService() throws RemoteException {
+ // WHEN an entry has an inflation error.
+ mErrorManager.setInflationError(mEntry, mInflationError);
+
+ // THEN we log to status bar service.
+ verify(mService).onNotificationError(
+ eq(mEntry.getSbn().getPackageName()),
+ eq(mEntry.getSbn().getTag()),
+ eq(mEntry.getSbn().getId()),
+ eq(mEntry.getSbn().getUid()),
+ eq(mEntry.getSbn().getInitialPid()),
+ eq(mInflationError.getMessage()),
+ eq(mEntry.getSbn().getUserId()));
+ }
+
+ @Test
+ public void testFiltersOutErroredNotifications() {
+ // WHEN an entry has an inflation error.
+ mErrorManager.setInflationError(mEntry, mInflationError);
+
+ // THEN we filter it from the notification list.
+ assertTrue(mInflationErrorFilter.shouldFilterOut(mEntry, 0));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 35b5508..2319758 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -41,7 +41,6 @@
import android.view.LayoutInflater;
import android.widget.RemoteViews;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.TestableDependency;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.bubbles.BubblesTestActivity;
@@ -112,7 +111,7 @@
mock(NotifRemoteViewCache.class),
mock(NotificationRemoteInputManager.class));
contentBinder.setInflateSynchronously(true);
- mBindStage = new RowContentBindStage(contentBinder, mock(IStatusBarService.class));
+ mBindStage = new RowContentBindStage(contentBinder, mock(NotifInflationErrorManager.class));
NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
index 775f722..d9fe655 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
@@ -34,7 +34,6 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
@@ -62,7 +61,7 @@
MockitoAnnotations.initMocks(this);
mRowContentBindStage = new RowContentBindStage(mBinder,
- mock(IStatusBarService.class));
+ mock(NotifInflationErrorManager.class));
mRowContentBindStage.createStageParams(mEntry);
}