diff options
| author | 2017-05-08 21:11:31 +0000 | |
|---|---|---|
| committer | 2017-05-08 21:11:37 +0000 | |
| commit | 03fbdbe6ffc769f864f62a5cd87183b9b299fe30 (patch) | |
| tree | a772f4a28e60e9aef746f6aa463f21d2ae024d66 | |
| parent | f0de5cdd9ab79126efcfe56788fc9756e1d8de1d (diff) | |
| parent | ac5f02749a595d39711beb4a1defb01949eb548a (diff) | |
Merge changes from topic 'background_inflation' into oc-dev
* changes:
Fixed the contrast for low-priority notifications
Moving Row inflation to the background too
Moving the inflation to the background
13 files changed, 635 insertions, 204 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2e56bcf8a3de..0041879453a8 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); @@ -4662,10 +4670,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() { @@ -4882,7 +4906,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; } } @@ -4913,6 +4938,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 @@ -286,6 +286,38 @@ public class NotificationColorUtil { } /** + * 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. * * @param color the color to start searching from. @@ -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))); } } @@ -502,6 +534,13 @@ public class NotificationColorUtil { } /** + * 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. */ private static class ColorUtilsFromCompat { 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/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 540c39150188..f8bad053c3ee 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<SnoozeCriterion> snoozeCriteria; private int mCachedContrastColor = COLOR_INVALID; private int mCachedContrastColorIsFor = COLOR_INVALID; - private ArraySet<AsyncTask> mRunningTasks = new ArraySet(); + private Abortable mRunningTask = null; public Entry(StatusBarNotification n) { this.key = n.getKey(); @@ -203,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; @@ -218,24 +218,26 @@ public class NotificationData { /** * Abort all existing inflation tasks */ - public void abortInflation() { - for (AsyncTask task : mRunningTasks) { - task.cancel(true /* mayInterruptIfRunning */); + public void abortTask() { + if (mRunningTask != null) { + mRunningTask.abort(); + mRunningTask = null; } - mRunningTasks.clear(); } - public void addInflationTask(AsyncTask asyncInflationTask) { - mRunningTasks.add(asyncInflationTask); + public void setInflationTask(Abortable abortableTask) { + // abort any existing inflation + abortTask(); + mRunningTask = abortableTask; } - public void onInflationTaskFinished(AsyncTask asyncInflationTask) { - mRunningTasks.remove(asyncInflationTask); + public void onInflationTaskFinished() { + mRunningTask = null; } @VisibleForTesting - public ArraySet<AsyncTask> getRunningTasks() { - return mRunningTasks; + 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 7cfc767f89b7..f1c26cd2daa8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java @@ -16,19 +16,26 @@ 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; 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; 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 +123,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<Integer, CancellationSignal> 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); } - entry.cachedAmbientContentView = newAmbientNotification; } + + 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); + } + } + + 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<Integer, CancellationSignal> 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<Integer, CancellationSignal> 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<Integer, CancellationSignal> 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 +433,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 +442,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 +456,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 +470,73 @@ public class NotificationInflater { inflateNotificationViews(); } - private class AsyncInflationTask extends AsyncTask<Void, Void, Notification.Builder> { + 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<Void, Void, InflationProgress> + implements InflationCallback, Abortable { 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 (mIsLowPriority) { + int backgroundColor = mContext.getColor( + R.color.notification_material_background_low_priority_color); + recoveredBuilder.setBackgroundColorHint(backgroundColor); + } 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 +544,64 @@ 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)); + } + + @Override + 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/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 badbcb3da845..4610bc8fe54a 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(); } } @@ -1610,7 +1611,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()); } @@ -6172,50 +6173,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 fbb25e5484ba..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 @@ -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); @@ -124,10 +126,10 @@ public class NotificationInflaterTest { @Test public void testAsyncTaskRemoved() throws Exception { - mRow.getEntry().abortInflation(); + mRow.getEntry().abortTask(); 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); } |