Merge changes from topic 'background_inflation' into oc-dev am: 03fbdbe6ff

am: cc360f9b69

Change-Id: I456f81ff6300792e8639733d0115d878683d1694
This commit is contained in:
Selim Cinek
2017-05-08 22:30:48 +00:00
committed by android-build-merger
13 changed files with 635 additions and 204 deletions

View File

@@ -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;
}
}
/**

View File

@@ -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.
*/

View File

@@ -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 \

View File

@@ -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();
}

View File

@@ -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) {

View File

@@ -362,7 +362,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);
}
@@ -875,7 +876,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private void updateNotificationColor() {
mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext,
getStatusBarNotification().getNotification().color);
getStatusBarNotification().getNotification().color,
getBackgroundColorWithoutTint());
}
public HybridNotificationView getSingleLineView() {

View File

@@ -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;
}
}

View File

@@ -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;
@@ -108,7 +109,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);

View File

@@ -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;
}
result.packageContext = packageContext;
return result;
}
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);
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 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);
}
}
private RemoteViews createBigContentView(Notification.Builder builder,
/**
* 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 RemoteViews.OnClickHandler mRemoteViewClickHandler;
private CancellationSignal mCancellationSignal;
private AsyncInflationTask(Context context, StatusBarNotification notification,
int reInflateFlags) {
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){
handleError(e);
return;
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);
}
@Override
public void onAsyncInflationFinished(NotificationData.Entry entry) {
mRow.getEntry().onInflationTaskFinished();
mRow.onNotificationUpdated();
mCallback.onAsyncInflationFinished(mRow.getEntry());
}
mRow.onNotificationUpdated();
mCallback.onAsyncInflationFinished(mRow.getEntry());
}
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 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 abstract static class ApplyCallback {
public abstract void setResultView(View v);
public abstract RemoteViews getRemoteView();
}
}

View File

@@ -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);
}
}

View File

@@ -207,6 +207,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;
@@ -1598,12 +1599,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();
}
}
@@ -1620,7 +1621,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());
}
@@ -6234,50 +6235,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);

View File

@@ -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 \

View File

@@ -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);
}