blob: ead4ff5e97aefcef289050233f24c1af4d6baeed [file] [log] [blame]
/*
* Copyright (C) 2018 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.car.notification.template;
import android.car.drivingstate.CarUxRestrictions;
import android.car.drivingstate.CarUxRestrictionsManager;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import com.android.car.notification.AlertEntry;
import com.android.car.notification.CarNotificationItemTouchListener;
import com.android.car.notification.CarNotificationViewAdapter;
import com.android.car.notification.NotificationClickHandlerFactory;
import com.android.car.notification.NotificationGroup;
import com.android.car.notification.R;
import java.util.ArrayList;
import java.util.List;
/**
* ViewHolder that binds a list of notifications as a grouped notification.
*/
public class GroupNotificationViewHolder extends CarNotificationBaseViewHolder
implements CarUxRestrictionsManager.OnUxRestrictionsChangedListener {
private final Context mContext;
private final CardView mCardView;
private final View mHeaderDividerView;
private final ImageView mToggleIcon;
private final TextView mExpansionFooterView;
private final RecyclerView mNotificationListView;
private final CarNotificationViewAdapter mAdapter;
private final Drawable mExpandDrawable;
private final Drawable mCollapseDrawable;
private final Paint mPaint;
private final int mDividerHeight;
private final CarNotificationHeaderView mGroupHeaderView;
private final View mTouchInterceptorView;
private AlertEntry mSummaryNotification;
private NotificationGroup mNotificationGroup;
public GroupNotificationViewHolder(
View view, NotificationClickHandlerFactory clickHandlerFactory) {
super(view, clickHandlerFactory);
mContext = view.getContext();
mCardView = itemView.findViewById(R.id.card_view);
mGroupHeaderView = view.findViewById(R.id.group_header);
mHeaderDividerView = view.findViewById(R.id.header_divider);
mToggleIcon = view.findViewById(R.id.group_toggle_icon);
mExpansionFooterView = view.findViewById(R.id.expansion_footer);
mNotificationListView = view.findViewById(R.id.notification_list);
mTouchInterceptorView = view.findViewById(R.id.touch_interceptor_view);
mExpandDrawable = mContext.getDrawable(R.drawable.expand_more);
mCollapseDrawable = mContext.getDrawable(R.drawable.expand_less);
mPaint = new Paint();
mPaint.setColor(mContext.getColor(R.color.notification_list_divider_color));
mDividerHeight = mContext.getResources().getDimensionPixelSize(
R.dimen.notification_list_divider_height);
mNotificationListView.setLayoutManager(new LinearLayoutManager(mContext));
mNotificationListView.addItemDecoration(new GroupedNotificationItemDecoration());
((SimpleItemAnimator) mNotificationListView.getItemAnimator())
.setSupportsChangeAnimations(false);
mNotificationListView.setNestedScrollingEnabled(false);
mAdapter = new CarNotificationViewAdapter(mContext, /* isGroupNotificationAdapter= */
true, /* notificationItemController= */ null);
mAdapter.setClickHandlerFactory(clickHandlerFactory);
mNotificationListView.addOnItemTouchListener(
new CarNotificationItemTouchListener(view.getContext(), mAdapter));
mNotificationListView.setAdapter(mAdapter);
}
/**
* Because this view holder does not call {@link CarNotificationBaseViewHolder#bind},
* we need to override this method.
*/
@Override
public AlertEntry getAlertEntry() {
return mSummaryNotification;
}
/**
* Returns the notification group for this viewholder.
*
* @return NotificationGroup {@link NotificationGroup}.
*/
public NotificationGroup getNotificationGroup() {
return mNotificationGroup;
}
/**
* Group notification view holder is special in that it requires extra data to bind,
* therefore the standard bind() method is not used. We are calling super.reset()
* directly and binding the onclick listener manually because the card's on click behavior is
* different when collapsed/expanded.
*/
public void bind(
NotificationGroup group, CarNotificationViewAdapter parentAdapter, boolean isExpanded) {
reset();
mNotificationGroup = group;
mSummaryNotification = group.getGroupSummaryNotification();
// Bind the notification's data to the headerView.
mGroupHeaderView.bind(mSummaryNotification, /* isInGroup= */ false);
// Set the header's UI attributes (i.e. smallIconColor, etc.) based on the BaseViewHolder.
bindHeader(mGroupHeaderView, /* isInGroup= */ false);
// use the same view pool with all the grouped notifications
// to increase the number of the shared views and reduce memory cost
// the view pool is created and stored in the root adapter
mNotificationListView.setRecycledViewPool(parentAdapter.getViewPool());
// notification cards
List<NotificationGroup> list = new ArrayList<>();
if (isExpanded) {
// show header divider
mHeaderDividerView.setVisibility(View.VISIBLE);
// all child notifications
group.getChildNotifications().forEach(notification -> {
NotificationGroup notificationGroup = new NotificationGroup();
notificationGroup.addNotification(notification);
list.add(notificationGroup);
});
} else {
// hide header divider
mHeaderDividerView.setVisibility(View.GONE);
// only show group summary notification
NotificationGroup newGroup = new NotificationGroup();
newGroup.addNotification(group.getGroupSummaryNotification());
// If the group summary notification is automatically generated,
// it does not contain a summary of the titles of the child notifications.
// Therefore, we generate a list of the child notification titles from
// the parent notification group, and pass them on.
newGroup.setChildTitles(group.generateChildTitles());
list.add(newGroup);
}
mAdapter.setNotifications(list,
/* setRecyclerViewListHeaderAndFooter= */ false);
updateExpansionIcon(group.getChildCount(), isExpanded);
updateOnClickListener(parentAdapter, group, isExpanded);
}
private void updateExpansionIcon(int childCount, boolean isExpanded) {
// expansion button in the group header
if (childCount == 0) {
mToggleIcon.setVisibility(View.GONE);
return;
}
mToggleIcon.setVisibility(View.VISIBLE);
mToggleIcon.setImageDrawable(isExpanded ? mCollapseDrawable : mExpandDrawable);
// Don't allow most controls to be focused when collapsed.
mNotificationListView.setDescendantFocusability(isExpanded
? ViewGroup.FOCUS_BEFORE_DESCENDANTS : ViewGroup.FOCUS_BLOCK_DESCENDANTS);
mNotificationListView.setFocusable(false);
mGroupHeaderView.setFocusable(isExpanded);
mExpansionFooterView.setFocusable(isExpanded);
// expansion button in the group footer
if (isExpanded) {
mExpansionFooterView.setText(mContext.getString(R.string.show_less));
hideDismissButton();
return;
}
int unshownCount = childCount - 2;
mExpansionFooterView.setText(
unshownCount <= 0
? mContext.getString(R.string.show_more)
: mContext.getString(R.string.show_count_more, unshownCount));
updateDismissButton(getAlertEntry(), /* isHeadsUp= */ false);
}
private void updateOnClickListener(
CarNotificationViewAdapter parentAdapter, NotificationGroup group, boolean isExpanded) {
View.OnClickListener expansionClickListener = view -> {
boolean isExpanding = !isExpanded;
parentAdapter.setExpanded(group.getGroupKey(), isExpanding);
mAdapter.notifyDataSetChanged();
};
if (isExpanded) {
mCardView.setOnClickListener(null);
mCardView.setClickable(false);
mCardView.setFocusable(false);
} else {
mCardView.setOnClickListener(expansionClickListener);
}
mGroupHeaderView.setOnClickListener(expansionClickListener);
mExpansionFooterView.setOnClickListener(expansionClickListener);
mTouchInterceptorView.setOnClickListener(expansionClickListener);
mTouchInterceptorView.setVisibility(isExpanded ? View.GONE : View.VISIBLE);
}
@Override
public boolean isDismissible() {
return mNotificationGroup == null || mNotificationGroup.isDismissible();
}
@Override
void reset() {
super.reset();
mCardView.setOnClickListener(null);
mGroupHeaderView.reset();
}
@Override
public void onUxRestrictionsChanged(CarUxRestrictions restrictionInfo) {
mAdapter.setCarUxRestrictions(mAdapter.getCarUxRestrictions());
}
private class GroupedNotificationItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
// not drawing the divider for the last item
for (int i = 0; i < parent.getChildCount() - 1; i++) {
drawDivider(c, parent.getChildAt(i));
}
}
/**
* Draws a divider under {@code container}.
*/
private void drawDivider(Canvas c, View container) {
int left = container.getLeft();
int right = container.getRight();
int bottom = container.getBottom() + mDividerHeight;
int top = bottom - mDividerHeight;
c.drawRect(left, top, right, bottom, mPaint);
}
}
}