blob: 7f95d48f36d46e308016283a9f8e0af0dfdbef88 [file] [log] [blame]
/*
* Copyright (C) 2015 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;
import android.app.Notification;
import android.graphics.PorterDuff;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
import android.view.NotificationHeaderView;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/**
* A Util to manage {@link android.view.NotificationHeaderView} objects and their redundancies.
*/
public class NotificationHeaderUtil {
private static final TextViewComparator sTextViewComparator = new TextViewComparator();
private static final VisibilityApplicator sVisibilityApplicator = new VisibilityApplicator();
private static final DataExtractor sIconExtractor = new DataExtractor() {
@Override
public Object extractData(ExpandableNotificationRow row) {
return row.getStatusBarNotification().getNotification();
}
};
private static final IconComparator sIconVisibilityComparator = new IconComparator() {
public boolean compare(View parent, View child, Object parentData,
Object childData) {
return hasSameIcon(parentData, childData)
&& hasSameColor(parentData, childData);
}
};
private static final IconComparator sGreyComparator = new IconComparator() {
public boolean compare(View parent, View child, Object parentData,
Object childData) {
return !hasSameIcon(parentData, childData)
|| hasSameColor(parentData, childData);
}
};
private final static ResultApplicator mGreyApplicator = new ResultApplicator() {
@Override
public void apply(View view, boolean apply) {
NotificationHeaderView header = (NotificationHeaderView) view;
ImageView icon = (ImageView) view.findViewById(
com.android.internal.R.id.icon);
ImageView expand = (ImageView) view.findViewById(
com.android.internal.R.id.expand_button);
applyToChild(icon, apply, header.getOriginalIconColor());
applyToChild(expand, apply, header.getOriginalNotificationColor());
}
private void applyToChild(View view, boolean shouldApply, int originalColor) {
if (originalColor != NotificationHeaderView.NO_COLOR) {
ImageView imageView = (ImageView) view;
imageView.getDrawable().mutate();
if (shouldApply) {
// lets gray it out
int grey = view.getContext().getColor(
com.android.internal.R.color.notification_icon_default_color);
imageView.getDrawable().setColorFilter(grey, PorterDuff.Mode.SRC_ATOP);
} else {
// lets reset it
imageView.getDrawable().setColorFilter(originalColor,
PorterDuff.Mode.SRC_ATOP);
}
}
}
};
private final ExpandableNotificationRow mRow;
private final ArrayList<HeaderProcessor> mComparators = new ArrayList<>();
private final HashSet<Integer> mDividers = new HashSet<>();
public NotificationHeaderUtil(ExpandableNotificationRow row) {
mRow = row;
// To hide the icons if they are the same and the color is the same
mComparators.add(new HeaderProcessor(mRow,
com.android.internal.R.id.icon,
sIconExtractor,
sIconVisibilityComparator,
sVisibilityApplicator));
// To grey them out the icons and expand button when the icons are not the same
mComparators.add(new HeaderProcessor(mRow,
com.android.internal.R.id.notification_header,
sIconExtractor,
sGreyComparator,
mGreyApplicator));
mComparators.add(new HeaderProcessor(mRow,
com.android.internal.R.id.profile_badge,
null /* Extractor */,
new ViewComparator() {
@Override
public boolean compare(View parent, View child, Object parentData,
Object childData) {
return parent.getVisibility() != View.GONE;
}
@Override
public boolean isEmpty(View view) {
if (view instanceof ImageView) {
return ((ImageView) view).getDrawable() == null;
}
return false;
}
},
sVisibilityApplicator));
mComparators.add(HeaderProcessor.forTextView(mRow,
com.android.internal.R.id.app_name_text));
mComparators.add(HeaderProcessor.forTextView(mRow,
com.android.internal.R.id.header_text));
mDividers.add(com.android.internal.R.id.header_text_divider);
mDividers.add(com.android.internal.R.id.time_divider);
}
public void updateChildrenHeaderAppearance() {
List<ExpandableNotificationRow> notificationChildren = mRow.getNotificationChildren();
if (notificationChildren == null) {
return;
}
// Initialize the comparators
for (int compI = 0; compI < mComparators.size(); compI++) {
mComparators.get(compI).init();
}
// Compare all notification headers
for (int i = 0; i < notificationChildren.size(); i++) {
ExpandableNotificationRow row = notificationChildren.get(i);
for (int compI = 0; compI < mComparators.size(); compI++) {
mComparators.get(compI).compareToHeader(row);
}
}
// Apply the comparison to the row
for (int i = 0; i < notificationChildren.size(); i++) {
ExpandableNotificationRow row = notificationChildren.get(i);
for (int compI = 0; compI < mComparators.size(); compI++) {
mComparators.get(compI).apply(row);
}
// We need to sanitize the dividers since they might be off-balance now
sanitizeHeaderViews(row);
}
}
private void sanitizeHeaderViews(ExpandableNotificationRow row) {
if (row.isSummaryWithChildren()) {
sanitizeHeader(row.getNotificationHeader());
return;
}
final NotificationContentView layout = row.getPrivateLayout();
sanitizeChild(layout.getContractedChild());
sanitizeChild(layout.getHeadsUpChild());
sanitizeChild(layout.getExpandedChild());
}
private void sanitizeChild(View child) {
if (child != null) {
NotificationHeaderView header = (NotificationHeaderView) child.findViewById(
com.android.internal.R.id.notification_header);
sanitizeHeader(header);
}
}
private void sanitizeHeader(NotificationHeaderView rowHeader) {
if (rowHeader == null) {
return;
}
final int childCount = rowHeader.getChildCount();
View time = rowHeader.findViewById(com.android.internal.R.id.time);
boolean hasVisibleText = false;
for (int i = 1; i < childCount - 1 ; i++) {
View child = rowHeader.getChildAt(i);
if (child instanceof TextView
&& child.getVisibility() != View.GONE
&& !mDividers.contains(Integer.valueOf(child.getId()))
&& child != time) {
hasVisibleText = true;
break;
}
}
// in case no view is visible we make sure the time is visible
int timeVisibility = !hasVisibleText
|| mRow.getStatusBarNotification().getNotification().showsTime()
? View.VISIBLE : View.GONE;
time.setVisibility(timeVisibility);
View left = null;
View right;
for (int i = 1; i < childCount - 1 ; i++) {
View child = rowHeader.getChildAt(i);
if (mDividers.contains(Integer.valueOf(child.getId()))) {
boolean visible = false;
// Lets find the item to the right
for (i++; i < childCount - 1; i++) {
right = rowHeader.getChildAt(i);
if (mDividers.contains(Integer.valueOf(right.getId()))) {
// A divider was found, this needs to be hidden
i--;
break;
} else if (right.getVisibility() != View.GONE && right instanceof TextView) {
visible = left != null;
left = right;
break;
}
}
child.setVisibility(visible ? View.VISIBLE : View.GONE);
} else if (child.getVisibility() != View.GONE && child instanceof TextView) {
left = child;
}
}
}
public void restoreNotificationHeader(ExpandableNotificationRow row) {
for (int compI = 0; compI < mComparators.size(); compI++) {
mComparators.get(compI).apply(row, true /* reset */);
}
sanitizeHeaderViews(row);
}
private static class HeaderProcessor {
private final int mId;
private final DataExtractor mExtractor;
private final ResultApplicator mApplicator;
private final ExpandableNotificationRow mParentRow;
private boolean mApply;
private View mParentView;
private ViewComparator mComparator;
private Object mParentData;
public static HeaderProcessor forTextView(ExpandableNotificationRow row, int id) {
return new HeaderProcessor(row, id, null, sTextViewComparator, sVisibilityApplicator);
}
HeaderProcessor(ExpandableNotificationRow row, int id, DataExtractor extractor,
ViewComparator comparator,
ResultApplicator applicator) {
mId = id;
mExtractor = extractor;
mApplicator = applicator;
mComparator = comparator;
mParentRow = row;
}
public void init() {
mParentView = mParentRow.getNotificationHeader().findViewById(mId);
mParentData = mExtractor == null ? null : mExtractor.extractData(mParentRow);
mApply = !mComparator.isEmpty(mParentView);
}
public void compareToHeader(ExpandableNotificationRow row) {
if (!mApply) {
return;
}
NotificationHeaderView header = row.getNotificationHeader();
if (header == null) {
mApply = false;
return;
}
Object childData = mExtractor == null ? null : mExtractor.extractData(row);
mApply = mComparator.compare(mParentView, header.findViewById(mId),
mParentData, childData);
}
public void apply(ExpandableNotificationRow row) {
apply(row, false /* reset */);
}
public void apply(ExpandableNotificationRow row, boolean reset) {
boolean apply = mApply && !reset;
if (row.isSummaryWithChildren()) {
applyToView(apply, row.getNotificationHeader());
return;
}
applyToView(apply, row.getPrivateLayout().getContractedChild());
applyToView(apply, row.getPrivateLayout().getHeadsUpChild());
applyToView(apply, row.getPrivateLayout().getExpandedChild());
}
private void applyToView(boolean apply, View parent) {
if (parent != null) {
View view = parent.findViewById(mId);
if (view != null && !mComparator.isEmpty(view)) {
mApplicator.apply(view, apply);
}
}
}
}
private interface ViewComparator {
/**
* @param parent the parent view
* @param child the child view
* @param parentData optional data for the parent
* @param childData optional data for the child
* @return whether to views are the same
*/
boolean compare(View parent, View child, Object parentData, Object childData);
boolean isEmpty(View view);
}
private interface DataExtractor {
Object extractData(ExpandableNotificationRow row);
}
private static class TextViewComparator implements ViewComparator {
@Override
public boolean compare(View parent, View child, Object parentData, Object childData) {
TextView parentView = (TextView) parent;
TextView childView = (TextView) child;
return parentView.getText().equals(childView.getText());
}
@Override
public boolean isEmpty(View view) {
return TextUtils.isEmpty(((TextView) view).getText());
}
}
private static abstract class IconComparator implements ViewComparator {
@Override
public boolean compare(View parent, View child, Object parentData, Object childData) {
return false;
}
protected boolean hasSameIcon(Object parentData, Object childData) {
Icon parentIcon = ((Notification) parentData).getSmallIcon();
Icon childIcon = ((Notification) childData).getSmallIcon();
return parentIcon.sameAs(childIcon);
}
/**
* @return whether two ImageViews have the same colorFilterSet or none at all
*/
protected boolean hasSameColor(Object parentData, Object childData) {
int parentColor = ((Notification) parentData).color;
int childColor = ((Notification) childData).color;
return parentColor == childColor;
}
@Override
public boolean isEmpty(View view) {
return false;
}
}
private interface ResultApplicator {
void apply(View view, boolean apply);
}
private static class VisibilityApplicator implements ResultApplicator {
@Override
public void apply(View view, boolean apply) {
view.setVisibility(apply ? View.GONE : View.VISIBLE);
}
}
}