From 01d3da63cef1f82db182c6995264bf3ea3371dcc Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Fri, 28 Apr 2017 15:03:48 -0700 Subject: Moving the inflation to the background The inflation of the notifications is moved to the background. This should improve the general performance when adding / removing groups and alike. Test: runtest systemui Fixes: 34888292 Change-Id: Ieb19a09a5a97d496d9319d917a5e317a3ad76fc4 --- .../systemui/statusbar/NotificationData.java | 22 +- .../notification/NotificationInflater.java | 502 +++++++++++++++------ .../systemui/statusbar/phone/StatusBar.java | 2 +- .../notification/NotificationInflaterTest.java | 10 +- 4 files changed, 391 insertions(+), 145 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 540c39150188..64f36c61111e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -86,7 +86,7 @@ public class NotificationData { public List snoozeCriteria; private int mCachedContrastColor = COLOR_INVALID; private int mCachedContrastColorIsFor = COLOR_INVALID; - private ArraySet mRunningTasks = new ArraySet(); + private NotificationInflater.AsyncInflationTask mRunningTask = null; public Entry(StatusBarNotification n) { this.key = n.getKey(); @@ -219,23 +219,25 @@ public class NotificationData { * Abort all existing inflation tasks */ public void abortInflation() { - for (AsyncTask task : mRunningTasks) { - task.cancel(true /* mayInterruptIfRunning */); + if (mRunningTask != null) { + mRunningTask.abort(); + mRunningTask = null; } - mRunningTasks.clear(); } - public void addInflationTask(AsyncTask asyncInflationTask) { - mRunningTasks.add(asyncInflationTask); + public void setInflationTask(NotificationInflater.AsyncInflationTask asyncInflationTask) { + // abort any existing inflation + abortInflation(); + mRunningTask = asyncInflationTask; } - public void onInflationTaskFinished(AsyncTask asyncInflationTask) { - mRunningTasks.remove(asyncInflationTask); + public void onInflationTaskFinished() { + mRunningTask = null; } @VisibleForTesting - public ArraySet getRunningTasks() { - return mRunningTasks; + public AsyncTask getRunningTask() { + return mRunningTask; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java index 7cfc767f89b7..a7034c62c8f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java @@ -16,9 +16,11 @@ package com.android.systemui.statusbar.notification; +import android.annotation.Nullable; import android.app.Notification; import android.content.Context; import android.os.AsyncTask; +import android.os.CancellationSignal; import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.View; @@ -29,6 +31,9 @@ import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationContentView; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.util.Assert; + +import java.util.HashMap; /** * A utility that inflates the right kind of contentView based on the state @@ -116,126 +121,303 @@ public class NotificationInflater { @VisibleForTesting void inflateNotificationViews(int reInflateFlags) { StatusBarNotification sbn = mRow.getEntry().notification; - new AsyncInflationTask(mRow.getContext(), sbn, reInflateFlags).execute(); + new AsyncInflationTask(sbn, reInflateFlags, mRow, mIsLowPriority, + mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient, + mCallback, mRemoteViewClickHandler).execute(); } @VisibleForTesting - void inflateNotificationViews(int reInflateFlags, + InflationProgress inflateNotificationViews(int reInflateFlags, Notification.Builder builder, Context packageContext) { - NotificationData.Entry entry = mRow.getEntry(); - NotificationContentView privateLayout = mRow.getPrivateLayout(); - NotificationContentView publicLayout = mRow.getPublicLayout(); + InflationProgress result = createRemoteViews(reInflateFlags, builder, mIsLowPriority, + mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, + mRedactAmbient, packageContext); + apply(result, reInflateFlags, mRow, mRedactAmbient, mRemoteViewClickHandler, null); + return result; + } - boolean isLowPriority = mIsLowPriority && !mIsChildInGroup; + private static InflationProgress createRemoteViews(int reInflateFlags, + Notification.Builder builder, boolean isLowPriority, boolean isChildInGroup, + boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient, + Context packageContext) { + InflationProgress result = new InflationProgress(); + isLowPriority = isLowPriority && !isChildInGroup; if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) { - final RemoteViews newContentView = createContentView(builder, - isLowPriority, mUsesIncreasedHeight); - if (!compareRemoteViews(newContentView, - entry.cachedContentView)) { - View contentViewLocal = newContentView.apply( - packageContext, - privateLayout, - mRemoteViewClickHandler); - contentViewLocal.setIsRootNamespace(true); - privateLayout.setContractedChild(contentViewLocal); - } else { - newContentView.reapply(packageContext, - privateLayout.getContractedChild(), - mRemoteViewClickHandler); - } - entry.cachedContentView = newContentView; + result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight); } if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) { - final RemoteViews newBigContentView = createBigContentView( - builder, isLowPriority); - if (newBigContentView != null) { - if (!compareRemoteViews(newBigContentView, entry.cachedBigContentView)) { - View bigContentViewLocal = newBigContentView.apply( - packageContext, - privateLayout, - mRemoteViewClickHandler); - bigContentViewLocal.setIsRootNamespace(true); - privateLayout.setExpandedChild(bigContentViewLocal); - } else { - newBigContentView.reapply(packageContext, - privateLayout.getExpandedChild(), - mRemoteViewClickHandler); - } - } else if (entry.cachedBigContentView != null) { - privateLayout.setExpandedChild(null); - } - entry.cachedBigContentView = newBigContentView; - mRow.setExpandable(newBigContentView != null); + result.newExpandedView = createExpandedView(builder, isLowPriority); } if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) { - final RemoteViews newHeadsUpContentView = - builder.createHeadsUpContentView(mUsesIncreasedHeadsUpHeight); - if (newHeadsUpContentView != null) { - if (!compareRemoteViews(newHeadsUpContentView, - entry.cachedHeadsUpContentView)) { - View headsUpContentViewLocal = newHeadsUpContentView.apply( - packageContext, - privateLayout, - mRemoteViewClickHandler); - headsUpContentViewLocal.setIsRootNamespace(true); - privateLayout.setHeadsUpChild(headsUpContentViewLocal); - } else { - newHeadsUpContentView.reapply(packageContext, - privateLayout.getHeadsUpChild(), - mRemoteViewClickHandler); - } - } else if (entry.cachedHeadsUpContentView != null) { - privateLayout.setHeadsUpChild(null); - } - entry.cachedHeadsUpContentView = newHeadsUpContentView; + result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight); } if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) { - final RemoteViews newPublicNotification - = builder.makePublicContentView(); - if (!compareRemoteViews(newPublicNotification, entry.cachedPublicContentView)) { - View publicContentView = newPublicNotification.apply( - packageContext, - publicLayout, - mRemoteViewClickHandler); - publicContentView.setIsRootNamespace(true); - publicLayout.setContractedChild(publicContentView); - } else { - newPublicNotification.reapply(packageContext, - publicLayout.getContractedChild(), - mRemoteViewClickHandler); - } - entry.cachedPublicContentView = newPublicNotification; + result.newPublicView = builder.makePublicContentView(); } if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) { - final RemoteViews newAmbientNotification = mRedactAmbient - ? builder.makePublicAmbientNotification() + result.newAmbientView = redactAmbient ? builder.makePublicAmbientNotification() : builder.makeAmbientNotification(); - NotificationContentView newParent = mRedactAmbient ? publicLayout : privateLayout; - NotificationContentView otherParent = !mRedactAmbient ? publicLayout : privateLayout; - - if (newParent.getAmbientChild() == null || - !compareRemoteViews(newAmbientNotification, entry.cachedAmbientContentView)) { - View ambientContentView = newAmbientNotification.apply( - packageContext, - newParent, - mRemoteViewClickHandler); - ambientContentView.setIsRootNamespace(true); - newParent.setAmbientChild(ambientContentView); - otherParent.setAmbientChild(null); - } else { - newAmbientNotification.reapply(packageContext, - newParent.getAmbientChild(), - mRemoteViewClickHandler); + } + result.packageContext = packageContext; + return result; + } + + public static CancellationSignal apply(InflationProgress result, int reInflateFlags, + ExpandableNotificationRow row, boolean redactAmbient, + RemoteViews.OnClickHandler remoteViewClickHandler, + @Nullable InflationCallback callback) { + NotificationData.Entry entry = row.getEntry(); + NotificationContentView privateLayout = row.getPrivateLayout(); + NotificationContentView publicLayout = row.getPublicLayout(); + final HashMap runningInflations = new HashMap<>(); + + int flag = FLAG_REINFLATE_CONTENT_VIEW; + if ((reInflateFlags & flag) != 0) { + boolean isNewView = !compareRemoteViews(result.newContentView, entry.cachedContentView); + ApplyCallback applyCallback = new ApplyCallback() { + @Override + public void setResultView(View v) { + result.inflatedContentView = v; + } + + @Override + public RemoteViews getRemoteView() { + return result.newContentView; + } + }; + applyRemoteView(result, reInflateFlags, flag, row, redactAmbient, + isNewView, remoteViewClickHandler, callback, entry, privateLayout, + privateLayout.getContractedChild(), + runningInflations, applyCallback); + } + + flag = FLAG_REINFLATE_EXPANDED_VIEW; + if ((reInflateFlags & flag) != 0) { + if (result.newExpandedView != null) { + boolean isNewView = !compareRemoteViews(result.newExpandedView, + entry.cachedBigContentView); + ApplyCallback applyCallback = new ApplyCallback() { + @Override + public void setResultView(View v) { + result.inflatedExpandedView = v; + } + + @Override + public RemoteViews getRemoteView() { + return result.newExpandedView; + } + }; + applyRemoteView(result, reInflateFlags, flag, row, + redactAmbient, isNewView, remoteViewClickHandler, callback, entry, + privateLayout, privateLayout.getExpandedChild(), runningInflations, + applyCallback); + } + } + + flag = FLAG_REINFLATE_HEADS_UP_VIEW; + if ((reInflateFlags & flag) != 0) { + if (result.newHeadsUpView != null) { + boolean isNewView = !compareRemoteViews(result.newHeadsUpView, + entry.cachedHeadsUpContentView); + ApplyCallback applyCallback = new ApplyCallback() { + @Override + public void setResultView(View v) { + result.inflatedHeadsUpView = v; + } + + @Override + public RemoteViews getRemoteView() { + return result.newHeadsUpView; + } + }; + applyRemoteView(result, reInflateFlags, flag, row, + redactAmbient, isNewView, remoteViewClickHandler, callback, entry, + privateLayout, privateLayout.getHeadsUpChild(), runningInflations, + applyCallback); } - entry.cachedAmbientContentView = newAmbientNotification; } + + flag = FLAG_REINFLATE_PUBLIC_VIEW; + if ((reInflateFlags & flag) != 0) { + boolean isNewView = !compareRemoteViews(result.newPublicView, + entry.cachedPublicContentView); + ApplyCallback applyCallback = new ApplyCallback() { + @Override + public void setResultView(View v) { + result.inflatedPublicView = v; + } + + @Override + public RemoteViews getRemoteView() { + return result.newPublicView; + } + }; + applyRemoteView(result, reInflateFlags, flag, row, + redactAmbient, isNewView, remoteViewClickHandler, callback, entry, + publicLayout, publicLayout.getContractedChild(), runningInflations, + applyCallback); + } + + flag = FLAG_REINFLATE_AMBIENT_VIEW; + if ((reInflateFlags & flag) != 0) { + NotificationContentView newParent = redactAmbient ? publicLayout : privateLayout; + boolean isNewView = !canReapplyAmbient(row, redactAmbient) || + !compareRemoteViews(result.newAmbientView, entry.cachedAmbientContentView); + ApplyCallback applyCallback = new ApplyCallback() { + @Override + public void setResultView(View v) { + result.inflatedAmbientView = v; + } + + @Override + public RemoteViews getRemoteView() { + return result.newAmbientView; + } + }; + applyRemoteView(result, reInflateFlags, flag, row, + redactAmbient, isNewView, remoteViewClickHandler, callback, entry, + newParent, newParent.getAmbientChild(), runningInflations, + applyCallback); + } + + // Let's try to finish, maybe nobody is even inflating anything + finishIfDone(result, reInflateFlags, runningInflations, callback, row, + redactAmbient); + CancellationSignal cancellationSignal = new CancellationSignal(); + cancellationSignal.setOnCancelListener( + () -> runningInflations.values().forEach(CancellationSignal::cancel)); + return cancellationSignal; + } + + private static void applyRemoteView(final InflationProgress result, + final int reInflateFlags, int inflationId, + final ExpandableNotificationRow row, + final boolean redactAmbient, boolean isNewView, + RemoteViews.OnClickHandler remoteViewClickHandler, + @Nullable final InflationCallback callback, NotificationData.Entry entry, + NotificationContentView parentLayout, View existingView, + final HashMap runningInflations, + ApplyCallback applyCallback) { + RemoteViews.OnViewAppliedListener listener + = new RemoteViews.OnViewAppliedListener() { + + @Override + public void onViewApplied(View v) { + if (isNewView) { + v.setIsRootNamespace(true); + applyCallback.setResultView(v); + } + runningInflations.remove(inflationId); + finishIfDone(result, reInflateFlags, runningInflations, callback, row, + redactAmbient); + } + + @Override + public void onError(Exception e) { + runningInflations.remove(inflationId); + handleInflationError(runningInflations, e, entry.notification, callback); + } + }; + CancellationSignal cancellationSignal; + RemoteViews newContentView = applyCallback.getRemoteView(); + if (isNewView) { + cancellationSignal = newContentView.applyAsync( + result.packageContext, + parentLayout, + null /* executor */, + listener, + remoteViewClickHandler); + } else { + cancellationSignal = newContentView.reapplyAsync( + result.packageContext, + existingView, + null /* executor */, + listener, + remoteViewClickHandler); + } + runningInflations.put(inflationId, cancellationSignal); } - private RemoteViews createBigContentView(Notification.Builder builder, + private static void handleInflationError(HashMap runningInflations, + Exception e, StatusBarNotification notification, @Nullable InflationCallback callback) { + Assert.isMainThread(); + runningInflations.values().forEach(CancellationSignal::cancel); + if (callback != null) { + callback.handleInflationException(notification, e); + } + } + + /** + * Finish the inflation of the views + * + * @return true if the inflation was finished + */ + private static boolean finishIfDone(InflationProgress result, int reInflateFlags, + HashMap runningInflations, + @Nullable InflationCallback endListener, ExpandableNotificationRow row, + boolean redactAmbient) { + Assert.isMainThread(); + NotificationData.Entry entry = row.getEntry(); + NotificationContentView privateLayout = row.getPrivateLayout(); + NotificationContentView publicLayout = row.getPublicLayout(); + if (runningInflations.isEmpty()) { + if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) { + if (result.inflatedContentView != null) { + privateLayout.setContractedChild(result.inflatedContentView); + } + entry.cachedContentView = result.newContentView; + } + + if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) { + if (result.inflatedExpandedView != null) { + privateLayout.setExpandedChild(result.inflatedExpandedView); + } else if (result.newExpandedView == null) { + privateLayout.setExpandedChild(null); + } + entry.cachedBigContentView = result.newExpandedView; + row.setExpandable(result.newExpandedView != null); + } + + if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) { + if (result.inflatedHeadsUpView != null) { + privateLayout.setHeadsUpChild(result.inflatedHeadsUpView); + } else if (result.newHeadsUpView == null) { + privateLayout.setHeadsUpChild(null); + } + entry.cachedHeadsUpContentView = result.newHeadsUpView; + } + + if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) { + if (result.inflatedPublicView != null) { + publicLayout.setContractedChild(result.inflatedPublicView); + } + entry.cachedPublicContentView = result.newPublicView; + } + + if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) { + if (result.inflatedAmbientView != null) { + NotificationContentView newParent = redactAmbient + ? publicLayout : privateLayout; + NotificationContentView otherParent = !redactAmbient + ? publicLayout : privateLayout; + newParent.setAmbientChild(result.inflatedAmbientView); + otherParent.setAmbientChild(null); + } + entry.cachedAmbientContentView = result.newAmbientView; + } + if (endListener != null) { + endListener.onAsyncInflationFinished(row.getEntry()); + } + return true; + } + return false; + } + + private static RemoteViews createExpandedView(Notification.Builder builder, boolean isLowPriority) { RemoteViews bigContentView = builder.createBigContentView(); if (bigContentView != null) { @@ -249,7 +431,7 @@ public class NotificationInflater { return null; } - private RemoteViews createContentView(Notification.Builder builder, + private static RemoteViews createContentView(Notification.Builder builder, boolean isLowPriority, boolean useLarge) { if (isLowPriority) { return builder.makeLowPriorityContentView(false /* useRegularSubtext */); @@ -258,7 +440,7 @@ public class NotificationInflater { } // Returns true if the RemoteViews are the same. - private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) { + private static boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) { return (a == null && b == null) || (a != null && b != null && b.getPackage() != null @@ -272,7 +454,7 @@ public class NotificationInflater { } public interface InflationCallback { - void handleInflationException(StatusBarNotification notification, InflationException e); + void handleInflationException(StatusBarNotification notification, Exception e); void onAsyncInflationFinished(NotificationData.Entry entry); } @@ -286,37 +468,68 @@ public class NotificationInflater { inflateNotificationViews(); } - private class AsyncInflationTask extends AsyncTask { + private static boolean canReapplyAmbient(ExpandableNotificationRow row, boolean redactAmbient) { + NotificationContentView ambientView = redactAmbient ? row.getPublicLayout() + : row.getPrivateLayout(); ; + return ambientView.getAmbientChild() != null; + } + + public static class AsyncInflationTask extends AsyncTask + implements InflationCallback { private final StatusBarNotification mSbn; private final Context mContext; private final int mReInflateFlags; - private Context mPackageContext = null; + private final boolean mIsLowPriority; + private final boolean mIsChildInGroup; + private final boolean mUsesIncreasedHeight; + private final InflationCallback mCallback; + private final boolean mUsesIncreasedHeadsUpHeight; + private final boolean mRedactAmbient; + private ExpandableNotificationRow mRow; private Exception mError; - - private AsyncInflationTask(Context context, StatusBarNotification notification, - int reInflateFlags) { + private RemoteViews.OnClickHandler mRemoteViewClickHandler; + private CancellationSignal mCancellationSignal; + + private AsyncInflationTask(StatusBarNotification notification, + int reInflateFlags, ExpandableNotificationRow row, boolean isLowPriority, + boolean isChildInGroup, boolean usesIncreasedHeight, + boolean usesIncreasedHeadsUpHeight, boolean redactAmbient, + InflationCallback callback, + RemoteViews.OnClickHandler remoteViewClickHandler) { + mRow = row; + NotificationData.Entry entry = row.getEntry(); + entry.setInflationTask(this); mSbn = notification; - mContext = context; mReInflateFlags = reInflateFlags; - mRow.getEntry().addInflationTask(this); + mContext = mRow.getContext(); + mIsLowPriority = isLowPriority; + mIsChildInGroup = isChildInGroup; + mUsesIncreasedHeight = usesIncreasedHeight; + mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight; + mRedactAmbient = redactAmbient; + mRemoteViewClickHandler = remoteViewClickHandler; + mCallback = callback; } @Override - protected Notification.Builder doInBackground(Void... params) { + protected InflationProgress doInBackground(Void... params) { try { final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(mContext, mSbn.getNotification()); - mPackageContext = mSbn.getPackageContext(mContext); + Context packageContext = mSbn.getPackageContext(mContext); Notification notification = mSbn.getNotification(); if (notification.isMediaNotification()) { MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext, - mPackageContext); + packageContext); processor.setIsLowPriority(mIsLowPriority); processor.processNotification(notification, recoveredBuilder); } - return recoveredBuilder; + return createRemoteViews(mReInflateFlags, + recoveredBuilder, mIsLowPriority, mIsChildInGroup, + mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient, + packageContext); } catch (Exception e) { mError = e; return null; @@ -324,34 +537,63 @@ public class NotificationInflater { } @Override - protected void onPostExecute(Notification.Builder builder) { - mRow.getEntry().onInflationTaskFinished(this); + protected void onPostExecute(InflationProgress result) { if (mError == null) { - finishInflation(mReInflateFlags, builder, mPackageContext); + mCancellationSignal = apply(result, mReInflateFlags, mRow, mRedactAmbient, + mRemoteViewClickHandler, this); } else { handleError(mError); } } - } - private void finishInflation(int reinflationFlags, Notification.Builder builder, - Context context) { - try { - inflateNotificationViews(reinflationFlags, builder, context); - } catch (RuntimeException e){ + private void handleError(Exception e) { + mRow.getEntry().onInflationTaskFinished(); + StatusBarNotification sbn = mRow.getStatusBarNotification(); + final String ident = sbn.getPackageName() + "/0x" + + Integer.toHexString(sbn.getId()); + Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e); + mCallback.handleInflationException(sbn, + new InflationException("Couldn't inflate contentViews" + e)); + } + + public void abort() { + cancel(true /* mayInterruptIfRunning */); + if (mCancellationSignal != null) { + mCancellationSignal.cancel(); + } + } + + @Override + public void handleInflationException(StatusBarNotification notification, Exception e) { handleError(e); - return; } - mRow.onNotificationUpdated(); - mCallback.onAsyncInflationFinished(mRow.getEntry()); + + @Override + public void onAsyncInflationFinished(NotificationData.Entry entry) { + mRow.getEntry().onInflationTaskFinished(); + mRow.onNotificationUpdated(); + mCallback.onAsyncInflationFinished(mRow.getEntry()); + } + } + + private static class InflationProgress { + private RemoteViews newContentView; + private RemoteViews newHeadsUpView; + private RemoteViews newExpandedView; + private RemoteViews newAmbientView; + private RemoteViews newPublicView; + + private Context packageContext; + + private View inflatedContentView; + private View inflatedHeadsUpView; + private View inflatedExpandedView; + private View inflatedAmbientView; + private View inflatedPublicView; } - private void handleError(Exception e) { - StatusBarNotification sbn = mRow.getStatusBarNotification(); - final String ident = sbn.getPackageName() + "/0x" - + Integer.toHexString(sbn.getId()); - Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e); - mCallback.handleInflationException(sbn, - new InflationException("Couldn't inflate contentViews" + e)); + private abstract static class ApplyCallback { + public abstract void setResultView(View v); + public abstract RemoteViews getRemoteView(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index c1859fe43e01..2e6af7576aa0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1610,7 +1610,7 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override - public void handleInflationException(StatusBarNotification notification, InflationException e) { + public void handleInflationException(StatusBarNotification notification, Exception e) { handleNotificationError(notification, e.getMessage()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java index fbb25e5484ba..5e8ea9c46356 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java @@ -26,6 +26,7 @@ import android.app.Notification; import android.content.Context; import android.service.notification.StatusBarNotification; import android.support.test.InstrumentationRegistry; +import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.widget.RemoteViews; @@ -41,7 +42,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.concurrent.CountDownLatch; -import java.util.function.Function; @SmallTest @RunWith(AndroidJUnit4.class) @@ -67,7 +67,7 @@ public class NotificationInflaterTest { mNotificationInflater.setInflationCallback(new NotificationInflater.InflationCallback() { @Override public void handleInflationException(StatusBarNotification notification, - InflationException e) { + Exception e) { } @Override @@ -77,6 +77,7 @@ public class NotificationInflaterTest { } @Test + @UiThreadTest public void testIncreasedHeadsUpBeingUsed() { mNotificationInflater.setUsesIncreasedHeadsUpHeight(true); Notification.Builder builder = spy(mBuilder); @@ -85,6 +86,7 @@ public class NotificationInflaterTest { } @Test + @UiThreadTest public void testIncreasedHeightBeingUsed() { mNotificationInflater.setUsesIncreasedHeight(true); Notification.Builder builder = spy(mBuilder); @@ -127,7 +129,7 @@ public class NotificationInflaterTest { mRow.getEntry().abortInflation(); runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(), mNotificationInflater); - Assert.assertTrue(mRow.getEntry().getRunningTasks().size() == 0); + Assert.assertNull(mRow.getEntry().getRunningTask() ); } public static void runThenWaitForInflation(Runnable block, @@ -143,7 +145,7 @@ public class NotificationInflaterTest { inflater.setInflationCallback(new NotificationInflater.InflationCallback() { @Override public void handleInflationException(StatusBarNotification notification, - InflationException e) { + Exception e) { if (!expectingException) { exceptionHolder.setException(e); } -- cgit v1.2.3-59-g8ed1b From 0f66a4cc16ec1a927c90ac559c73c80ddcb5ee71 Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Fri, 28 Apr 2017 19:26:28 -0700 Subject: Moving Row inflation to the background too Previously only the contentview inflation was on the background, now the inflation of the row is too. Test: runtest systemui Bug: 34888292 Change-Id: I3adc6b3311217421c9de5c37794397b8a3fd665d --- packages/SystemUI/Android.mk | 1 + .../com/android/systemui/statusbar/Abortable.java | 24 ++++++ .../systemui/statusbar/NotificationData.java | 14 ++-- .../systemui/statusbar/NotificationMenuRow.java | 3 +- .../notification/NotificationInflater.java | 4 +- .../statusbar/notification/RowInflaterTask.java | 65 ++++++++++++++++ .../systemui/statusbar/phone/StatusBar.java | 88 ++++++++++++---------- packages/SystemUI/tests/Android.mk | 1 + .../notification/NotificationInflaterTest.java | 2 +- 9 files changed, 151 insertions(+), 51 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/Abortable.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk index 5ee0c64c9591..2fd7e87a683e 100644 --- a/packages/SystemUI/Android.mk +++ b/packages/SystemUI/Android.mk @@ -31,6 +31,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-unde LOCAL_STATIC_ANDROID_LIBRARIES := \ SystemUIPluginLib \ + android-support-v4 \ android-support-v7-recyclerview \ android-support-v7-preference \ android-support-v7-appcompat \ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/Abortable.java b/packages/SystemUI/src/com/android/systemui/statusbar/Abortable.java new file mode 100644 index 000000000000..d5ec4f67e82d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/Abortable.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017 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; + +/** + * An interface that allows aborting existing operations. + */ +public interface Abortable { + void abort(); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 64f36c61111e..f9d939cf62fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -33,7 +33,6 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; -import android.util.ArraySet; import android.view.View; import android.widget.ImageView; import android.widget.RemoteViews; @@ -43,7 +42,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.NotificationColorUtil; import com.android.systemui.statusbar.notification.InflationException; -import com.android.systemui.statusbar.notification.NotificationInflater; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -86,7 +84,7 @@ public class NotificationData { public List snoozeCriteria; private int mCachedContrastColor = COLOR_INVALID; private int mCachedContrastColorIsFor = COLOR_INVALID; - private NotificationInflater.AsyncInflationTask mRunningTask = null; + private Abortable mRunningTask = null; public Entry(StatusBarNotification n) { this.key = n.getKey(); @@ -218,17 +216,17 @@ public class NotificationData { /** * Abort all existing inflation tasks */ - public void abortInflation() { + public void abortTask() { if (mRunningTask != null) { mRunningTask.abort(); mRunningTask = null; } } - public void setInflationTask(NotificationInflater.AsyncInflationTask asyncInflationTask) { + public void setInflationTask(Abortable abortableTask) { // abort any existing inflation - abortInflation(); - mRunningTask = asyncInflationTask; + abortTask(); + mRunningTask = abortableTask; } public void onInflationTaskFinished() { @@ -236,7 +234,7 @@ public class NotificationData { } @VisibleForTesting - public AsyncTask getRunningTask() { + public Abortable getRunningTask() { return mRunningTask; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java index 4305bdef6fef..dc538dac6856 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java @@ -35,6 +35,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.os.Looper; import android.util.Log; import android.service.notification.StatusBarNotification; import android.view.LayoutInflater; @@ -107,7 +108,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size); mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height); mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding); - mHandler = new Handler(); + mHandler = new Handler(Looper.getMainLooper()); mMenuItems = new ArrayList<>(); mSnoozeItem = createSnoozeItem(context); mInfoItem = createInfoItem(context); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java index a7034c62c8f1..52f5e1cd5de1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java @@ -27,6 +27,7 @@ import android.view.View; import android.widget.RemoteViews; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.statusbar.Abortable; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationContentView; import com.android.systemui.statusbar.NotificationData; @@ -475,7 +476,7 @@ public class NotificationInflater { } public static class AsyncInflationTask extends AsyncTask - implements InflationCallback { + implements InflationCallback, Abortable { private final StatusBarNotification mSbn; private final Context mContext; @@ -556,6 +557,7 @@ public class NotificationInflater { new InflationException("Couldn't inflate contentViews" + e)); } + @Override public void abort() { cancel(true /* mayInterruptIfRunning */); if (mCancellationSignal != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java new file mode 100644 index 000000000000..1bfc0cc6a6df --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 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; + +import android.content.Context; +import android.support.v4.view.AsyncLayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.R; +import com.android.systemui.statusbar.Abortable; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; + +/** + * An inflater task that asynchronously inflates a ExpandableNotificationRow + */ +public class RowInflaterTask implements Abortable, AsyncLayoutInflater.OnInflateFinishedListener { + private RowInflationFinishedListener mListener; + private NotificationData.Entry mEntry; + private boolean mCancelled; + + /** + * Inflates a new notificationView. This should not be called twice on this object + */ + public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry, + RowInflationFinishedListener listener) { + mListener = listener; + AsyncLayoutInflater inflater = new AsyncLayoutInflater(context); + mEntry = entry; + entry.setInflationTask(this); + inflater.inflate(R.layout.status_bar_notification_row, parent, this); + } + + @Override + public void abort() { + mCancelled = true; + } + + @Override + public void onInflateFinished(View view, int resid, ViewGroup parent) { + if (!mCancelled) { + mEntry.onInflationTaskFinished(); + mListener.onInflationFinished((ExpandableNotificationRow) view); + } + } + + public interface RowInflationFinishedListener { + void onInflationFinished(ExpandableNotificationRow row); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 2e6af7576aa0..6998c1f33af4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -172,6 +172,7 @@ import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.InflationException; +import com.android.systemui.statusbar.notification.RowInflaterTask; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager; import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; @@ -1588,12 +1589,12 @@ public class StatusBar extends SystemUI implements DemoMode, private void abortExistingInflation(String key) { if (mPendingNotifications.containsKey(key)) { Entry entry = mPendingNotifications.get(key); - entry.abortInflation(); + entry.abortTask(); mPendingNotifications.remove(key); } Entry addedEntry = mNotificationData.get(key); if (addedEntry != null) { - addedEntry.abortInflation(); + addedEntry.abortTask(); } } @@ -6173,50 +6174,57 @@ public class StatusBar extends SystemUI implements DemoMode, entry.notification.getUser().getIdentifier()); final StatusBarNotification sbn = entry.notification; - ExpandableNotificationRow row; if (entry.row != null) { - row = entry.row; entry.reset(); + updateNotification(entry, pmUser, sbn, entry.row); } else { - // create the row view - LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row, - parent, false); - row.setExpansionLogger(this, entry.notification.getKey()); - row.setGroupManager(mGroupManager); - row.setHeadsUpManager(mHeadsUpManager); - row.setRemoteInputController(mRemoteInputController); - row.setOnExpandClickListener(this); - row.setRemoteViewClickHandler(mOnClickHandler); - row.setInflationCallback(this); - - // Get the app name. - // Note that Notification.Builder#bindHeaderAppName has similar logic - // but since this field is used in the guts, it must be accurate. - // Therefore we will only show the application label, or, failing that, the - // package name. No substitutions. - final String pkg = sbn.getPackageName(); - String appname = pkg; - try { - final ApplicationInfo info = pmUser.getApplicationInfo(pkg, - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS); - if (info != null) { - appname = String.valueOf(pmUser.getApplicationLabel(info)); - } - } catch (NameNotFoundException e) { - // Do nothing - } - row.setAppName(appname); - row.setOnDismissRunnable(() -> - performRemoveNotification(row.getStatusBarNotification())); - row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - if (ENABLE_REMOTE_INPUT) { - row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); + new RowInflaterTask().inflate(mContext, parent, entry, + row -> { + bindRow(entry, pmUser, sbn, row); + updateNotification(entry, pmUser, sbn, row); + }); + } + + } + + private void bindRow(Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row) { + row.setExpansionLogger(this, entry.notification.getKey()); + row.setGroupManager(mGroupManager); + row.setHeadsUpManager(mHeadsUpManager); + row.setRemoteInputController(mRemoteInputController); + row.setOnExpandClickListener(this); + row.setRemoteViewClickHandler(mOnClickHandler); + row.setInflationCallback(this); + + // Get the app name. + // Note that Notification.Builder#bindHeaderAppName has similar logic + // but since this field is used in the guts, it must be accurate. + // Therefore we will only show the application label, or, failing that, the + // package name. No substitutions. + final String pkg = sbn.getPackageName(); + String appname = pkg; + try { + final ApplicationInfo info = pmUser.getApplicationInfo(pkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS); + if (info != null) { + appname = String.valueOf(pmUser.getApplicationLabel(info)); } + } catch (NameNotFoundException e) { + // Do nothing + } + row.setAppName(appname); + row.setOnDismissRunnable(() -> + performRemoveNotification(row.getStatusBarNotification())); + row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + if (ENABLE_REMOTE_INPUT) { + row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); } + } + private void updateNotification(Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row) { row.setNeedsRedaction(needsRedaction(entry)); boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey()); row.setIsLowPriority(isLowPriority); diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index 5e8b3f905258..5e71dd4684c5 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -38,6 +38,7 @@ LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \ LOCAL_STATIC_ANDROID_LIBRARIES := \ SystemUIPluginLib \ + android-support-v4 \ android-support-v7-recyclerview \ android-support-v7-preference \ android-support-v7-appcompat \ diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java index 5e8ea9c46356..15381b7e9425 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java @@ -126,7 +126,7 @@ public class NotificationInflaterTest { @Test public void testAsyncTaskRemoved() throws Exception { - mRow.getEntry().abortInflation(); + mRow.getEntry().abortTask(); runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(), mNotificationInflater); Assert.assertNull(mRow.getEntry().getRunningTask() ); -- cgit v1.2.3-59-g8ed1b From ac5f02749a595d39711beb4a1defb01949eb548a Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Tue, 2 May 2017 16:05:41 -0700 Subject: Fixed the contrast for low-priority notifications The low priority notifications had their contrast calculated against the white background and weren't satisfying our contrast requirements. This also aligns the case where the app had no colors better, and ensures that it's actually using the secondary text color there. Test: existing tests pass Change-Id: Ic11e9d06783e60998f35e0eb7f6f29fb1d86c7df Fixes: 37444266 --- core/java/android/app/Notification.java | 42 +++++++++++++++++-- .../internal/util/NotificationColorUtil.java | 49 +++++++++++++++++++--- .../statusbar/ActivatableNotificationView.java | 2 +- .../statusbar/ExpandableNotificationRow.java | 6 ++- .../systemui/statusbar/NotificationData.java | 8 ++-- .../notification/NotificationInflater.java | 6 +++ 6 files changed, 99 insertions(+), 14 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index c8b8c6c1b262..2ad7498709b6 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2675,6 +2675,7 @@ public class Notification implements Parcelable private int mActionBarColor = COLOR_INVALID; private int mBackgroundColor = COLOR_INVALID; private int mForegroundColor = COLOR_INVALID; + private int mBackgroundColorHint = COLOR_INVALID; /** * Constructs a new Builder with the defaults: @@ -3839,6 +3840,13 @@ public class Notification implements Parcelable backgroundColor); mSecondaryTextColor = NotificationColorUtil.resolveSecondaryColor(mContext, backgroundColor); + if (backgroundColor != COLOR_DEFAULT + && (mBackgroundColorHint != COLOR_INVALID || isColorized())) { + mPrimaryTextColor = NotificationColorUtil.findAlphaToMeetContrast( + mPrimaryTextColor, backgroundColor, 4.5); + mSecondaryTextColor = NotificationColorUtil.findAlphaToMeetContrast( + mSecondaryTextColor, backgroundColor, 4.5); + } } else { double backLum = NotificationColorUtil.calculateLuminance(backgroundColor); double textLum = NotificationColorUtil.calculateLuminance(mForegroundColor); @@ -4659,10 +4667,26 @@ public class Notification implements Parcelable if (mCachedContrastColorIsFor == mN.color && mCachedContrastColor != COLOR_INVALID) { return mCachedContrastColor; } - final int contrasted = NotificationColorUtil.resolveContrastColor(mContext, mN.color); + int color; + int background = mBackgroundColorHint; + if (mBackgroundColorHint == COLOR_INVALID) { + background = mContext.getColor( + com.android.internal.R.color.notification_material_background_color); + } + if (mN.color == COLOR_DEFAULT) { + ensureColors(); + color = mSecondaryTextColor; + } else { + color = NotificationColorUtil.resolveContrastColor(mContext, mN.color, + background); + } + if (Color.alpha(color) < 255) { + // alpha doesn't go well for color filters, so let's blend it manually + color = NotificationColorUtil.compositeColors(color, background); + } mCachedContrastColorIsFor = mN.color; - return mCachedContrastColor = contrasted; + return mCachedContrastColor = color; } int resolveAmbientColor() { @@ -4879,7 +4903,8 @@ public class Notification implements Parcelable if (isColorized()) { return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : mN.color; } else { - return COLOR_DEFAULT; + return mBackgroundColorHint != COLOR_INVALID ? mBackgroundColorHint + : COLOR_DEFAULT; } } @@ -4910,6 +4935,17 @@ public class Notification implements Parcelable mTextColorsAreForBackground = COLOR_INVALID; ensureColors(); } + + /** + * Sets the background color for this notification to be a different one then the default. + * This is mainly used to calculate contrast and won't necessarily be applied to the + * background. + * + * @hide + */ + public void setBackgroundColorHint(int backgroundColor) { + mBackgroundColorHint = backgroundColor; + } } /** diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java index 2c97f8bd5971..cd41f9e9f902 100644 --- a/core/java/com/android/internal/util/NotificationColorUtil.java +++ b/core/java/com/android/internal/util/NotificationColorUtil.java @@ -285,6 +285,38 @@ public class NotificationColorUtil { return ColorUtilsFromCompat.LABToColor(low, a, b); } + /** + * Finds a suitable alpha such that there's enough contrast. + * + * @param color the color to start searching from. + * @param backgroundColor the color to ensure contrast against. + * @param minRatio the minimum contrast ratio required. + * @return the same color as {@param color} with potentially modified alpha to meet contrast + */ + public static int findAlphaToMeetContrast(int color, int backgroundColor, double minRatio) { + int fg = color; + int bg = backgroundColor; + if (ColorUtilsFromCompat.calculateContrast(fg, bg) >= minRatio) { + return color; + } + int startAlpha = Color.alpha(color); + int r = Color.red(color); + int g = Color.green(color); + int b = Color.blue(color); + + int low = startAlpha, high = 255; + for (int i = 0; i < 15 && high - low > 0; i++) { + final int alpha = (low + high) / 2; + fg = Color.argb(alpha, r, g, b); + if (ColorUtilsFromCompat.calculateContrast(fg, bg) > minRatio) { + high = alpha; + } else { + low = alpha; + } + } + return Color.argb(high, r, g, b); + } + /** * Finds a suitable color such that there's enough contrast. * @@ -373,19 +405,19 @@ public class NotificationColorUtil { * color for the Notification's action and header text. * * @param notificationColor the color of the notification or {@link Notification#COLOR_DEFAULT} + * @param backgroundColor the background color to ensure the contrast against. * @return a color of the same hue with enough contrast against the backgrounds. */ - public static int resolveContrastColor(Context context, int notificationColor) { + public static int resolveContrastColor(Context context, int notificationColor, + int backgroundColor) { final int resolvedColor = resolveColor(context, notificationColor); final int actionBg = context.getColor( com.android.internal.R.color.notification_action_list); - final int notiBg = context.getColor( - com.android.internal.R.color.notification_material_background_color); int color = resolvedColor; color = NotificationColorUtil.ensureLargeTextContrast(color, actionBg); - color = NotificationColorUtil.ensureTextContrast(color, notiBg); + color = NotificationColorUtil.ensureTextContrast(color, backgroundColor); if (color != resolvedColor) { if (DEBUG){ @@ -394,7 +426,7 @@ public class NotificationColorUtil { + " and %s (over background) by changing #%s to %s", context.getPackageName(), NotificationColorUtil.contrastChange(resolvedColor, color, actionBg), - NotificationColorUtil.contrastChange(resolvedColor, color, notiBg), + NotificationColorUtil.contrastChange(resolvedColor, color, backgroundColor), Integer.toHexString(resolvedColor), Integer.toHexString(color))); } } @@ -501,6 +533,13 @@ public class NotificationColorUtil { return ColorUtilsFromCompat.calculateContrast(foregroundColor, backgroundColor); } + /** + * Composite two potentially translucent colors over each other and returns the result. + */ + public static int compositeColors(int foreground, int background) { + return ColorUtilsFromCompat.compositeColors(foreground, background); + } + /** * Framework copy of functions needed from android.support.v4.graphics.ColorUtils. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index d7eab9772677..b91561e01290 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -891,7 +891,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView * @return the calculated background color */ private int calculateBgColor(boolean withTint, boolean withOverRide) { - if (mDark) { + if (withTint && mDark) { return getContext().getColor(R.color.notification_material_background_dark_color); } if (withOverRide && mOverrideTint != NO_COLOR) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 8c1b334fe570..93687478fc86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -355,7 +355,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView NotificationColorUtil.getInstance(mContext)); int color = StatusBarIconView.NO_COLOR; if (colorize) { - color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded()); + color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded(), + getBackgroundColorWithoutTint()); } expandedIcon.setStaticDrawableColor(color); } @@ -859,7 +860,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private void updateNotificationColor() { mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext, - getStatusBarNotification().getNotification().color); + getStatusBarNotification().getNotification().color, + getBackgroundColorWithoutTint()); } public HybridNotificationView getSingleLineView() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index f9d939cf62fa..f8bad053c3ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -201,13 +201,15 @@ public class NotificationData { } } - public int getContrastedColor(Context context, boolean ambient) { - int rawColor = ambient ? Notification.COLOR_DEFAULT : + public int getContrastedColor(Context context, boolean isLowPriority, + int backgroundColor) { + int rawColor = isLowPriority ? Notification.COLOR_DEFAULT : notification.getNotification().color; if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) { return mCachedContrastColor; } - final int contrasted = NotificationColorUtil.resolveContrastColor(context, rawColor); + final int contrasted = NotificationColorUtil.resolveContrastColor(context, rawColor, + backgroundColor); mCachedContrastColorIsFor = rawColor; mCachedContrastColor = contrasted; return mCachedContrastColor; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java index 52f5e1cd5de1..f1c26cd2daa8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java @@ -27,6 +27,7 @@ import android.view.View; import android.widget.RemoteViews; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.R; import com.android.systemui.statusbar.Abortable; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationContentView; @@ -521,6 +522,11 @@ public class NotificationInflater { mSbn.getNotification()); Context packageContext = mSbn.getPackageContext(mContext); Notification notification = mSbn.getNotification(); + if (mIsLowPriority) { + int backgroundColor = mContext.getColor( + R.color.notification_material_background_low_priority_color); + recoveredBuilder.setBackgroundColorHint(backgroundColor); + } if (notification.isMediaNotification()) { MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext, packageContext); -- cgit v1.2.3-59-g8ed1b