Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java +32 −7 Original line number Original line Diff line number Diff line Loading @@ -48,6 +48,7 @@ import java.util.concurrent.atomic.AtomicInteger; */ */ public class NotificationInflater { public class NotificationInflater { public static final String TAG = "NotificationInflater"; @VisibleForTesting @VisibleForTesting static final int FLAG_REINFLATE_ALL = ~0; static final int FLAG_REINFLATE_ALL = ~0; private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0; private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0; Loading Loading @@ -315,7 +316,8 @@ public class NotificationInflater { return cancellationSignal; return cancellationSignal; } } private static void applyRemoteView(final InflationProgress result, @VisibleForTesting static void applyRemoteView(final InflationProgress result, final int reInflateFlags, int inflationId, final int reInflateFlags, int inflationId, final ExpandableNotificationRow row, final ExpandableNotificationRow row, final boolean redactAmbient, boolean isNewView, final boolean redactAmbient, boolean isNewView, Loading @@ -325,6 +327,7 @@ public class NotificationInflater { NotificationViewWrapper existingWrapper, NotificationViewWrapper existingWrapper, final HashMap<Integer, CancellationSignal> runningInflations, final HashMap<Integer, CancellationSignal> runningInflations, ApplyCallback applyCallback) { ApplyCallback applyCallback) { RemoteViews newContentView = applyCallback.getRemoteView(); RemoteViews.OnViewAppliedListener listener RemoteViews.OnViewAppliedListener listener = new RemoteViews.OnViewAppliedListener() { = new RemoteViews.OnViewAppliedListener() { Loading @@ -343,12 +346,31 @@ public class NotificationInflater { @Override @Override public void onError(Exception e) { 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); runningInflations.remove(inflationId); handleInflationError(runningInflations, e, entry.notification, callback); handleInflationError(runningInflations, e, entry.notification, callback); } } } }; }; CancellationSignal cancellationSignal; CancellationSignal cancellationSignal; RemoteViews newContentView = applyCallback.getRemoteView(); if (isNewView) { if (isNewView) { cancellationSignal = newContentView.applyAsync( cancellationSignal = newContentView.applyAsync( result.packageContext, result.packageContext, Loading Loading @@ -620,14 +642,16 @@ public class NotificationInflater { } } } } private static class InflationProgress { @VisibleForTesting static class InflationProgress { private RemoteViews newContentView; private RemoteViews newContentView; private RemoteViews newHeadsUpView; private RemoteViews newHeadsUpView; private RemoteViews newExpandedView; private RemoteViews newExpandedView; private RemoteViews newAmbientView; private RemoteViews newAmbientView; private RemoteViews newPublicView; private RemoteViews newPublicView; private Context packageContext; @VisibleForTesting Context packageContext; private View inflatedContentView; private View inflatedContentView; private View inflatedHeadsUpView; private View inflatedHeadsUpView; Loading @@ -636,7 +660,8 @@ public class NotificationInflater { private View inflatedPublicView; private View inflatedPublicView; } } private abstract static class ApplyCallback { @VisibleForTesting abstract static class ApplyCallback { public abstract void setResultView(View v); public abstract void setResultView(View v); public abstract RemoteViews getRemoteView(); public abstract RemoteViews getRemoteView(); } } Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java +68 −0 Original line number Original line Diff line number Diff line Loading @@ -24,12 +24,17 @@ import static org.mockito.Mockito.verify; import android.app.Notification; import android.app.Notification; import android.content.Context; import android.content.Context; import android.os.CancellationSignal; import android.os.Handler; import android.os.Looper; import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification; import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry; import android.support.test.annotation.UiThreadTest; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.FlakyTest; import android.support.test.filters.FlakyTest; import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4; import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; import android.widget.RemoteViews; import com.android.systemui.R; import com.android.systemui.R; Loading @@ -45,7 +50,9 @@ import org.junit.Ignore; import org.junit.Test; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runner.RunWith; import java.util.HashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @SmallTest @SmallTest @RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class) Loading Loading @@ -142,6 +149,41 @@ public class NotificationInflaterTest extends SysuiTestCase { Assert.assertNull(mRow.getEntry().getRunningTask()); 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 @Test public void testSupersedesExistingTask() throws Exception { public void testSupersedesExistingTask() throws Exception { Loading Loading @@ -200,4 +242,30 @@ public class NotificationInflaterTest extends SysuiTestCase { mException = exception; 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); } } } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java +32 −7 Original line number Original line Diff line number Diff line Loading @@ -48,6 +48,7 @@ import java.util.concurrent.atomic.AtomicInteger; */ */ public class NotificationInflater { public class NotificationInflater { public static final String TAG = "NotificationInflater"; @VisibleForTesting @VisibleForTesting static final int FLAG_REINFLATE_ALL = ~0; static final int FLAG_REINFLATE_ALL = ~0; private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0; private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0; Loading Loading @@ -315,7 +316,8 @@ public class NotificationInflater { return cancellationSignal; return cancellationSignal; } } private static void applyRemoteView(final InflationProgress result, @VisibleForTesting static void applyRemoteView(final InflationProgress result, final int reInflateFlags, int inflationId, final int reInflateFlags, int inflationId, final ExpandableNotificationRow row, final ExpandableNotificationRow row, final boolean redactAmbient, boolean isNewView, final boolean redactAmbient, boolean isNewView, Loading @@ -325,6 +327,7 @@ public class NotificationInflater { NotificationViewWrapper existingWrapper, NotificationViewWrapper existingWrapper, final HashMap<Integer, CancellationSignal> runningInflations, final HashMap<Integer, CancellationSignal> runningInflations, ApplyCallback applyCallback) { ApplyCallback applyCallback) { RemoteViews newContentView = applyCallback.getRemoteView(); RemoteViews.OnViewAppliedListener listener RemoteViews.OnViewAppliedListener listener = new RemoteViews.OnViewAppliedListener() { = new RemoteViews.OnViewAppliedListener() { Loading @@ -343,12 +346,31 @@ public class NotificationInflater { @Override @Override public void onError(Exception e) { 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); runningInflations.remove(inflationId); handleInflationError(runningInflations, e, entry.notification, callback); handleInflationError(runningInflations, e, entry.notification, callback); } } } }; }; CancellationSignal cancellationSignal; CancellationSignal cancellationSignal; RemoteViews newContentView = applyCallback.getRemoteView(); if (isNewView) { if (isNewView) { cancellationSignal = newContentView.applyAsync( cancellationSignal = newContentView.applyAsync( result.packageContext, result.packageContext, Loading Loading @@ -620,14 +642,16 @@ public class NotificationInflater { } } } } private static class InflationProgress { @VisibleForTesting static class InflationProgress { private RemoteViews newContentView; private RemoteViews newContentView; private RemoteViews newHeadsUpView; private RemoteViews newHeadsUpView; private RemoteViews newExpandedView; private RemoteViews newExpandedView; private RemoteViews newAmbientView; private RemoteViews newAmbientView; private RemoteViews newPublicView; private RemoteViews newPublicView; private Context packageContext; @VisibleForTesting Context packageContext; private View inflatedContentView; private View inflatedContentView; private View inflatedHeadsUpView; private View inflatedHeadsUpView; Loading @@ -636,7 +660,8 @@ public class NotificationInflater { private View inflatedPublicView; private View inflatedPublicView; } } private abstract static class ApplyCallback { @VisibleForTesting abstract static class ApplyCallback { public abstract void setResultView(View v); public abstract void setResultView(View v); public abstract RemoteViews getRemoteView(); public abstract RemoteViews getRemoteView(); } } Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java +68 −0 Original line number Original line Diff line number Diff line Loading @@ -24,12 +24,17 @@ import static org.mockito.Mockito.verify; import android.app.Notification; import android.app.Notification; import android.content.Context; import android.content.Context; import android.os.CancellationSignal; import android.os.Handler; import android.os.Looper; import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification; import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry; import android.support.test.annotation.UiThreadTest; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.FlakyTest; import android.support.test.filters.FlakyTest; import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4; import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; import android.widget.RemoteViews; import com.android.systemui.R; import com.android.systemui.R; Loading @@ -45,7 +50,9 @@ import org.junit.Ignore; import org.junit.Test; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runner.RunWith; import java.util.HashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @SmallTest @SmallTest @RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class) Loading Loading @@ -142,6 +149,41 @@ public class NotificationInflaterTest extends SysuiTestCase { Assert.assertNull(mRow.getEntry().getRunningTask()); 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 @Test public void testSupersedesExistingTask() throws Exception { public void testSupersedesExistingTask() throws Exception { Loading Loading @@ -200,4 +242,30 @@ public class NotificationInflaterTest extends SysuiTestCase { mException = exception; 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); } } } }