Update one-line layout for conversation notifs

Fixes: 152770837
Test: manual, visual
Change-Id: I8433d5e26be4e6fe0c16acc87b29bac334011cbf
This commit is contained in:
Steve Elliott
2020-04-14 13:59:53 -04:00
parent f51a85f60a
commit 936df15c79
19 changed files with 481 additions and 99 deletions

View File

@@ -35,10 +35,14 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -109,7 +113,7 @@ public class ConversationLayout extends FrameLayout
private CharSequence mNameReplacement;
private boolean mIsCollapsed;
private ImageResolver mImageResolver;
private CachingIconView mConversationIcon;
private CachingIconView mConversationIconView;
private View mConversationIconContainer;
private int mConversationIconTopPadding;
private int mConversationIconTopPaddingExpandedGroup;
@@ -157,6 +161,7 @@ public class ConversationLayout extends FrameLayout
private ViewGroup mAppOps;
private Rect mAppOpsTouchRect = new Rect();
private float mMinTouchSize;
private Icon mConversationIcon;
public ConversationLayout(@NonNull Context context) {
super(context);
@@ -192,7 +197,7 @@ public class ConversationLayout extends FrameLayout
mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setAntiAlias(true);
mConversationIcon = findViewById(R.id.conversation_icon);
mConversationIconView = findViewById(R.id.conversation_icon);
mConversationIconContainer = findViewById(R.id.conversation_icon_container);
mIcon = findViewById(R.id.icon);
mAppOps = findViewById(com.android.internal.R.id.app_ops);
@@ -232,7 +237,7 @@ public class ConversationLayout extends FrameLayout
});
// When the conversation icon is gone, hide the whole badge
mConversationIcon.setOnForceHiddenChangedListener((forceHidden) -> {
mConversationIconView.setOnForceHiddenChangedListener((forceHidden) -> {
animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
animateViewForceHidden(mImportanceRingView, forceHidden);
animateViewForceHidden(mIcon, forceHidden);
@@ -463,7 +468,7 @@ public class ConversationLayout extends FrameLayout
CharSequence conversationText = mConversationTitle;
if (mIsOneToOne) {
// Let's resolve the icon / text from the last sender
mConversationIcon.setVisibility(VISIBLE);
mConversationIconView.setVisibility(VISIBLE);
mConversationFacePile.setVisibility(GONE);
CharSequence userKey = getKey(mUser);
for (int i = mGroups.size() - 1; i >= 0; i--) {
@@ -480,17 +485,20 @@ public class ConversationLayout extends FrameLayout
if (avatarIcon == null) {
avatarIcon = createAvatarSymbol(conversationText, "", mLayoutColor);
}
mConversationIcon.setImageIcon(avatarIcon);
mConversationIcon = avatarIcon;
mConversationIconView.setImageIcon(mConversationIcon);
break;
}
}
} else {
if (mLargeIcon != null) {
mConversationIcon.setVisibility(VISIBLE);
mConversationIcon = mLargeIcon;
mConversationIconView.setVisibility(VISIBLE);
mConversationFacePile.setVisibility(GONE);
mConversationIcon.setImageIcon(mLargeIcon);
mConversationIconView.setImageIcon(mLargeIcon);
} else {
mConversationIcon.setVisibility(GONE);
mConversationIcon = null;
mConversationIconView.setVisibility(GONE);
// This will also inflate it!
mConversationFacePile.setVisibility(VISIBLE);
// rebind the value to the inflated view instead of the stub
@@ -561,15 +569,8 @@ public class ConversationLayout extends FrameLayout
mImageMessageContainer.setVisibility(newMessage != null ? VISIBLE : GONE);
}
private void bindFacePile() {
// Let's bind the face pile
ImageView bottomBackground = mConversationFacePile.findViewById(
R.id.conversation_face_pile_bottom_background);
public void bindFacePile(ImageView bottomBackground, ImageView bottomView, ImageView topView) {
applyNotificationBackgroundColor(bottomBackground);
ImageView bottomView = mConversationFacePile.findViewById(
R.id.conversation_face_pile_bottom);
ImageView topView = mConversationFacePile.findViewById(
R.id.conversation_face_pile_top);
// Let's find the two last conversations:
Icon secondLastIcon = null;
CharSequence lastKey = null;
@@ -601,6 +602,17 @@ public class ConversationLayout extends FrameLayout
secondLastIcon = createAvatarSymbol("", "", mLayoutColor);
}
topView.setImageIcon(secondLastIcon);
}
private void bindFacePile() {
ImageView bottomBackground = mConversationFacePile.findViewById(
R.id.conversation_face_pile_bottom_background);
ImageView bottomView = mConversationFacePile.findViewById(
R.id.conversation_face_pile_bottom);
ImageView topView = mConversationFacePile.findViewById(
R.id.conversation_face_pile_top);
bindFacePile(bottomBackground, bottomView, topView);
int conversationAvatarSize;
int facepileAvatarSize;
@@ -614,7 +626,7 @@ public class ConversationLayout extends FrameLayout
facepileAvatarSize = mFacePileAvatarSizeExpandedGroup;
facePileBackgroundSize = facepileAvatarSize + 2 * mFacePileProtectionWidthExpanded;
}
LayoutParams layoutParams = (LayoutParams) mConversationIcon.getLayoutParams();
LayoutParams layoutParams = (LayoutParams) mConversationIconView.getLayoutParams();
layoutParams.width = conversationAvatarSize;
layoutParams.height = conversationAvatarSize;
mConversationFacePile.setLayoutParams(layoutParams);
@@ -664,11 +676,11 @@ public class ConversationLayout extends FrameLayout
layoutParams.setMarginStart(sidemargin);
mConversationIconBadge.setLayoutParams(layoutParams);
if (mConversationIcon.getVisibility() == VISIBLE) {
layoutParams = (LayoutParams) mConversationIcon.getLayoutParams();
if (mConversationIconView.getVisibility() == VISIBLE) {
layoutParams = (LayoutParams) mConversationIconView.getLayoutParams();
layoutParams.width = conversationAvatarSize;
layoutParams.height = conversationAvatarSize;
mConversationIcon.setLayoutParams(layoutParams);
mConversationIconView.setLayoutParams(layoutParams);
}
}
@@ -719,6 +731,10 @@ public class ConversationLayout extends FrameLayout
mConversationTitle = conversationTitle;
}
public CharSequence getConversationTitle() {
return mConversationText.getText();
}
private void removeGroups(ArrayList<MessagingGroup> oldGroups) {
int size = oldGroups.size();
for (int i = 0; i < size; i++) {
@@ -1218,4 +1234,43 @@ public class ConversationLayout extends FrameLayout
public void setMessagingClippingDisabled(boolean clippingDisabled) {
mMessagingLinearLayout.setClipBounds(clippingDisabled ? null : mMessagingClipRect);
}
@Nullable
public CharSequence getConversationSenderName() {
if (mGroups.isEmpty()) {
return null;
}
final CharSequence name = mGroups.get(mGroups.size() - 1).getSenderName();
return getResources().getString(R.string.conversation_single_line_name_display, name);
}
public boolean isOneToOne() {
return mIsOneToOne;
}
@Nullable
public CharSequence getConversationText() {
if (mMessages.isEmpty()) {
return null;
}
final MessagingMessage messagingMessage = mMessages.get(mMessages.size() - 1);
final CharSequence text = messagingMessage.getMessage().getText();
if (text == null && messagingMessage instanceof MessagingImageMessage) {
final String unformatted =
getResources().getString(R.string.conversation_single_line_image_placeholder);
SpannableString spannableString = new SpannableString(unformatted);
spannableString.setSpan(
new StyleSpan(Typeface.ITALIC),
0,
spannableString.length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
return spannableString;
}
return text;
}
@Nullable
public Icon getConversationIcon() {
return mConversationIcon;
}
}

View File

@@ -12,7 +12,7 @@
~ 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
~ limitations underthe License
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"

View File

@@ -24,12 +24,20 @@
android:layout_height="@dimen/notification_header_height"
android:clipChildren="false"
style="?attr/notificationHeaderStyle">
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
android:layout_width="?attr/notificationHeaderIconSize"
android:layout_height="?attr/notificationHeaderIconSize"
android:layout_marginEnd="@dimen/notification_header_icon_margin_end"
<!-- Wrapper used to expand the width of the "space" containing the icon programmatically -->
<FrameLayout
android:id="@+id/header_icon_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
android:layout_width="?attr/notificationHeaderIconSize"
android:layout_height="?attr/notificationHeaderIconSize"
android:layout_marginEnd="@dimen/notification_header_icon_margin_end"
android:layout_gravity="center"
/>
</FrameLayout>
<TextView
android:id="@+id/app_name_text"
android:layout_width="wrap_content"

View File

@@ -5449,6 +5449,9 @@
<!-- The way a conversation name is displayed when single line. The text will be displayed to the end of this text with some spacing -->
<string name="conversation_single_line_name_display"><xliff:g id="sender_name" example="Sara">%1$s</xliff:g>:</string>
<!-- Text used when a conversation is displayed in a single-line when the latest message is an image. [CHAR_LIMIT=NONE] -->
<string name="conversation_single_line_image_placeholder">sent an image</string>
<!-- Conversation Title fallback if the there is no name provided in a 1:1 conversation [CHAR LIMIT=40]-->
<string name="conversation_title_fallback_one_to_one">Conversation</string>

View File

@@ -296,6 +296,10 @@ easier.
<style name="TextAppearance.DeviceDefault.Notification.Info" parent="TextAppearance.Material.Notification.Info">
<item name="fontFamily">@string/config_bodyFontFamily</item>
</style>
<style name="TextAppearance.DeviceDefault.Notification.Conversation.AppName"
parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title">
<item name="android:textSize">16sp</item>
</style>
<style name="TextAppearance.DeviceDefault.Widget" parent="TextAppearance.Material.Widget">
<item name="fontFamily">@string/config_bodyFontFamily</item>
</style>

View File

@@ -1599,6 +1599,8 @@
<java-symbol type="style" name="Theme.DeviceDefault.VoiceInteractionSession" />
<java-symbol type="style" name="Pointer" />
<java-symbol type="style" name="LargePointer" />
<java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Title" />
<java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Info" />
<java-symbol type="attr" name="mediaRouteButtonStyle" />
<java-symbol type="attr" name="externalRouteEnabledDrawable" />
@@ -3863,7 +3865,10 @@
<java-symbol type="array" name="config_defaultImperceptibleKillingExemptionPkgs" />
<java-symbol type="array" name="config_defaultImperceptibleKillingExemptionProcStates" />
<java-symbol type="id" name="header_icon_container" />
<java-symbol type="attr" name="notificationHeaderTextAppearance" />
<java-symbol type="string" name="conversation_single_line_name_display" />
<java-symbol type="string" name="conversation_single_line_image_placeholder" />
<java-symbol type="string" name="conversation_title_fallback_one_to_one" />
<java-symbol type="string" name="conversation_title_fallback_group_chat" />
<java-symbol type="id" name="conversation_icon" />
@@ -3907,6 +3912,7 @@
<java-symbol type="layout" name="conversation_face_pile_layout" />
<java-symbol type="id" name="conversation_unread_count" />
<java-symbol type="string" name="unread_convo_overflow" />
<java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Conversation.AppName" />
<!-- Intent resolver and share sheet -->
<java-symbol type="string" name="resolver_personal_tab" />

View File

@@ -279,8 +279,12 @@ public class Utils {
}
public static int getThemeAttr(Context context, int attr) {
return getThemeAttr(context, attr, 0);
}
public static int getThemeAttr(Context context, int attr, int defaultValue) {
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
int theme = ta.getResourceId(0, 0);
int theme = ta.getResourceId(0, defaultValue);
ta.recycle();
return theme;
}

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<com.android.systemui.statusbar.notification.row.HybridConversationNotificationView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|start"
android:paddingEnd="12dp">
<FrameLayout
android:layout_width="@*android:dimen/conversation_content_start"
android:layout_height="36dp"
>
<ImageView
android:id="@*android:id/conversation_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
/>
<ViewStub
android:id="@*android:id/conversation_face_pile"
android:layout="@*android:layout/conversation_face_pile_layout"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center"
/>
</FrameLayout>
<TextView
android:id="@+id/notification_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:paddingEnd="8dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
/>
<TextView
android:id="@+id/conversation_notification_sender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
style="?attr/hybridNotificationTextStyle"
/>
<TextView
android:id="@+id/notification_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
style="?attr/hybridNotificationTextStyle"
/>
</com.android.systemui.statusbar.notification.row.HybridConversationNotificationView>

View File

@@ -625,6 +625,15 @@
<!-- The height of the gap between adjacent notification sections. -->
<dimen name="notification_section_divider_height">@dimen/notification_side_paddings</dimen>
<!-- Size of the face pile shown on one-line (children of a group) conversation notifications -->
<dimen name="conversation_single_line_face_pile_size">36dp</dimen>
<!-- Size of an avatar shown on one-line (children of a group) conversation notifications -->
<dimen name="conversation_single_line_avatar_size">24dp</dimen>
<!-- Border width for avatars in the face pile shown on one-line (children of a group) conversation notifications -->
<dimen name="conversation_single_line_face_pile_protection_width">1dp</dimen>
<!-- The minimum amount of top overscroll to go to the quick settings. -->
<dimen name="min_top_overscroll_to_qs">36dp</dimen>

View File

@@ -475,7 +475,6 @@
<item name="android:textColor">?android:attr/textColorTertiary</item>
</style>
<style name="VolumeButtons" parent="@android:style/Widget.Material.Button.Borderless">
<item name="android:background">@drawable/btn_borderless_rect</item>
</style>

View File

@@ -536,6 +536,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return isNonblockable;
}
private boolean isConversation() {
return mPeopleNotificationIdentifier
.getPeopleNotificationType(mEntry.getSbn(), mEntry.getRanking())
!= PeopleNotificationIdentifier.TYPE_NON_PERSON;
}
public void onNotificationUpdated() {
for (NotificationContentView l : mLayouts) {
l.onNotificationUpdated(mEntry);
@@ -548,7 +554,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mMenuRow.setAppName(mAppName);
}
if (mIsSummaryWithChildren) {
mChildrenContainer.recreateNotificationHeader(mExpandClickListener);
mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());
mChildrenContainer.onNotificationUpdated();
}
if (mIconAnimationRunning) {
@@ -2358,8 +2364,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mIsSummaryWithChildren = mChildrenContainer != null
&& mChildrenContainer.getNotificationChildCount() > 0;
if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
mChildrenContainer.recreateNotificationHeader(mExpandClickListener
);
mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());
}
getShowingLayout().updateBackgroundColor(false /* animate */);
mPrivateLayout.updateExpandButtons(isExpandable());

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2020 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.row;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.widget.ConversationLayout;
import com.android.systemui.R;
/**
* A hybrid view which may contain information about one ore more conversations.
*/
public class HybridConversationNotificationView extends HybridNotificationView {
private ImageView mConversationIconView;
private TextView mConversationSenderName;
private View mConversationFacePile;
private int mConversationIconSize;
private int mFacePileSize;
private int mFacePileProtectionWidth;
public HybridConversationNotificationView(Context context) {
this(context, null);
}
public HybridConversationNotificationView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public HybridConversationNotificationView(
Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public HybridConversationNotificationView(
Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mConversationIconView = requireViewById(com.android.internal.R.id.conversation_icon);
mConversationFacePile = requireViewById(com.android.internal.R.id.conversation_face_pile);
mConversationSenderName = requireViewById(R.id.conversation_notification_sender);
mFacePileSize = getResources()
.getDimensionPixelSize(R.dimen.conversation_single_line_face_pile_size);
mConversationIconSize = getResources()
.getDimensionPixelSize(R.dimen.conversation_single_line_avatar_size);
mFacePileProtectionWidth = getResources().getDimensionPixelSize(
R.dimen.conversation_single_line_face_pile_protection_width);
mTransformationHelper.addViewTransformingToSimilar(mConversationIconView);
}
@Override
public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
@Nullable View contentView) {
if (!(contentView instanceof ConversationLayout)) {
super.bind(title, text, contentView);
return;
}
ConversationLayout conversationLayout = (ConversationLayout) contentView;
Icon conversationIcon = conversationLayout.getConversationIcon();
if (conversationIcon != null) {
mConversationFacePile.setVisibility(GONE);
mConversationIconView.setVisibility(VISIBLE);
mConversationIconView.setImageIcon(conversationIcon);
} else {
// If there isn't an icon, generate a "face pile" based on the sender avatars
mConversationIconView.setVisibility(GONE);
mConversationFacePile.setVisibility(VISIBLE);
mConversationFacePile =
requireViewById(com.android.internal.R.id.conversation_face_pile);
ImageView facePileBottomBg = mConversationFacePile.requireViewById(
com.android.internal.R.id.conversation_face_pile_bottom_background);
ImageView facePileBottom = mConversationFacePile.requireViewById(
com.android.internal.R.id.conversation_face_pile_bottom);
ImageView facePileTop = mConversationFacePile.requireViewById(
com.android.internal.R.id.conversation_face_pile_top);
conversationLayout.bindFacePile(facePileBottomBg, facePileBottom, facePileTop);
setSize(mConversationFacePile, mFacePileSize);
setSize(facePileBottom, mConversationIconSize);
setSize(facePileTop, mConversationIconSize);
setSize(facePileBottomBg, mConversationIconSize + 2 * mFacePileProtectionWidth);
mTransformationHelper.addViewTransformingToSimilar(facePileTop);
mTransformationHelper.addViewTransformingToSimilar(facePileBottom);
mTransformationHelper.addViewTransformingToSimilar(facePileBottomBg);
}
CharSequence conversationTitle = conversationLayout.getConversationTitle();
if (TextUtils.isEmpty(conversationTitle)) {
conversationTitle = title;
}
if (conversationLayout.isOneToOne()) {
mConversationSenderName.setVisibility(GONE);
} else {
mConversationSenderName.setVisibility(VISIBLE);
mConversationSenderName.setText(conversationLayout.getConversationSenderName());
}
CharSequence conversationText = conversationLayout.getConversationText();
if (TextUtils.isEmpty(conversationText)) {
conversationText = text;
}
super.bind(conversationTitle, conversationText, conversationLayout);
}
private static void setSize(View view, int size) {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) view.getLayoutParams();
lp.width = size;
lp.height = size;
view.setLayoutParams(lp);
}
}

View File

@@ -16,18 +16,20 @@
package com.android.systemui.statusbar.notification.row;
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.Resources;
import android.service.notification.StatusBarNotification;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.android.internal.widget.ConversationLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.NotificationDozeHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
/**
* A class managing hybrid groups that include {@link HybridNotificationView} and the notification
@@ -36,41 +38,41 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
public class HybridGroupManager {
private final Context mContext;
private final ViewGroup mParent;
private float mOverflowNumberSize;
private int mOverflowNumberPadding;
private int mOverflowNumberColor;
public HybridGroupManager(Context ctx, ViewGroup parent) {
public HybridGroupManager(Context ctx) {
mContext = ctx;
mParent = parent;
initDimens();
}
public void initDimens() {
Resources res = mContext.getResources();
mOverflowNumberSize = res.getDimensionPixelSize(
R.dimen.group_overflow_number_size);
mOverflowNumberPadding = res.getDimensionPixelSize(
R.dimen.group_overflow_number_padding);
mOverflowNumberSize = res.getDimensionPixelSize(R.dimen.group_overflow_number_size);
mOverflowNumberPadding = res.getDimensionPixelSize(R.dimen.group_overflow_number_padding);
}
private HybridNotificationView inflateHybridViewWithStyle(int style) {
private HybridNotificationView inflateHybridViewWithStyle(int style,
View contentView, ViewGroup parent) {
LayoutInflater inflater = new ContextThemeWrapper(mContext, style)
.getSystemService(LayoutInflater.class);
HybridNotificationView hybrid = (HybridNotificationView) inflater.inflate(
R.layout.hybrid_notification, mParent, false);
mParent.addView(hybrid);
int layout = contentView instanceof ConversationLayout
? R.layout.hybrid_conversation_notification
: R.layout.hybrid_notification;
HybridNotificationView hybrid = (HybridNotificationView)
inflater.inflate(layout, parent, false);
parent.addView(hybrid);
return hybrid;
}
private TextView inflateOverflowNumber() {
private TextView inflateOverflowNumber(ViewGroup parent) {
LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
TextView numberView = (TextView) inflater.inflate(
R.layout.hybrid_overflow_number, mParent, false);
mParent.addView(numberView);
R.layout.hybrid_overflow_number, parent, false);
parent.addView(numberView);
updateOverFlowNumberColor(numberView);
return numberView;
}
@@ -87,22 +89,26 @@ public class HybridGroupManager {
}
public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
Notification notification) {
return bindFromNotificationWithStyle(reusableView, notification,
R.style.HybridNotification);
View contentView, StatusBarNotification notification,
ViewGroup parent) {
return bindFromNotificationWithStyle(reusableView, contentView, notification,
R.style.HybridNotification, parent);
}
private HybridNotificationView bindFromNotificationWithStyle(
HybridNotificationView reusableView, Notification notification, int style) {
HybridNotificationView reusableView, View contentView,
StatusBarNotification notification,
int style, ViewGroup parent) {
if (reusableView == null) {
reusableView = inflateHybridViewWithStyle(style);
reusableView = inflateHybridViewWithStyle(style, contentView, parent);
}
CharSequence titleText = resolveTitle(notification);
CharSequence contentText = resolveText(notification);
reusableView.bind(titleText, contentText);
CharSequence titleText = resolveTitle(notification.getNotification());
CharSequence contentText = resolveText(notification.getNotification());
reusableView.bind(titleText, contentText, contentView);
return reusableView;
}
@Nullable
private CharSequence resolveText(Notification notification) {
CharSequence contentText = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
if (contentText == null) {
@@ -111,6 +117,7 @@ public class HybridGroupManager {
return contentText;
}
@Nullable
private CharSequence resolveTitle(Notification notification) {
CharSequence titleText = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
if (titleText == null) {
@@ -119,9 +126,10 @@ public class HybridGroupManager {
return titleText;
}
public TextView bindOverflowNumber(TextView reusableView, int number) {
public TextView bindOverflowNumber(TextView reusableView, int number,
ViewGroup parent) {
if (reusableView == null) {
reusableView = inflateOverflowNumber();
reusableView = inflateOverflowNumber(parent);
}
String text = mContext.getResources().getString(
R.string.notification_group_overflow_indicator, number);

View File

@@ -36,8 +36,7 @@ import com.android.systemui.statusbar.notification.TransformState;
public class HybridNotificationView extends AlphaOptimizedLinearLayout
implements TransformableView {
private ViewTransformationHelper mTransformationHelper;
protected final ViewTransformationHelper mTransformationHelper = new ViewTransformationHelper();
protected TextView mTitleView;
protected TextView mTextView;
@@ -69,9 +68,8 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTitleView = (TextView) findViewById(R.id.notification_title);
mTextView = (TextView) findViewById(R.id.notification_text);
mTransformationHelper = new ViewTransformationHelper();
mTitleView = findViewById(R.id.notification_title);
mTextView = findViewById(R.id.notification_text);
mTransformationHelper.setCustomTransformation(
new ViewTransformationHelper.CustomTransformation() {
@Override
@@ -106,11 +104,8 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TEXT, mTextView);
}
public void bind(CharSequence title) {
bind(title, null);
}
public void bind(CharSequence title, CharSequence text) {
public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
@Nullable View contentView) {
mTitleView.setText(title);
mTitleView.setVisibility(TextUtils.isEmpty(title) ? GONE : VISIBLE);
if (TextUtils.isEmpty(text)) {

View File

@@ -177,7 +177,7 @@ public class NotificationContentView extends FrameLayout {
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mHybridGroupManager = new HybridGroupManager(getContext(), this);
mHybridGroupManager = new HybridGroupManager(getContext());
mMediaTransferManager = new MediaTransferManager(getContext());
mSmartReplyConstants = Dependency.get(SmartReplyConstants.class);
mSmartReplyController = Dependency.get(SmartReplyController.class);
@@ -1163,7 +1163,7 @@ public class NotificationContentView extends FrameLayout {
if (mIsChildInGroup) {
boolean isNewView = mSingleLineView == null;
mSingleLineView = mHybridGroupManager.bindFromNotification(
mSingleLineView, mStatusBarNotification.getNotification());
mSingleLineView, mContractedChild, mStatusBarNotification, this);
if (isNewView) {
updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
mSingleLineView, mSingleLineView);

View File

@@ -46,13 +46,13 @@ class NotificationConversationTemplateViewWrapper constructor(
)
private val conversationLayout: ConversationLayout = view as ConversationLayout
private lateinit var conversationIcon: CachingIconView
private lateinit var conversationIconView: CachingIconView
private lateinit var conversationBadgeBg: View
private lateinit var expandButton: View
private lateinit var expandButtonContainer: View
private lateinit var imageMessageContainer: ViewGroup
private lateinit var messagingLinearLayout: MessagingLinearLayout
private lateinit var conversationTitle: View
private lateinit var conversationTitleView: View
private lateinit var importanceRing: View
private lateinit var appName: View
private var facePileBottomBg: View? = null
@@ -63,7 +63,7 @@ class NotificationConversationTemplateViewWrapper constructor(
messagingLinearLayout = conversationLayout.messagingLinearLayout
imageMessageContainer = conversationLayout.imageMessageContainer
with(conversationLayout) {
conversationIcon = requireViewById(com.android.internal.R.id.conversation_icon)
conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
conversationBadgeBg =
requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
expandButton = requireViewById(com.android.internal.R.id.expand_button)
@@ -71,7 +71,7 @@ class NotificationConversationTemplateViewWrapper constructor(
requireViewById(com.android.internal.R.id.expand_button_container)
importanceRing = requireViewById(com.android.internal.R.id.conversation_icon_badge_ring)
appName = requireViewById(com.android.internal.R.id.app_name_text)
conversationTitle = requireViewById(com.android.internal.R.id.conversation_text)
conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
facePileTop = findViewById(com.android.internal.R.id.conversation_face_pile_top)
facePileBottom = findViewById(com.android.internal.R.id.conversation_face_pile_bottom)
facePileBottomBg =
@@ -93,7 +93,7 @@ class NotificationConversationTemplateViewWrapper constructor(
addTransformedViews(
messagingLinearLayout,
appName,
conversationTitle)
conversationTitleView)
// Let's ignore the image message container since that is transforming as part of the
// messages already
@@ -124,7 +124,7 @@ class NotificationConversationTemplateViewWrapper constructor(
)
addViewsTransformingToSimilar(
conversationIcon,
conversationIconView,
conversationBadgeBg,
expandButton,
importanceRing,
@@ -136,29 +136,27 @@ class NotificationConversationTemplateViewWrapper constructor(
override fun setShelfIconVisible(visible: Boolean) {
if (conversationLayout.isImportantConversation) {
if (conversationIcon.visibility != GONE) {
conversationIcon.setForceHidden(visible);
if (conversationIconView.visibility != GONE) {
conversationIconView.isForceHidden = visible
// We don't want the small icon to be hidden by the extended wrapper, as force
// hiding the conversationIcon will already do that via its listener.
return;
return
}
}
super.setShelfIconVisible(visible)
}
override fun getShelfTransformationTarget(): View? {
if (conversationLayout.isImportantConversation) {
if (conversationIcon.visibility != GONE) {
return conversationIcon
} else {
// A notification with a fallback icon was set to important. Currently
// the transformation doesn't work for these and needs to be fixed. In the meantime
// those are using the icon.
return super.getShelfTransformationTarget();
}
}
return super.getShelfTransformationTarget()
}
override fun getShelfTransformationTarget(): View? =
if (conversationLayout.isImportantConversation)
if (conversationIconView.visibility != GONE)
conversationIconView
else
// A notification with a fallback icon was set to important. Currently
// the transformation doesn't work for these and needs to be fixed.
// In the meantime those are using the icon.
super.getShelfTransformationTarget()
else
super.getShelfTransformationTarget()
override fun setRemoteInputVisible(visible: Boolean) =
conversationLayout.showHistoricMessages(visible)

View File

@@ -27,11 +27,13 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.NotificationExpandButton;
import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.TransformableView;
@@ -60,12 +62,14 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
private NotificationExpandButton mExpandButton;
protected NotificationHeaderView mNotificationHeader;
private TextView mHeaderText;
private TextView mAppNameText;
private ImageView mWorkProfileImage;
private View mCameraIcon;
private View mMicIcon;
private View mOverlayIcon;
private View mAppOps;
private View mAudiblyAlertedIcon;
private FrameLayout mIconContainer;
private boolean mIsLowPriority;
private boolean mTransformLowPriorityTitle;
@@ -108,8 +112,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
}
protected void resolveHeaderViews() {
mIconContainer = mView.findViewById(com.android.internal.R.id.header_icon_container);
mIcon = mView.findViewById(com.android.internal.R.id.icon);
mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
mAppNameText = mView.findViewById(com.android.internal.R.id.app_name_text);
mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
@@ -182,6 +188,61 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
}
}
public void applyConversationSkin() {
if (mAppNameText != null) {
mAppNameText.setTextAppearance(
com.android.internal.R.style
.TextAppearance_DeviceDefault_Notification_Conversation_AppName);
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams();
layoutParams.setMarginStart(0);
}
if (mIconContainer != null) {
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams();
layoutParams.width =
mIconContainer.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.conversation_content_start);
final int marginStart =
mIconContainer.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_start);
layoutParams.setMarginStart(marginStart * -1);
}
if (mIcon != null) {
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
layoutParams.setMarginEnd(0);
}
}
public void clearConversationSkin() {
if (mAppNameText != null) {
final int textAppearance = Utils.getThemeAttr(
mAppNameText.getContext(),
com.android.internal.R.attr.notificationHeaderTextAppearance,
com.android.internal.R.style.TextAppearance_DeviceDefault_Notification_Info);
mAppNameText.setTextAppearance(textAppearance);
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams();
final int marginStart = mAppNameText.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_header_app_name_margin_start);
layoutParams.setMarginStart(marginStart);
}
if (mIconContainer != null) {
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.setMarginStart(0);
}
if (mIcon != null) {
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
final int marginEnd = mIcon.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_header_icon_margin_end);
layoutParams.setMarginEnd(marginEnd);
}
}
/**
* Adds the remaining TransformTypes to the TransformHelper. This is done to make sure that each
* child is faded automatically and doesn't have to be manually added.

View File

@@ -40,6 +40,7 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.HybridGroupManager;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import java.util.ArrayList;
@@ -99,6 +100,7 @@ public class NotificationChildrenContainer extends ViewGroup {
private boolean mIsLowPriority;
private OnClickListener mHeaderClickListener;
private ViewGroup mCurrentHeader;
private boolean mIsConversation;
private boolean mShowDividersWhenExpanded;
private boolean mHideDividersDuringExpand;
@@ -122,7 +124,7 @@ public class NotificationChildrenContainer extends ViewGroup {
public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mHybridGroupManager = new HybridGroupManager(getContext(), this);
mHybridGroupManager = new HybridGroupManager(getContext());
initDimens();
setClipChildren(false);
}
@@ -308,8 +310,9 @@ public class NotificationChildrenContainer extends ViewGroup {
return mAttachedChildren.size();
}
public void recreateNotificationHeader(OnClickListener listener) {
public void recreateNotificationHeader(OnClickListener listener, boolean isConversation) {
mHeaderClickListener = listener;
mIsConversation = isConversation;
StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
notification.getNotification());
@@ -328,7 +331,16 @@ public class NotificationChildrenContainer extends ViewGroup {
header.reapply(getContext(), mNotificationHeader);
}
mNotificationHeaderWrapper.onContentUpdated(mContainingNotification);
recreateLowPriorityHeader(builder);
if (mNotificationHeaderWrapper instanceof NotificationHeaderViewWrapper) {
NotificationHeaderViewWrapper headerWrapper =
(NotificationHeaderViewWrapper) mNotificationHeaderWrapper;
if (isConversation) {
headerWrapper.applyConversationSkin();
} else {
headerWrapper.clearConversationSkin();
}
}
recreateLowPriorityHeader(builder, isConversation);
updateHeaderVisibility(false /* animate */);
updateChildrenHeaderAppearance();
}
@@ -338,7 +350,7 @@ public class NotificationChildrenContainer extends ViewGroup {
*
* @param builder a builder to reuse. Otherwise the builder will be recovered.
*/
private void recreateLowPriorityHeader(Notification.Builder builder) {
private void recreateLowPriorityHeader(Notification.Builder builder, boolean isConversation) {
RemoteViews header;
StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
if (mIsLowPriority) {
@@ -362,6 +374,15 @@ public class NotificationChildrenContainer extends ViewGroup {
header.reapply(getContext(), mNotificationHeaderLowPriority);
}
mNotificationHeaderWrapperLowPriority.onContentUpdated(mContainingNotification);
if (mNotificationHeaderWrapper instanceof NotificationHeaderViewWrapper) {
NotificationHeaderViewWrapper headerWrapper =
(NotificationHeaderViewWrapper) mNotificationHeaderWrapper;
if (isConversation) {
headerWrapper.applyConversationSkin();
} else {
headerWrapper.clearConversationSkin();
}
}
resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, calculateDesiredHeader());
} else {
removeView(mNotificationHeaderLowPriority);
@@ -378,7 +399,7 @@ public class NotificationChildrenContainer extends ViewGroup {
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
if (mUntruncatedChildCount > maxAllowedVisibleChildren) {
int number = mUntruncatedChildCount - maxAllowedVisibleChildren;
mOverflowNumber = mHybridGroupManager.bindOverflowNumber(mOverflowNumber, number);
mOverflowNumber = mHybridGroupManager.bindOverflowNumber(mOverflowNumber, number, this);
if (mGroupOverFlowState == null) {
mGroupOverFlowState = new ViewState();
mNeverAppliedGroupState = true;
@@ -1141,7 +1162,7 @@ public class NotificationChildrenContainer extends ViewGroup {
removeView(mNotificationHeaderLowPriority);
mNotificationHeaderLowPriority = null;
}
recreateNotificationHeader(listener);
recreateNotificationHeader(listener, mIsConversation);
initDimens();
for (int i = 0; i < mDividers.size(); i++) {
View prevDivider = mDividers.get(i);
@@ -1225,7 +1246,7 @@ public class NotificationChildrenContainer extends ViewGroup {
public void setIsLowPriority(boolean isLowPriority) {
mIsLowPriority = isLowPriority;
if (mContainingNotification != null) { /* we're not yet set up yet otherwise */
recreateLowPriorityHeader(null /* existingBuilder */);
recreateLowPriorityHeader(null /* existingBuilder */, mIsConversation);
updateHeaderVisibility(false /* animate */);
}
if (mUserLocked) {

View File

@@ -146,7 +146,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase {
@Test
public void testRecreateNotificationHeader_hasHeader() {
mChildrenContainer.recreateNotificationHeader(null);
mChildrenContainer.recreateNotificationHeader(null, false);
Assert.assertNotNull("Children container must have a header after recreation",
mChildrenContainer.getCurrentHeaderView());
}