Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d246bed1 authored by Selim Cinek's avatar Selim Cinek
Browse files

Retrying the remoteview application on the ui thread

Because of various reasons, the async inflation could spuriously
fail, leading to dropped notifications.
We're now retrying them on the UI thread before we fail completely.

Test: runtest -x packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
Bug: 38190555
Change-Id: I13feaeaad925b72798a22194add16528c9438412
parent c06746ce
Loading
Loading
Loading
Loading
+32 −7
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 */
public class NotificationInflater {

    public static final String TAG = "NotificationInflater";
    @VisibleForTesting
    static final int FLAG_REINFLATE_ALL = ~0;
    private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0;
@@ -315,7 +316,8 @@ public class NotificationInflater {
        return cancellationSignal;
    }

    private static void applyRemoteView(final InflationProgress result,
    @VisibleForTesting
    static void applyRemoteView(final InflationProgress result,
            final int reInflateFlags, int inflationId,
            final ExpandableNotificationRow row,
            final boolean redactAmbient, boolean isNewView,
@@ -325,6 +327,7 @@ public class NotificationInflater {
            NotificationViewWrapper existingWrapper,
            final HashMap<Integer, CancellationSignal> runningInflations,
            ApplyCallback applyCallback) {
        RemoteViews newContentView = applyCallback.getRemoteView();
        RemoteViews.OnViewAppliedListener listener
                = new RemoteViews.OnViewAppliedListener() {

@@ -343,12 +346,31 @@ public class NotificationInflater {

            @Override
            public void onError(Exception e) {
                // 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.
                try {
                    View newView = existingView;
                    if (isNewView) {
                        newView = newContentView.apply(
                                result.packageContext,
                                parentLayout,
                                remoteViewClickHandler);
                    } else {
                        newContentView.reapply(
                                result.packageContext,
                                existingView,
                                remoteViewClickHandler);
                    }
                    Log.wtf(TAG, "Async Inflation failed but normal inflation finished normally.",
                            e);
                    onViewApplied(newView);
                } catch (Exception anotherException) {
                    runningInflations.remove(inflationId);
                    handleInflationError(runningInflations, e, entry.notification, callback);
                }
            }
        };
        CancellationSignal cancellationSignal;
        RemoteViews newContentView = applyCallback.getRemoteView();
        if (isNewView) {
            cancellationSignal = newContentView.applyAsync(
                    result.packageContext,
@@ -620,14 +642,16 @@ public class NotificationInflater {
        }
    }

    private static class InflationProgress {
    @VisibleForTesting
    static class InflationProgress {
        private RemoteViews newContentView;
        private RemoteViews newHeadsUpView;
        private RemoteViews newExpandedView;
        private RemoteViews newAmbientView;
        private RemoteViews newPublicView;

        private Context packageContext;
        @VisibleForTesting
        Context packageContext;

        private View inflatedContentView;
        private View inflatedHeadsUpView;
@@ -636,7 +660,8 @@ public class NotificationInflater {
        private View inflatedPublicView;
    }

    private abstract static class ApplyCallback {
    @VisibleForTesting
    abstract static class ApplyCallback {
        public abstract void setResultView(View v);
        public abstract RemoteViews getRemoteView();
    }
+68 −0
Original line number Diff line number Diff line
@@ -24,12 +24,17 @@ import static org.mockito.Mockito.verify;

import android.app.Notification;
import android.content.Context;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RemoteViews;

import com.android.systemui.R;
@@ -45,7 +50,9 @@ import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;

@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -141,6 +148,41 @@ public class NotificationInflaterTest extends SysuiTestCase {
        Assert.assertNull(mRow.getEntry().getRunningTask());
    }

    @Test
    public void testInflationIsRetriedIfAsyncFails() throws Exception {
        NotificationInflater.InflationProgress result =
                new NotificationInflater.InflationProgress();
        result.packageContext = mContext;
        CountDownLatch countDownLatch = new CountDownLatch(1);
        NotificationInflater.applyRemoteView(result,
                NotificationInflater.FLAG_REINFLATE_EXPANDED_VIEW, 0, mRow,
                false /* redactAmbient */, true /* isNewView */, new RemoteViews.OnClickHandler(),
                new NotificationInflater.InflationCallback() {
                    @Override
                    public void handleInflationException(StatusBarNotification notification,
                            Exception e) {
                        countDownLatch.countDown();
                        throw new RuntimeException("No Exception expected");
                    }

                    @Override
                    public void onAsyncInflationFinished(NotificationData.Entry entry) {
                        countDownLatch.countDown();
                    }
                }, mRow.getEntry(), mRow.getPrivateLayout(), null, null, new HashMap<>(),
                new NotificationInflater.ApplyCallback() {
                    @Override
                    public void setResultView(View v) {
                    }

                    @Override
                    public RemoteViews getRemoteView() {
                        return new AsyncFailRemoteView(mContext.getPackageName(),
                                R.layout.custom_view_dark);
                    }
                });
        countDownLatch.await();
    }

    @Test
    public void testSupersedesExistingTask() throws Exception {
@@ -199,4 +241,30 @@ public class NotificationInflaterTest extends SysuiTestCase {
            mException = exception;
        }
    }

    private class AsyncFailRemoteView extends RemoteViews {
        Handler mHandler = new Handler(Looper.getMainLooper());

        public AsyncFailRemoteView(String packageName, int layoutId) {
            super(packageName, layoutId);
        }

        @Override
        public View apply(Context context, ViewGroup parent) {
            return super.apply(context, parent);
        }

        @Override
        public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor,
                OnViewAppliedListener listener, OnClickHandler handler) {
            mHandler.post(() -> listener.onError(new RuntimeException("Failed to inflate async")));
            return new CancellationSignal();
        }

        @Override
        public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor,
                OnViewAppliedListener listener) {
            return applyAsync(context, parent, executor, listener, null);
        }
    }
}