summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt14
-rw-r--r--core/java/android/app/Notification.java543
-rw-r--r--core/java/com/android/internal/widget/CallLayout.java137
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java137
-rw-r--r--core/java/com/android/internal/widget/EmphasizedNotificationButton.java63
-rw-r--r--core/java/com/android/internal/widget/NotificationActionListLayout.java47
-rw-r--r--core/java/com/android/internal/widget/PeopleHelper.java179
-rw-r--r--core/res/res/drawable/btn_notification_emphasized.xml2
-rw-r--r--core/res/res/drawable/ic_call_answer.xml34
-rw-r--r--core/res/res/drawable/ic_call_decline.xml36
-rw-r--r--core/res/res/layout/notification_material_action_emphasized.xml1
-rw-r--r--core/res/res/layout/notification_template_conversation_header.xml166
-rw-r--r--core/res/res/layout/notification_template_conversation_icon_container.xml97
-rw-r--r--core/res/res/layout/notification_template_material_call.xml92
-rw-r--r--core/res/res/layout/notification_template_material_conversation.xml228
-rw-r--r--core/res/res/layout/notification_top_line_views.xml12
-rw-r--r--core/res/res/values/colors.xml5
-rw-r--r--core/res/res/values/dimens.xml11
-rw-r--r--core/res/res/values/ids.xml4
-rw-r--r--core/res/res/values/strings.xml12
-rw-r--r--core/res/res/values/symbols.xml19
-rw-r--r--packages/SystemUI/res/values/dimens.xml6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java2
27 files changed, 1608 insertions, 403 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index f5780ab0eec2..bd1339fe12a8 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5555,15 +5555,19 @@ package android.app {
field public static final int DEFAULT_LIGHTS = 4; // 0x4
field public static final int DEFAULT_SOUND = 1; // 0x1
field public static final int DEFAULT_VIBRATE = 2; // 0x2
+ field public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
field public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
field public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final String EXTRA_BIG_TEXT = "android.bigText";
+ field public static final String EXTRA_CALL_PERSON = "android.callPerson";
field public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
field public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
field public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
field public static final String EXTRA_COLORIZED = "android.colorized";
field public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
field public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+ field public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
+ field public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
field public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
field public static final String EXTRA_INFO_TEXT = "android.infoText";
field public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
@@ -5595,6 +5599,8 @@ package android.app {
field public static final String EXTRA_TEXT_LINES = "android.textLines";
field public static final String EXTRA_TITLE = "android.title";
field public static final String EXTRA_TITLE_BIG = "android.title.big";
+ field public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
+ field public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
field public static final int FLAG_AUTO_CANCEL = 16; // 0x10
field public static final int FLAG_BUBBLE = 4096; // 0x1000
field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40
@@ -5843,6 +5849,14 @@ package android.app {
method @NonNull public android.app.Notification.Builder setWhen(long);
}
+ public static class Notification.CallStyle extends android.app.Notification.Style {
+ method @NonNull public static android.app.Notification.CallStyle forIncomingCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent, @NonNull android.app.PendingIntent);
+ method @NonNull public static android.app.Notification.CallStyle forOngoingCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent);
+ method @NonNull public static android.app.Notification.CallStyle forScreeningCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent, @NonNull android.app.PendingIntent);
+ method @NonNull public android.app.Notification.CallStyle setVerificationIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.app.Notification.CallStyle setVerificationText(@Nullable CharSequence);
+ }
+
public static final class Notification.CarExtender implements android.app.Notification.Extender {
ctor public Notification.CarExtender();
ctor public Notification.CarExtender(android.app.Notification);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 49f508d83f91..c242fd466c41 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -22,7 +22,10 @@ import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.ColorInt;
+import android.annotation.ColorRes;
import android.annotation.DimenRes;
import android.annotation.Dimension;
import android.annotation.DrawableRes;
@@ -33,6 +36,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringRes;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -403,6 +407,7 @@ public class Notification implements Parcelable
STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
+ STANDARD_LAYOUTS.add(R.layout.notification_template_material_call);
STANDARD_LAYOUTS.add(R.layout.notification_template_header);
}
@@ -649,7 +654,7 @@ public class Notification implements Parcelable
private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
- MessagingStyle.class);
+ MessagingStyle.class, CallStyle.class);
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT,
@@ -1318,6 +1323,53 @@ public class Notification implements Parcelable
public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
/**
+ * {@link #extras} key: the type of call represented by the
+ * {@link android.app.Notification.CallStyle} notification. This extra is an int.
+ * @hide
+ */
+ public static final String EXTRA_CALL_TYPE = "android.callType";
+
+ /**
+ * {@link #extras} key: the person to be displayed as calling for the
+ * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}.
+ */
+ public static final String EXTRA_CALL_PERSON = "android.callPerson";
+
+ /**
+ * {@link #extras} key: the icon to be displayed as a verification status of the caller on a
+ * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}.
+ */
+ public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
+
+ /**
+ * {@link #extras} key: the text to be displayed as a verification status of the caller on a
+ * {@link android.app.Notification.CallStyle} notification. This extra is a
+ * {@link CharSequence}.
+ */
+ public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
+
+ /**
+ * {@link #extras} key: the intent to be sent when the users answers a
+ * {@link android.app.Notification.CallStyle} notification. This extra is a
+ * {@link PendingIntent}.
+ */
+ public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
+
+ /**
+ * {@link #extras} key: the intent to be sent when the users declines a
+ * {@link android.app.Notification.CallStyle} notification. This extra is a
+ * {@link PendingIntent}.
+ */
+ public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
+
+ /**
+ * {@link #extras} key: the intent to be sent when the users hangs up a
+ * {@link android.app.Notification.CallStyle} notification. This extra is a
+ * {@link PendingIntent}.
+ */
+ public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
+
+ /**
* {@link #extras} key: whether the notification should be colorized as
* supplied to {@link Builder#setColorized(boolean)}.
*/
@@ -5876,11 +5928,11 @@ public class Notification implements Parcelable
return summary;
}
- private RemoteViews generateActionButton(Action action, boolean emphazisedMode,
+ private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
StandardTemplateParams p) {
final boolean tombstone = (action.actionIntent == null);
RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
- emphazisedMode ? getEmphasizedActionLayoutResource()
+ emphasizedMode ? getEmphasizedActionLayoutResource()
: tombstone ? getActionTombstoneLayoutResource()
: getActionLayoutResource());
if (!tombstone) {
@@ -5890,43 +5942,42 @@ public class Notification implements Parcelable
if (action.mRemoteInputs != null) {
button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
}
- if (emphazisedMode) {
+ if (emphasizedMode) {
// change the background bgColor
CharSequence title = action.title;
- ColorStateList[] outResultColor = null;
+ ColorStateList[] outResultColor = new ColorStateList[1];
int background = resolveBackgroundColor(p);
if (isLegacy()) {
title = ContrastColorUtil.clearColorSpans(title);
} else {
- outResultColor = new ColorStateList[1];
title = ensureColorSpanContrast(title, background, outResultColor);
}
button.setTextViewText(R.id.action0, processTextSpans(title));
- setTextViewColorPrimary(button, R.id.action0, p);
- int rippleColor;
- boolean hasColorOverride = outResultColor != null && outResultColor[0] != null;
+ int textColor = getPrimaryTextColor(p);
+ boolean hasColorOverride = outResultColor[0] != null;
if (hasColorOverride) {
// There's a span spanning the full text, let's take it and use it as the
// background color
background = outResultColor[0].getDefaultColor();
- int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
+ textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
background, mInNightMode);
- button.setTextColor(R.id.action0, textColor);
- rippleColor = textColor;
} else if (getRawColor(p) != COLOR_DEFAULT && !isColorized(p)
&& mTintActionButtons && !mInNightMode) {
- rippleColor = resolveContrastColor(p);
- button.setTextColor(R.id.action0, rippleColor);
- } else {
- rippleColor = getPrimaryTextColor(p);
+ textColor = resolveContrastColor(p);
}
+ button.setTextColor(R.id.action0, textColor);
// We only want about 20% alpha for the ripple
- rippleColor = (rippleColor & 0x00ffffff) | 0x33000000;
+ final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
button.setColorStateList(R.id.action0, "setRippleColor",
ColorStateList.valueOf(rippleColor));
button.setColorStateList(R.id.action0, "setButtonBackground",
ColorStateList.valueOf(background));
button.setBoolean(R.id.action0, "setHasStroke", !hasColorOverride);
+ if (p.mAllowActionIcons) {
+ button.setImageViewIcon(R.id.action0, action.getIcon());
+ boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
+ button.setBoolean(R.id.action0, "setWrapModePriority", priority);
+ }
} else {
button.setTextViewText(R.id.action0, processTextSpans(
processLegacyText(action.title)));
@@ -5936,8 +5987,12 @@ public class Notification implements Parcelable
button.setTextColor(R.id.action0, resolveContrastColor(p));
}
}
- button.setIntTag(R.id.action0, R.id.notification_action_index_tag,
- mActions.indexOf(action));
+ // CallStyle notifications add action buttons which don't actually exist in mActions,
+ // so we have to omit the index in that case.
+ int actionIndex = mActions.indexOf(action);
+ if (actionIndex != -1) {
+ button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex);
+ }
return button;
}
@@ -6371,6 +6426,10 @@ public class Notification implements Parcelable
return R.layout.notification_template_material_conversation;
}
+ private int getCallLayoutResource() {
+ return R.layout.notification_template_material_call;
+ }
+
private int getActionLayoutResource() {
return R.layout.notification_material_action;
}
@@ -8039,6 +8098,10 @@ public class Notification implements Parcelable
: mBuilder.getMessagingLayoutResource(),
p,
bindResult);
+ if (isConversationLayout) {
+ mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
+ mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
+ }
addExtras(mBuilder.mN.extras);
if (!isConversationLayout) {
@@ -8925,6 +8988,441 @@ public class Notification implements Parcelable
}
}
+
+
+ /**
+ * Helper class for generating large-format notifications that include a large image attachment.
+ *
+ * Here's how you'd set the <code>CallStyle</code> on a notification:
+ * <pre class="prettyprint">
+ * Notification notif = new Notification.Builder(mContext)
+ * .setSmallIcon(R.drawable.new_post)
+ * .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent))
+ * .build();
+ * </pre>
+ */
+ public static class CallStyle extends Style {
+ private static final int CALL_TYPE_INCOMING = 1;
+ private static final int CALL_TYPE_ONGOING = 2;
+ private static final int CALL_TYPE_SCREENING = 3;
+
+ /**
+ * This is a key used privately on the action.extras to give spacing priority
+ * to the required call actions
+ */
+ private static final String KEY_ACTION_PRIORITY = "key_action_priority";
+
+ private int mCallType;
+ private Person mPerson;
+ private PendingIntent mAnswerIntent;
+ private PendingIntent mDeclineIntent;
+ private PendingIntent mHangUpIntent;
+ private Icon mVerificationIcon;
+ private CharSequence mVerificationText;
+
+ CallStyle() {
+ }
+
+ /**
+ * Create a CallStyle for an incoming call.
+ * This notification will have a decline and an answer action, will allow a single
+ * custom {@link Builder#addAction(Action) action}, and will have a default
+ * {@link Builder#setContentText(CharSequence) content text} for an incoming call.
+ *
+ * @param person The person displayed as the caller.
+ * The person also needs to have a non-empty name associated with it.
+ * @param declineIntent The intent to be sent when the user taps the decline action
+ * @param answerIntent The intent to be sent when the user taps the answer action
+ */
+ @NonNull
+ public static CallStyle forIncomingCall(@NonNull Person person,
+ @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) {
+ return new CallStyle(CALL_TYPE_INCOMING, person,
+ null /* hangUpIntent */,
+ requireNonNull(declineIntent, "declineIntent is required"),
+ requireNonNull(answerIntent, "answerIntent is required")
+ );
+ }
+
+ /**
+ * Create a CallStyle for an ongoing call.
+ * This notification will have a hang up action, will allow up to two
+ * custom {@link Builder#addAction(Action) actions}, and will have a default
+ * {@link Builder#setContentText(CharSequence) content text} for an ongoing call.
+ *
+ * @param person The person displayed as being on the other end of the call.
+ * The person also needs to have a non-empty name associated with it.
+ * @param hangUpIntent The intent to be sent when the user taps the hang up action
+ */
+ @NonNull
+ public static CallStyle forOngoingCall(@NonNull Person person,
+ @NonNull PendingIntent hangUpIntent) {
+ return new CallStyle(CALL_TYPE_ONGOING, person,
+ requireNonNull(hangUpIntent, "hangUpIntent is required"),
+ null /* declineIntent */,
+ null /* answerIntent */
+ );
+ }
+
+ /**
+ * Create a CallStyle for a call that is being screened.
+ * This notification will have a hang up and an answer action, will allow a single
+ * custom {@link Builder#addAction(Action) action}, and will have a default
+ * {@link Builder#setContentText(CharSequence) content text} for a call that is being
+ * screened.
+ *
+ * @param person The person displayed as the caller.
+ * The person also needs to have a non-empty name associated with it.
+ * @param hangUpIntent The intent to be sent when the user taps the hang up action
+ * @param answerIntent The intent to be sent when the user taps the answer action
+ */
+ @NonNull
+ public static CallStyle forScreeningCall(@NonNull Person person,
+ @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) {
+ return new CallStyle(CALL_TYPE_SCREENING, person,
+ requireNonNull(hangUpIntent, "hangUpIntent is required"),
+ null /* declineIntent */,
+ requireNonNull(answerIntent, "answerIntent is required")
+ );
+ }
+
+ /**
+ * @param person The person displayed for the incoming call.
+ * The user also needs to have a non-empty name associated with it.
+ * @param hangUpIntent The intent to be sent when the user taps the hang up action
+ * @param declineIntent The intent to be sent when the user taps the decline action
+ * @param answerIntent The intent to be sent when the user taps the answer action
+ */
+ private CallStyle(int callType, @NonNull Person person,
+ @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent,
+ @Nullable PendingIntent answerIntent) {
+ if (person == null || TextUtils.isEmpty(person.getName())) {
+ throw new IllegalArgumentException("person must have a non-empty a name");
+ }
+ mCallType = callType;
+ mPerson = person;
+ mAnswerIntent = answerIntent;
+ mDeclineIntent = declineIntent;
+ mHangUpIntent = hangUpIntent;
+ }
+
+ /**
+ * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text}
+ * as a verification status of the caller.
+ */
+ @NonNull
+ public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) {
+ mVerificationIcon = verificationIcon;
+ return this;
+ }
+
+ /**
+ * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon}
+ * as a verification status of the caller.
+ */
+ @NonNull
+ public CallStyle setVerificationText(@Nullable CharSequence verificationText) {
+ mVerificationText = safeCharSequence(verificationText);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean displayCustomViewInline() {
+ // This is a lie; True is returned to make sure that the custom view is not used
+ // instead of the template, but it will not actually be included.
+ return true;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void purgeResources() {
+ super.purgeResources();
+ if (mVerificationIcon != null) {
+ mVerificationIcon.convertToAshmem();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void reduceImageSizes(Context context) {
+ super.reduceImageSizes(context);
+ if (mVerificationIcon != null) {
+ int rightIconSize = context.getResources().getDimensionPixelSize(
+ ActivityManager.isLowRamDeviceStatic()
+ ? R.dimen.notification_right_icon_size_low_ram
+ : R.dimen.notification_right_icon_size);
+ mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public RemoteViews makeContentView(boolean increasedHeight) {
+ return makeCallLayout();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ return makeCallLayout();
+ }
+
+ /**
+ * @hide
+ */
+ public RemoteViews makeBigContentView() {
+ return makeCallLayout();
+ }
+
+ @NonNull
+ private Action makeNegativeAction() {
+ if (mDeclineIntent == null) {
+ return makeAction(R.drawable.ic_call_decline,
+ R.string.call_notification_hang_up_action,
+ R.color.call_notification_decline_color, mHangUpIntent);
+ } else {
+ return makeAction(R.drawable.ic_call_decline,
+ R.string.call_notification_decline_action,
+ R.color.call_notification_decline_color, mDeclineIntent);
+ }
+ }
+
+ @Nullable
+ private Action makeAnswerAction() {
+ return mAnswerIntent == null ? null : makeAction(R.drawable.ic_call_answer,
+ R.string.call_notification_answer_action,
+ R.color.call_notification_answer_color, mAnswerIntent);
+ }
+
+ @NonNull
+ private Action makeAction(@DrawableRes int icon, @StringRes int title,
+ @ColorRes int colorRes, PendingIntent intent) {
+ Action action = new Action.Builder(Icon.createWithResource("", icon),
+ new SpannableStringBuilder().append(mBuilder.mContext.getString(title),
+ new ForegroundColorSpan(mBuilder.mContext.getColor(colorRes)),
+ SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE),
+ intent).build();
+ action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true);
+ return action;
+ }
+
+ private ArrayList<Action> makeActionsList() {
+ final Action negativeAction = makeNegativeAction();
+ final Action answerAction = makeAnswerAction();
+
+ ArrayList<Action> actions = new ArrayList<>(MAX_ACTION_BUTTONS);
+ final Action lastAction;
+ if (answerAction == null) {
+ // If there's no answer action, put the hang up / decline action at the end
+ lastAction = negativeAction;
+ } else {
+ // Otherwise put the answer action at the end, and put the decline action at start.
+ actions.add(negativeAction);
+ lastAction = answerAction;
+ }
+ // For consistency with the standard actions bar, contextual actions are ignored.
+ for (Action action : Builder.filterOutContextualActions(mBuilder.mActions)) {
+ if (actions.size() >= MAX_ACTION_BUTTONS - 1) {
+ break;
+ }
+ actions.add(action);
+ }
+ actions.add(lastAction);
+ return actions;
+ }
+
+ private RemoteViews makeCallLayout() {
+ Bundle extras = mBuilder.mN.extras;
+ CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
+ if (text == null) {
+ text = getDefaultText();
+ }
+
+ // Bind standard template
+ StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .allowActionIcons(true)
+ .hideLargeIcon(true)
+ .text(text)
+ .summaryText(mBuilder.processLegacyText(mVerificationText));
+ // TODO(b/179178086): hide the snooze button
+ RemoteViews contentView = mBuilder.applyStandardTemplate(
+ mBuilder.getCallLayoutResource(), p, null /* result */);
+
+ // Bind actions.
+ mBuilder.resetStandardTemplateWithActions(contentView);
+ bindCallActions(contentView, p);
+
+ // Bind some extra conversation-specific header fields.
+ mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
+ mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
+ contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
+ bindCallerVerification(contentView, p);
+
+ // Bind some custom CallLayout properties
+ contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+ mBuilder.isColorized(p)
+ ? mBuilder.getPrimaryTextColor(p)
+ : mBuilder.resolveContrastColor(p));
+ contentView.setInt(R.id.status_bar_latest_event_content,
+ "setNotificationBackgroundColor", mBuilder.resolveBackgroundColor(p));
+ contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
+ mBuilder.mN.mLargeIcon);
+ contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
+ mBuilder.mN.extras);
+
+ return contentView;
+ }
+
+ private void bindCallActions(RemoteViews view, StandardTemplateParams p) {
+ view.setViewVisibility(R.id.actions_container, View.VISIBLE);
+ view.setViewVisibility(R.id.actions, View.VISIBLE);
+ view.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
+ RemoteViews.MARGIN_BOTTOM, 0);
+
+ // Clear view padding to allow buttons to start on the left edge.
+ // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
+ view.setViewPadding(R.id.actions, 0, 0, 0, 0);
+ // Add an optional indent that will make buttons start at the correct column when
+ // there is enough space to do so (and fall back to the left edge if not).
+ view.setInt(R.id.actions, "setCollapsibleIndentDimen",
+ R.dimen.call_notification_collapsible_indent);
+
+ // Emphasize so that buttons have borders or colored backgrounds
+ boolean emphasizedMode = true;
+ view.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
+ // Use "wrap_content" (unlike normal emphasized mode) and allow prioritizing the
+ // required actions (Answer, Decline, and Hang Up).
+ view.setBoolean(R.id.actions, "setPrioritizedWrapMode", true);
+
+ // Create the buttons for the generated actions list.
+ int i = 0;
+ for (Action action : makeActionsList()) {
+ final RemoteViews button = mBuilder.generateActionButton(action, emphasizedMode, p);
+ if (i > 0) {
+ // Clear start margin from non-first buttons to reduce the gap between buttons.
+ // (8dp remaining gap is from all buttons' standard 4dp inset).
+ button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
+ }
+ view.addView(R.id.actions, button);
+ ++i;
+ }
+ }
+
+ private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) {
+ if (mVerificationIcon != null) {
+ contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon);
+ contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */,
+ mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
+ contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE);
+ } else {
+ contentView.setViewVisibility(R.id.verification_icon, View.GONE);
+ }
+ if (!TextUtils.isEmpty(mVerificationText)) {
+ contentView.setTextViewText(R.id.verification_text, mVerificationText);
+ mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p);
+ contentView.setViewVisibility(R.id.verification_text, View.VISIBLE);
+ } else {
+ contentView.setViewVisibility(R.id.verification_text, View.GONE);
+ }
+ }
+
+ @Nullable
+ private String getDefaultText() {
+ switch (mCallType) {
+ case CALL_TYPE_INCOMING:
+ return mBuilder.mContext.getString(R.string.call_notification_incoming_text);
+ case CALL_TYPE_ONGOING:
+ return mBuilder.mContext.getString(R.string.call_notification_ongoing_text);
+ case CALL_TYPE_SCREENING:
+ return mBuilder.mContext.getString(R.string.call_notification_screening_text);
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public void addExtras(Bundle extras) {
+ super.addExtras(extras);
+ extras.putInt(EXTRA_CALL_TYPE, mCallType);
+ extras.putParcelable(EXTRA_CALL_PERSON, mPerson);
+ if (mVerificationIcon != null) {
+ extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon);
+ }
+ if (mVerificationText != null) {
+ extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText);
+ }
+ if (mAnswerIntent != null) {
+ extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent);
+ }
+ if (mDeclineIntent != null) {
+ extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent);
+ }
+ if (mHangUpIntent != null) {
+ extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent);
+ }
+ fixTitleAndTextExtras(extras);
+ }
+
+ private void fixTitleAndTextExtras(Bundle extras) {
+ CharSequence sender = mPerson != null ? mPerson.getName() : null;
+ if (sender != null) {
+ extras.putCharSequence(EXTRA_TITLE, sender);
+ }
+ if (extras.getCharSequence(EXTRA_TEXT) == null) {
+ extras.putCharSequence(EXTRA_TEXT, getDefaultText());
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ protected void restoreFromExtras(Bundle extras) {
+ super.restoreFromExtras(extras);
+ mCallType = extras.getInt(EXTRA_CALL_TYPE);
+ mPerson = extras.getParcelable(EXTRA_CALL_PERSON);
+ mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON);
+ mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT);
+ mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT);
+ mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT);
+ mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean hasSummaryInHeader() {
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Style other) {
+ if (other == null || getClass() != other.getClass()) {
+ return true;
+ }
+ CallStyle otherS = (CallStyle) other;
+ return !Objects.equals(mCallType, otherS.mCallType)
+ || !Objects.equals(mPerson, otherS.mPerson)
+ || !Objects.equals(mVerificationText, otherS.mVerificationText);
+ }
+ }
+
/**
* Notification style for custom views that are decorated by the system
*
@@ -11376,6 +11874,7 @@ public class Notification implements Parcelable
boolean mHideActions;
boolean mHideProgress;
boolean mPromotePicture;
+ boolean mAllowActionIcons;
CharSequence title;
CharSequence text;
CharSequence headerTextSecondary;
@@ -11392,6 +11891,7 @@ public class Notification implements Parcelable
mHideActions = false;
mHideProgress = false;
mPromotePicture = false;
+ mAllowActionIcons = false;
title = null;
text = null;
summaryText = null;
@@ -11431,6 +11931,11 @@ public class Notification implements Parcelable
return this;
}
+ final StandardTemplateParams allowActionIcons(boolean allowActionIcons) {
+ this.mAllowActionIcons = allowActionIcons;
+ return this;
+ }
+
final StandardTemplateParams promotePicture(boolean promotePicture) {
this.mPromotePicture = promotePicture;
return this;
diff --git a/core/java/com/android/internal/widget/CallLayout.java b/core/java/com/android/internal/widget/CallLayout.java
new file mode 100644
index 000000000000..6cc5a4aacda5
--- /dev/null
+++ b/core/java/com/android/internal/widget/CallLayout.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 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.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.app.Person;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+/**
+ * A custom-built layout for the Notification.CallStyle.
+ */
+@RemoteViews.RemoteView
+public class CallLayout extends FrameLayout {
+ private final PeopleHelper mPeopleHelper = new PeopleHelper();
+
+ private int mLayoutColor;
+ private Icon mLargeIcon;
+ private Person mUser;
+
+ private CachingIconView mConversationIconView;
+ private CachingIconView mIcon;
+ private CachingIconView mConversationIconBadgeBg;
+ private TextView mConversationText;
+
+ public CallLayout(@NonNull Context context) {
+ super(context);
+ }
+
+ public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mPeopleHelper.init(getContext());
+ mConversationText = findViewById(R.id.conversation_text);
+ mConversationIconView = findViewById(R.id.conversation_icon);
+ mIcon = findViewById(R.id.icon);
+ mConversationIconBadgeBg = findViewById(R.id.conversation_icon_badge_bg);
+
+ // When the small icon is gone, hide the rest of the badge
+ mIcon.setOnForceHiddenChangedListener((forceHidden) -> {
+ mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
+ });
+ }
+
+ private void updateCallLayout() {
+ CharSequence callerName = "";
+ String symbol = "";
+ Icon icon = null;
+ if (mUser != null) {
+ icon = mUser.getIcon();
+ callerName = mUser.getName();
+ symbol = mPeopleHelper.findNamePrefix(callerName, "");
+ }
+ if (icon == null) {
+ icon = mLargeIcon;
+ }
+ if (icon == null) {
+ icon = mPeopleHelper.createAvatarSymbol(callerName, symbol, mLayoutColor);
+ }
+ // TODO(b/179178086): crop/clip the icon to a circle?
+ mConversationIconView.setImageIcon(icon);
+ mConversationText.setText(callerName);
+ }
+
+ @RemotableViewMethod
+ public void setLayoutColor(int color) {
+ mLayoutColor = color;
+ }
+
+ /**
+ * @param color the color of the notification background
+ */
+ @RemotableViewMethod
+ public void setNotificationBackgroundColor(int color) {
+ mConversationIconBadgeBg.setImageTintList(ColorStateList.valueOf(color));
+ }
+
+ @RemotableViewMethod
+ public void setLargeIcon(Icon largeIcon) {
+ mLargeIcon = largeIcon;
+ }
+
+ /**
+ * Set the notification extras so that this layout has access
+ */
+ @RemotableViewMethod
+ public void setData(Bundle extras) {
+ setUser(extras.getParcelable(Notification.EXTRA_CALL_PERSON));
+ updateCallLayout();
+ }
+
+ private void setUser(Person user) {
+ mUser = user;
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 40e671ffd27c..1b1e0bfb3a58 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -18,8 +18,6 @@ package com.android.internal.widget;
import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_EXTERNAL;
import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_INLINE;
-import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN;
-import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -34,10 +32,7 @@ import android.app.Person;
import android.app.RemoteInputHistoryItem;
import android.content.Context;
import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
@@ -68,14 +63,12 @@ import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.graphics.ColorUtils;
-import com.android.internal.util.ContrastColorUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Consumer;
-import java.util.regex.Pattern;
/**
* A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
@@ -85,16 +78,6 @@ import java.util.regex.Pattern;
public class ConversationLayout extends FrameLayout
implements ImageMessageConsumer, IMessagingLayout {
- private static final float COLOR_SHIFT_AMOUNT = 60;
- /**
- * Pattern for filter some ignorable characters.
- * p{Z} for any kind of whitespace or invisible separator.
- * p{C} for any kind of punctuation character.
- */
- private static final Pattern IGNORABLE_CHAR_PATTERN
- = Pattern.compile("[\\p{C}\\p{Z}]");
- private static final Pattern SPECIAL_CHAR_PATTERN
- = Pattern.compile ("[!@#$%&*()_+=|<>?{}\\[\\]~-]");
private static final Consumer<MessagingMessage> REMOVE_MESSAGE
= MessagingMessage::removeMessage;
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
@@ -106,6 +89,7 @@ public class ConversationLayout extends FrameLayout
public static final int IMPORTANCE_ANIM_GROW_DURATION = 250;
public static final int IMPORTANCE_ANIM_SHRINK_DURATION = 200;
public static final int IMPORTANCE_ANIM_SHRINK_DELAY = 25;
+ private final PeopleHelper mPeopleHelper = new PeopleHelper();
private List<MessagingMessage> mMessages = new ArrayList<>();
private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
private MessagingLinearLayout mMessagingLinearLayout;
@@ -114,9 +98,6 @@ public class ConversationLayout extends FrameLayout
private int mLayoutColor;
private int mSenderTextColor;
private int mMessageTextColor;
- private int mAvatarSize;
- private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private Paint mTextPaint = new Paint();
private Icon mAvatarReplacement;
private boolean mIsOneToOne;
private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
@@ -196,6 +177,7 @@ public class ConversationLayout extends FrameLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mPeopleHelper.init(getContext());
mMessagingLinearLayout = findViewById(R.id.notification_messaging);
mActions = findViewById(R.id.actions);
mImageMessageContainer = findViewById(R.id.conversation_image_message_container);
@@ -205,9 +187,6 @@ public class ConversationLayout extends FrameLayout
int size = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels);
mMessagingClipRect = new Rect(0, 0, size, size);
setMessagingClippingDisabled(false);
- mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
- mTextPaint.setTextAlign(Paint.Align.CENTER);
- mTextPaint.setAntiAlias(true);
mConversationIconView = findViewById(R.id.conversation_icon);
mConversationIconContainer = findViewById(R.id.conversation_icon_container);
mIcon = findViewById(R.id.icon);
@@ -250,15 +229,15 @@ public class ConversationLayout extends FrameLayout
});
// When the small icon is gone, hide the rest of the badge
mIcon.setOnForceHiddenChangedListener((forceHidden) -> {
- animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
- animateViewForceHidden(mImportanceRingView, forceHidden);
+ mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
+ mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
});
// When the conversation icon is gone, hide the whole badge
mConversationIconView.setOnForceHiddenChangedListener((forceHidden) -> {
- animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
- animateViewForceHidden(mImportanceRingView, forceHidden);
- animateViewForceHidden(mIcon, forceHidden);
+ mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
+ mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
+ mPeopleHelper.animateViewForceHidden(mIcon, forceHidden);
});
mConversationText = findViewById(R.id.conversation_text);
mExpandButtonContainer = findViewById(R.id.expand_button_container);
@@ -317,28 +296,6 @@ public class ConversationLayout extends FrameLayout
R.dimen.notification_header_separating_margin);
}
- private void animateViewForceHidden(CachingIconView view, boolean forceHidden) {
- boolean nowForceHidden = view.willBeForceHidden() || view.isForceHidden();
- if (forceHidden == nowForceHidden) {
- // We are either already forceHidden or will be
- return;
- }
- view.animate().cancel();
- view.setWillBeForceHidden(forceHidden);
- view.animate()
- .scaleX(forceHidden ? 0.5f : 1.0f)
- .scaleY(forceHidden ? 0.5f : 1.0f)
- .alpha(forceHidden ? 0.0f : 1.0f)
- .setInterpolator(forceHidden ? ALPHA_OUT : ALPHA_IN)
- .setDuration(160);
- if (view.getVisibility() != VISIBLE) {
- view.setForceHidden(forceHidden);
- } else {
- view.animate().withEndAction(() -> view.setForceHidden(forceHidden));
- }
- view.animate().start();
- }
-
@RemotableViewMethod
public void setAvatarReplacement(Icon icon) {
mAvatarReplacement = icon;
@@ -561,7 +518,8 @@ public class ConversationLayout extends FrameLayout
if (mConversationIcon == null) {
Icon avatarIcon = messagingGroup.getAvatarIcon();
if (avatarIcon == null) {
- avatarIcon = createAvatarSymbol(conversationText, "", mLayoutColor);
+ avatarIcon = mPeopleHelper.createAvatarSymbol(conversationText, "",
+ mLayoutColor);
}
mConversationIcon = avatarIcon;
}
@@ -674,11 +632,11 @@ public class ConversationLayout extends FrameLayout
}
}
if (lastIcon == null) {
- lastIcon = createAvatarSymbol(" ", "", mLayoutColor);
+ lastIcon = mPeopleHelper.createAvatarSymbol(" ", "", mLayoutColor);
}
bottomView.setImageIcon(lastIcon);
if (secondLastIcon == null) {
- secondLastIcon = createAvatarSymbol("", "", mLayoutColor);
+ secondLastIcon = mPeopleHelper.createAvatarSymbol("", "", mLayoutColor);
}
topView.setImageIcon(secondLastIcon);
}
@@ -838,8 +796,10 @@ public class ConversationLayout extends FrameLayout
}
private void updateTitleAndNamesDisplay() {
+ // Map of unique names to their prefix
ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
- ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();
+ // Map of single-character string prefix to the only name which uses it, or null if multiple
+ ArrayMap<String, CharSequence> uniqueCharacters = new ArrayMap<>();
for (int i = 0; i < mGroups.size(); i++) {
MessagingGroup group = mGroups.get(i);
CharSequence senderName = group.getSenderName();
@@ -847,22 +807,22 @@ public class ConversationLayout extends FrameLayout
continue;
}
if (!uniqueNames.containsKey(senderName)) {
- // Only use visible characters to get uniqueNames
- String pureSenderName = IGNORABLE_CHAR_PATTERN
- .matcher(senderName).replaceAll("" /* replacement */);
- char c = pureSenderName.charAt(0);
- if (uniqueCharacters.containsKey(c)) {
+ String charPrefix = mPeopleHelper.findNamePrefix(senderName, null);
+ if (charPrefix == null) {
+ continue;
+ }
+ if (uniqueCharacters.containsKey(charPrefix)) {
// this character was already used, lets make it more unique. We first need to
// resolve the existing character if it exists
- CharSequence existingName = uniqueCharacters.get(c);
+ CharSequence existingName = uniqueCharacters.get(charPrefix);
if (existingName != null) {
- uniqueNames.put(existingName, findNameSplit((String) existingName));
- uniqueCharacters.put(c, null);
+ uniqueNames.put(existingName, mPeopleHelper.findNameSplit(existingName));
+ uniqueCharacters.put(charPrefix, null);
}
- uniqueNames.put(senderName, findNameSplit((String) senderName));
+ uniqueNames.put(senderName, mPeopleHelper.findNameSplit(senderName));
} else {
- uniqueNames.put(senderName, Character.toString(c));
- uniqueCharacters.put(c, pureSenderName);
+ uniqueNames.put(senderName, charPrefix);
+ uniqueCharacters.put(charPrefix, senderName);
}
}
}
@@ -898,8 +858,8 @@ public class ConversationLayout extends FrameLayout
} else {
Icon cachedIcon = cachedAvatars.get(senderName);
if (cachedIcon == null) {
- cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName),
- mLayoutColor);
+ cachedIcon = mPeopleHelper.createAvatarSymbol(senderName,
+ uniqueNames.get(senderName), mLayoutColor);
cachedAvatars.put(senderName, cachedIcon);
}
group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName),
@@ -908,49 +868,6 @@ public class ConversationLayout extends FrameLayout
}
}
- private Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
- if (symbol.isEmpty() || TextUtils.isDigitsOnly(symbol) ||
- SPECIAL_CHAR_PATTERN.matcher(symbol).find()) {
- Icon avatarIcon = Icon.createWithResource(getContext(),
- R.drawable.messaging_user);
- avatarIcon.setTint(findColor(senderName, layoutColor));
- return avatarIcon;
- } else {
- Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- float radius = mAvatarSize / 2.0f;
- int color = findColor(senderName, layoutColor);
- mPaint.setColor(color);
- canvas.drawCircle(radius, radius, radius, mPaint);
- boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
- mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
- mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.5f : mAvatarSize * 0.3f);
- int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
- canvas.drawText(symbol, radius, yPos, mTextPaint);
- return Icon.createWithBitmap(bitmap);
- }
- }
-
- private int findColor(CharSequence senderName, int layoutColor) {
- double luminance = ContrastColorUtil.calculateLuminance(layoutColor);
- float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
-
- // we need to offset the range if the luminance is too close to the borders
- shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
- shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
- return ContrastColorUtil.getShiftedColor(layoutColor,
- (int) (shift * COLOR_SHIFT_AMOUNT));
- }
-
- private String findNameSplit(String existingName) {
- String[] split = existingName.split(" ");
- if (split.length > 1) {
- return Character.toString(split[0].charAt(0))
- + Character.toString(split[1].charAt(0));
- }
- return existingName.substring(0, 1);
- }
-
@RemotableViewMethod
public void setLayoutColor(int color) {
mLayoutColor = color;
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index 5213746e5a12..058a9218def4 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -16,17 +16,24 @@
package com.android.internal.widget;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableWrapper;
import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
+import android.view.ViewGroup;
import android.widget.Button;
+import android.widget.LinearLayout;
import android.widget.RemoteViews;
+import com.android.internal.R;
+
/**
* A button implementation for the emphasized notification style.
*
@@ -37,6 +44,7 @@ public class EmphasizedNotificationButton extends Button {
private final RippleDrawable mRipple;
private final int mStrokeWidth;
private final int mStrokeColor;
+ private boolean mPriority;
public EmphasizedNotificationButton(Context context) {
this(context, null);
@@ -80,4 +88,57 @@ public class EmphasizedNotificationButton extends Button {
inner.setStroke(hasStroke ? mStrokeWidth : 0, mStrokeColor);
invalidate();
}
+
+ /**
+ * Sets an image icon which will have its size constrained and will be set to the same color as
+ * the text. Must be called after {@link #setTextColor(int)} for the latter to work.
+ */
+ @RemotableViewMethod(asyncImpl = "setImageIconAsync")
+ public void setImageIcon(@Nullable Icon icon) {
+ final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+ setImageDrawable(drawable);
+ }
+
+ /**
+ * @hide
+ */
+ @RemotableViewMethod
+ public Runnable setImageIconAsync(@Nullable Icon icon) {
+ final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+ return () -> setImageDrawable(drawable);
+ }
+
+ private void setImageDrawable(Drawable drawable) {
+ if (drawable != null) {
+ drawable.mutate();
+ drawable.setTintList(getTextColors());
+ drawable.setTintBlendMode(BlendMode.SRC_IN);
+ int iconSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.notification_actions_icon_drawable_size);
+ drawable.setBounds(0, 0, iconSize, iconSize);
+ }
+ setCompoundDrawablesRelative(drawable, null, null, null);
+ }
+
+ /**
+ * Changes the LayoutParams.width to WRAP_CONTENT, with the argument representing if this view
+ * is a priority over its peers (which affects weight).
+ */
+ @RemotableViewMethod
+ public void setWrapModePriority(boolean priority) {
+ mPriority = priority;
+ ViewGroup.LayoutParams layoutParams = getLayoutParams();
+ layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ if (layoutParams instanceof LinearLayout.LayoutParams) {
+ ((LinearLayout.LayoutParams) layoutParams).weight = 0;
+ }
+ setLayoutParams(layoutParams);
+ }
+
+ /**
+ * Sizing this button is a priority compared with its peers.
+ */
+ public boolean isPriority() {
+ return mPriority;
+ }
}
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index c7ea781b2793..8e6497b204c7 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,6 +16,7 @@
package com.android.internal.widget;
+import android.annotation.DimenRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.RippleDrawable;
@@ -41,13 +42,16 @@ public class NotificationActionListLayout extends LinearLayout {
private final int mGravity;
private int mTotalWidth = 0;
+ private int mExtraStartPadding = 0;
private ArrayList<Pair<Integer, TextView>> mMeasureOrderTextViews = new ArrayList<>();
private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
private boolean mEmphasizedMode;
+ private boolean mPrioritizedWrapMode;
private int mDefaultPaddingBottom;
private int mDefaultPaddingTop;
private int mEmphasizedHeight;
private int mRegularHeight;
+ @DimenRes private int mCollapsibleIndentDimen;
public NotificationActionListLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -68,7 +72,7 @@ public class NotificationActionListLayout extends LinearLayout {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mEmphasizedMode) {
+ if (mEmphasizedMode && !mPrioritizedWrapMode) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
@@ -151,7 +155,15 @@ public class NotificationActionListLayout extends LinearLayout {
measuredChildren++;
}
- mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft;
+ int collapsibleIndent = mCollapsibleIndentDimen == 0 ? 0
+ : getResources().getDimensionPixelOffset(mCollapsibleIndentDimen);
+ if (innerWidth - usedWidth > collapsibleIndent) {
+ mExtraStartPadding = collapsibleIndent;
+ } else {
+ mExtraStartPadding = 0;
+ }
+
+ mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
@@ -163,7 +175,11 @@ public class NotificationActionListLayout extends LinearLayout {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View c = getChildAt(i);
- if (c instanceof TextView && ((TextView) c).getText().length() > 0) {
+ if (c instanceof EmphasizedNotificationButton
+ && ((EmphasizedNotificationButton) c).isPriority()) {
+ // add with 0 length to ensure that this view is measured before others.
+ mMeasureOrderTextViews.add(Pair.create(0, (TextView) c));
+ } else if (c instanceof TextView && ((TextView) c).getText().length() > 0) {
mMeasureOrderTextViews.add(Pair.create(((TextView) c).getText().length(),
(TextView)c));
} else {
@@ -197,7 +213,7 @@ public class NotificationActionListLayout extends LinearLayout {
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (mEmphasizedMode) {
+ if (mEmphasizedMode && !mPrioritizedWrapMode) {
super.onLayout(changed, left, top, right, bottom);
return;
}
@@ -214,6 +230,9 @@ public class NotificationActionListLayout extends LinearLayout {
int absoluteGravity = Gravity.getAbsoluteGravity(Gravity.START, getLayoutDirection());
if (absoluteGravity == Gravity.RIGHT) {
childLeft += right - left - mTotalWidth;
+ } else {
+ // Put the extra start padding (if any) on the left when LTR
+ childLeft += mExtraStartPadding;
}
}
@@ -274,6 +293,26 @@ public class NotificationActionListLayout extends LinearLayout {
}
/**
+ * When used with emphasizedMode, changes the button sizing behavior to prioritize certain
+ * buttons (which are system generated) to not scrunch, and leave the remaining space for
+ * custom actions.
+ */
+ @RemotableViewMethod
+ public void setPrioritizedWrapMode(boolean prioritizedWrapMode) {
+ mPrioritizedWrapMode = prioritizedWrapMode;
+ }
+
+ /**
+ * When buttons are in wrap mode, this is a padding that will be applied at the start of the
+ * layout of the actions, but only when those actions would fit with the entire padding
+ * visible. Otherwise, this padding will be omitted entirely.
+ */
+ @RemotableViewMethod
+ public void setCollapsibleIndentDimen(@DimenRes int collapsibleIndentDimen) {
+ mCollapsibleIndentDimen = collapsibleIndentDimen;
+ }
+
+ /**
* Set whether the list is in a mode where some actions are emphasized. This will trigger an
* equal measuring where all actions are full height and change a few parameters like
* the padding.
diff --git a/core/java/com/android/internal/widget/PeopleHelper.java b/core/java/com/android/internal/widget/PeopleHelper.java
new file mode 100644
index 000000000000..77f4c8f6bede
--- /dev/null
+++ b/core/java/com/android/internal/widget/PeopleHelper.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2021 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.internal.widget;
+
+import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN;
+import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.android.internal.R;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.util.ContrastColorUtil;
+
+import java.util.regex.Pattern;
+
+/**
+ * This class provides some methods used by both the {@link ConversationLayout} and
+ * {@link CallLayout} which both use the visual design originally created for conversations in R.
+ */
+public class PeopleHelper {
+
+ private static final float COLOR_SHIFT_AMOUNT = 60;
+ /**
+ * Pattern for filter some ignorable characters.
+ * p{Z} for any kind of whitespace or invisible separator.
+ * p{C} for any kind of punctuation character.
+ */
+ private static final Pattern IGNORABLE_CHAR_PATTERN = Pattern.compile("[\\p{C}\\p{Z}]");
+ private static final Pattern SPECIAL_CHAR_PATTERN =
+ Pattern.compile("[!@#$%&*()_+=|<>?{}\\[\\]~-]");
+
+ private Context mContext;
+ private int mAvatarSize;
+ private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private Paint mTextPaint = new Paint();
+
+ /**
+ * Call this when the view is inflated to provide a context and initialize the helper
+ */
+ public void init(Context context) {
+ mContext = context;
+ mAvatarSize = context.getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+ mTextPaint.setAntiAlias(true);
+ }
+
+ /**
+ * A utility for animating CachingIconViews away when hidden.
+ */
+ public void animateViewForceHidden(CachingIconView view, boolean forceHidden) {
+ boolean nowForceHidden = view.willBeForceHidden() || view.isForceHidden();
+ if (forceHidden == nowForceHidden) {
+ // We are either already forceHidden or will be
+ return;
+ }
+ view.animate().cancel();
+ view.setWillBeForceHidden(forceHidden);
+ view.animate()
+ .scaleX(forceHidden ? 0.5f : 1.0f)
+ .scaleY(forceHidden ? 0.5f : 1.0f)
+ .alpha(forceHidden ? 0.0f : 1.0f)
+ .setInterpolator(forceHidden ? ALPHA_OUT : ALPHA_IN)
+ .setDuration(160);
+ if (view.getVisibility() != View.VISIBLE) {
+ view.setForceHidden(forceHidden);
+ } else {
+ view.animate().withEndAction(() -> view.setForceHidden(forceHidden));
+ }
+ view.animate().start();
+ }
+
+ /**
+ * This creates an avatar symbol for the given person or group
+ *
+ * @param name the name of the person or group
+ * @param symbol a pre-chosen symbol for the person or group. See
+ * {@link #findNamePrefix(CharSequence, String)} or
+ * {@link #findNameSplit(CharSequence)}
+ * @param layoutColor the background color of the layout
+ */
+ @NonNull
+ public Icon createAvatarSymbol(@NonNull CharSequence name, @NonNull String symbol,
+ @ColorInt int layoutColor) {
+ if (symbol.isEmpty() || TextUtils.isDigitsOnly(symbol)
+ || SPECIAL_CHAR_PATTERN.matcher(symbol).find()) {
+ Icon avatarIcon = Icon.createWithResource(mContext, R.drawable.messaging_user);
+ avatarIcon.setTint(findColor(name, layoutColor));
+ return avatarIcon;
+ } else {
+ Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ float radius = mAvatarSize / 2.0f;
+ int color = findColor(name, layoutColor);
+ mPaint.setColor(color);
+ canvas.drawCircle(radius, radius, radius, mPaint);
+ boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
+ mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
+ mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.5f : mAvatarSize * 0.3f);
+ int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
+ canvas.drawText(symbol, radius, yPos, mTextPaint);
+ return Icon.createWithBitmap(bitmap);
+ }
+ }
+
+ private int findColor(@NonNull CharSequence senderName, int layoutColor) {
+ double luminance = ContrastColorUtil.calculateLuminance(layoutColor);
+ float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
+
+ // we need to offset the range if the luminance is too close to the borders
+ shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
+ shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
+ return ContrastColorUtil.getShiftedColor(layoutColor,
+ (int) (shift * COLOR_SHIFT_AMOUNT));
+ }
+
+ /**
+ * Get the name with whitespace and punctuation characters removed
+ */
+ private String getPureName(@NonNull CharSequence name) {
+ return IGNORABLE_CHAR_PATTERN.matcher(name).replaceAll("" /* replacement */);
+ }
+
+ /**
+ * Gets a single character string prefix name for the person or group
+ *
+ * @param name the name of the person or group
+ * @param fallback the string to return if the name has no usable characters
+ */
+ public String findNamePrefix(@NonNull CharSequence name, String fallback) {
+ String pureName = getPureName(name);
+ if (pureName.isEmpty()) {
+ return fallback;
+ }
+ try {
+ return new String(Character.toChars(pureName.codePointAt(0)));
+ } catch (RuntimeException ignore) {
+ return fallback;
+ }
+ }
+
+ /**
+ * Find a 1 or 2 character prefix name for the person or group
+ */
+ public String findNameSplit(@NonNull CharSequence name) {
+ String nameString = name instanceof String ? ((String) name) : name.toString();
+ String[] split = nameString.trim().split("[ ]+");
+ if (split.length > 1) {
+ String first = findNamePrefix(split[0], null);
+ String second = findNamePrefix(split[1], null);
+ if (first != null && second != null) {
+ return first + second;
+ }
+ }
+ return findNamePrefix(name, "");
+ }
+}
diff --git a/core/res/res/drawable/btn_notification_emphasized.xml b/core/res/res/drawable/btn_notification_emphasized.xml
index 1a574fe39e6e..ad680549aaa1 100644
--- a/core/res/res/drawable/btn_notification_emphasized.xml
+++ b/core/res/res/drawable/btn_notification_emphasized.xml
@@ -23,7 +23,7 @@
<ripple android:color="?attr/colorControlHighlight">
<item>
<shape android:shape="rectangle">
- <corners android:radius="?attr/buttonCornerRadius" />
+ <corners android:radius="@dimen/notification_action_button_radius" />
<padding android:left="@dimen/button_padding_horizontal_material"
android:top="@dimen/button_padding_vertical_material"
android:right="@dimen/button_padding_horizontal_material"
diff --git a/core/res/res/drawable/ic_call_answer.xml b/core/res/res/drawable/ic_call_answer.xml
new file mode 100644
index 000000000000..77c0ad1a585f
--- /dev/null
+++ b/core/res/res/drawable/ic_call_answer.xml
@@ -0,0 +1,34 @@
+<!--
+ Copyright (C) 2021 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20"
+ android:tint="?android:attr/colorControlNormal"
+ android:autoMirrored="true">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.0168,3.3333L6.5751,5.575L4.6668,7.4916C3.8751,5.7166 3.6001,4.1916
+ 3.4834,3.3333H6.0168ZM14.4251,13.375L16.6668,13.9416V16.5166C15.8084,16.4 14.2668,16.125
+ 12.4834,15.325L14.4251,13.375ZM6.3418,1.6666H2.5668C2.0918,1.6666 1.7001,2.0666
+ 1.7334,2.5416C2.4834,12.875 11.7668,18.275 17.5168,18.275C17.9668,18.275 18.3334,17.9
+ 18.3334,17.4416V13.6166C18.3334,13.0416 17.9418,12.5416
+ 17.3834,12.4083L14.5918,11.7083C14.2251,11.6166 13.7584,11.6833
+ 13.4084,12.0333L10.9251,14.5166C8.6751,13.1833 6.7918,11.3
+ 5.4668,9.0416L7.9168,6.5916C8.2251,6.2833 8.3501,5.8333
+ 8.2418,5.4083L7.5584,2.6166C7.4168,2.0583 6.9168,1.6666 6.3418,1.6666Z"/>
+</vector> \ No newline at end of file
diff --git a/core/res/res/drawable/ic_call_decline.xml b/core/res/res/drawable/ic_call_decline.xml
new file mode 100644
index 000000000000..a5ee8f4e6e72
--- /dev/null
+++ b/core/res/res/drawable/ic_call_decline.xml
@@ -0,0 +1,36 @@
+<!--
+ Copyright (C) 2021 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20"
+ android:tint="?android:attr/colorControlNormal"
+ android:autoMirrored="true">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5.2834,9.3083V10.7833L4.1084,11.4916L3.1334,10.5166C3.8084,10.0416
+ 4.5251,9.6333 5.2834,9.3083ZM14.75,9.325C15.4917,9.65 16.2084,10.05
+ 16.875,10.525L15.925,11.475L14.75,10.7666V9.325ZM9.975,6.6666C6.8584,6.6666 3.725,7.7333
+ 1.1751,9.9416C0.8084,10.2583 0.9667,10.7166 1.1501,10.9L3.2917,13.0416C3.4917,13.2333
+ 3.7417,13.3333 4.0001,13.3333C4.175,13.3333 4.35,13.2833
+ 4.5084,13.1916L6.4667,12.0166C6.725,11.8583 6.95,11.5583 6.95,11.1666V8.3833C7.95,8.125
+ 8.975,8 10.0084,8C11.0417,8 12.075,8.1333 13.0834,8.3916V11.1416C13.0834,11.4916
+ 13.2667,11.8166 13.5667,11.9916L15.525,13.1666C15.6834,13.2583 15.8584,13.3083
+ 16.0334,13.3083C16.2917,13.3083 16.5417,13.2083
+ 16.7334,13.0166L18.85,10.9C19.1167,10.6333 19.1167,10.1916 18.825,9.9416C16.3334,7.7833
+ 13.1667,6.6666 9.975,6.6666Z"/>
+</vector>
diff --git a/core/res/res/layout/notification_material_action_emphasized.xml b/core/res/res/layout/notification_material_action_emphasized.xml
index a6b7b380eaa4..cd1f1ab88c96 100644
--- a/core/res/res/layout/notification_material_action_emphasized.xml
+++ b/core/res/res/layout/notification_material_action_emphasized.xml
@@ -22,6 +22,7 @@
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_weight="1"
+ android:drawablePadding="6dp"
android:gravity="center"
android:textColor="@color/notification_default_color"
android:singleLine="true"
diff --git a/core/res/res/layout/notification_template_conversation_header.xml b/core/res/res/layout/notification_template_conversation_header.xml
new file mode 100644
index 000000000000..b018676e68f0
--- /dev/null
+++ b/core/res/res/layout/notification_template_conversation_header.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/conversation_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingTop="16dp"
+ >
+
+ <TextView
+ android:id="@+id/conversation_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="16sp"
+ android:singleLine="true"
+ android:layout_weight="1"
+ />
+
+ <TextView
+ android:id="@+id/app_name_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:layout_gravity="center"
+ android:paddingTop="1sp"
+ android:singleLine="true"
+ android:visibility="gone"
+ />
+
+ <!-- App Name -->
+ <com.android.internal.widget.ObservableTextView
+ android:id="@+id/app_name_text"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+ android:paddingTop="1sp"
+ android:singleLine="true"
+ android:visibility="gone"
+ />
+
+ <TextView
+ android:id="@+id/time_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:layout_gravity="center"
+ android:paddingTop="1sp"
+ android:singleLine="true"
+ android:visibility="gone"
+ />
+
+ <DateTimeView
+ android:id="@+id/time"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+ android:paddingTop="1sp"
+ android:showRelative="true"
+ android:singleLine="true"
+ android:visibility="gone"
+ />
+
+ <ViewStub
+ android:id="@+id/chronometer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+ android:layout="@layout/notification_template_part_chronometer"
+ android:visibility="gone"
+ />
+
+ <ImageView
+ android:id="@+id/verification_icon"
+ android:layout_width="@dimen/notification_badge_size"
+ android:layout_height="@dimen/notification_badge_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:contentDescription="@string/notification_alerted_content_description"
+ android:paddingTop="2dp"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_notifications_alerted"
+ android:visibility="gone"
+ />
+
+ <TextView
+ android:id="@+id/verification_text"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+ android:paddingTop="1sp"
+ android:showRelative="true"
+ android:singleLine="true"
+ android:visibility="gone"
+ />
+
+ <ImageButton
+ android:id="@+id/feedback"
+ android:layout_width="@dimen/notification_feedback_size"
+ android:layout_height="@dimen/notification_feedback_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:contentDescription="@string/notification_feedback_indicator"
+ android:paddingTop="2dp"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_feedback_indicator"
+ android:visibility="gone"
+ />
+
+ <ImageView
+ android:id="@+id/profile_badge"
+ android:layout_width="@dimen/notification_badge_size"
+ android:layout_height="@dimen/notification_badge_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:paddingTop="2dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_work_profile_content_description"
+ />
+
+ <ImageView
+ android:id="@+id/alerted_icon"
+ android:layout_width="@dimen/notification_alerted_size"
+ android:layout_height="@dimen/notification_alerted_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:contentDescription="@string/notification_alerted_content_description"
+ android:paddingTop="2dp"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_notifications_alerted"
+ android:visibility="gone"
+ />
+</LinearLayout>
diff --git a/core/res/res/layout/notification_template_conversation_icon_container.xml b/core/res/res/layout/notification_template_conversation_icon_container.xml
new file mode 100644
index 000000000000..e9ec7ce77deb
--- /dev/null
+++ b/core/res/res/layout/notification_template_conversation_icon_container.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/conversation_icon_container"
+ android:layout_width="@dimen/conversation_content_start"
+ android:layout_height="wrap_content"
+ android:gravity="start|top"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:importantForAccessibility="no"
+ >
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layout_gravity="top|center_horizontal"
+ >
+
+ <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/conversation_icon"
+ android:layout_width="@dimen/conversation_avatar_size"
+ android:layout_height="@dimen/conversation_avatar_size"
+ android:scaleType="centerCrop"
+ android:importantForAccessibility="no"
+ />
+
+ <ViewStub
+ android:layout="@layout/conversation_face_pile_layout"
+ android:layout_width="@dimen/conversation_avatar_size"
+ android:layout_height="@dimen/conversation_avatar_size"
+ android:id="@+id/conversation_face_pile"
+ />
+
+ <FrameLayout
+ android:id="@+id/conversation_icon_badge"
+ android:layout_width="@dimen/conversation_icon_size_badged"
+ android:layout_height="@dimen/conversation_icon_size_badged"
+ android:layout_marginLeft="@dimen/conversation_badge_side_margin"
+ android:layout_marginTop="@dimen/conversation_badge_side_margin"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ >
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/conversation_icon_badge_bg"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:src="@drawable/conversation_badge_background"
+ android:forceHasOverlappingRendering="false"
+ android:scaleType="center"
+ />
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/icon"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="4dp"
+ android:layout_gravity="center"
+ android:forceHasOverlappingRendering="false"
+ />
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/conversation_icon_badge_ring"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:src="@drawable/conversation_badge_ring"
+ android:visibility="gone"
+ android:forceHasOverlappingRendering="false"
+ android:clipToPadding="false"
+ android:scaleType="center"
+ />
+ </FrameLayout>
+ </FrameLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml
new file mode 100644
index 000000000000..471d874c59f5
--- /dev/null
+++ b/core/res/res/layout/notification_template_material_call.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.internal.widget.CallLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ android:tag="call"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
+ <include layout="@layout/notification_template_conversation_icon_container" />
+
+ <LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/notification_action_list_height"
+ android:orientation="vertical"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="top"
+ android:orientation="horizontal"
+ >
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginStart="@dimen/conversation_content_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:orientation="vertical"
+ android:minHeight="68dp"
+ >
+
+ <include
+ layout="@layout/notification_template_conversation_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+ <include layout="@layout/notification_template_text" />
+
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_progress_bar_height"
+ android:layout_marginTop="@dimen/notification_progress_margin_top"
+ layout="@layout/notification_template_progress"
+ />
+ </LinearLayout>
+
+ <!-- TODO(b/179178086): remove padding from main column when this is visible -->
+ <com.android.internal.widget.NotificationExpandButton
+ android:id="@+id/expand_button"
+ android:layout_width="@dimen/notification_header_expand_icon_size"
+ android:layout_height="@dimen/notification_header_expand_icon_size"
+ android:layout_gravity="top|end"
+ android:contentDescription="@string/expand_button_content_description_collapsed"
+ android:paddingTop="@dimen/notification_expand_button_padding_top"
+ android:scaleType="center"
+ android:visibility="gone"
+ />
+
+ </LinearLayout>
+
+ <include layout="@layout/notification_material_action_list" />
+
+ </LinearLayout>
+
+</com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index f9364d565f3b..f3aa54066c92 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -24,82 +24,7 @@
android:theme="@style/Theme.DeviceDefault.Notification"
>
- <FrameLayout
- android:id="@+id/conversation_icon_container"
- android:layout_width="@dimen/conversation_content_start"
- android:layout_height="wrap_content"
- android:gravity="start|top"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:paddingTop="12dp"
- android:paddingBottom="12dp"
- android:importantForAccessibility="no"
- >
-
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:layout_gravity="top|center_horizontal"
- >
-
- <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
- <com.android.internal.widget.CachingIconView
- android:id="@+id/conversation_icon"
- android:layout_width="@dimen/conversation_avatar_size"
- android:layout_height="@dimen/conversation_avatar_size"
- android:scaleType="centerCrop"
- android:importantForAccessibility="no"
- />
-
- <ViewStub
- android:layout="@layout/conversation_face_pile_layout"
- android:layout_width="@dimen/conversation_avatar_size"
- android:layout_height="@dimen/conversation_avatar_size"
- android:id="@+id/conversation_face_pile"
- />
-
- <FrameLayout
- android:id="@+id/conversation_icon_badge"
- android:layout_width="@dimen/conversation_icon_size_badged"
- android:layout_height="@dimen/conversation_icon_size_badged"
- android:layout_marginLeft="@dimen/conversation_badge_side_margin"
- android:layout_marginTop="@dimen/conversation_badge_side_margin"
- android:clipChildren="false"
- android:clipToPadding="false"
- >
- <com.android.internal.widget.CachingIconView
- android:id="@+id/conversation_icon_badge_bg"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:src="@drawable/conversation_badge_background"
- android:forceHasOverlappingRendering="false"
- android:scaleType="center"
- />
- <com.android.internal.widget.CachingIconView
- android:id="@+id/icon"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="4dp"
- android:layout_gravity="center"
- android:forceHasOverlappingRendering="false"
- />
- <com.android.internal.widget.CachingIconView
- android:id="@+id/conversation_icon_badge_ring"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:src="@drawable/conversation_badge_ring"
- android:visibility="gone"
- android:forceHasOverlappingRendering="false"
- android:clipToPadding="false"
- android:scaleType="center"
- />
- </FrameLayout>
- </FrameLayout>
- </FrameLayout>
+ <include layout="@layout/notification_template_conversation_icon_container" />
<!-- Wraps entire "expandable" notification -->
<com.android.internal.widget.RemeasuringLinearLayout
@@ -132,161 +57,14 @@
<!-- Use layout_marginStart instead of paddingStart to work around strange
measurement behavior on lower display densities. -->
- <LinearLayout
- android:id="@+id/conversation_header"
+ <include
+ layout="@layout/notification_template_conversation_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:paddingTop="16dp"
android:layout_marginBottom="2dp"
android:layout_marginStart="@dimen/conversation_content_start"
- >
- <TextView
- android:id="@+id/conversation_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="16sp"
- android:singleLine="true"
- android:layout_weight="1"
- />
-
- <TextView
- android:id="@+id/app_name_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:layout_gravity="center"
- android:paddingTop="1sp"
- android:singleLine="true"
- android:visibility="gone"
/>
- <!-- App Name -->
- <com.android.internal.widget.ObservableTextView
- android:id="@+id/app_name_text"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
- android:paddingTop="1sp"
- android:singleLine="true"
- android:visibility="gone"
- />
-
- <TextView
- android:id="@+id/time_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:layout_gravity="center"
- android:paddingTop="1sp"
- android:singleLine="true"
- android:visibility="gone"
- />
-
- <DateTimeView
- android:id="@+id/time"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:paddingTop="1sp"
- android:showRelative="true"
- android:singleLine="true"
- android:visibility="gone"
- />
-
- <ImageButton
- android:id="@+id/feedback"
- android:layout_width="@dimen/notification_feedback_size"
- android:layout_height="@dimen/notification_feedback_size"
- android:layout_gravity="center"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:background="?android:selectableItemBackgroundBorderless"
- android:contentDescription="@string/notification_feedback_indicator"
- android:paddingTop="2dp"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_feedback_indicator"
- android:visibility="gone"
- />
-
- <ImageView
- android:id="@+id/profile_badge"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_gravity="center"
- android:layout_marginStart="4dp"
- android:paddingTop="2dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
-
- <ImageView
- android:id="@+id/alerted_icon"
- android:layout_width="@dimen/notification_alerted_size"
- android:layout_height="@dimen/notification_alerted_size"
- android:layout_gravity="center"
- android:layout_marginStart="4dp"
- android:contentDescription="@string/notification_alerted_content_description"
- android:paddingTop="2dp"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_notifications_alerted"
- android:visibility="gone"
- />
-
- <LinearLayout
- android:id="@+id/app_ops"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:paddingTop="3dp"
- android:layout_marginStart="2dp"
- android:background="?android:selectableItemBackgroundBorderless"
- android:orientation="horizontal" >
- <ImageView
- android:layout_marginStart="4dp"
- android:id="@+id/camera"
- android:layout_width="?attr/notificationHeaderIconSize"
- android:layout_height="?attr/notificationHeaderIconSize"
- android:src="@drawable/ic_camera"
- android:visibility="gone"
- android:focusable="false"
- android:contentDescription="@string/notification_appops_camera_active"
- />
- <ImageView
- android:id="@+id/mic"
- android:layout_width="?attr/notificationHeaderIconSize"
- android:layout_height="?attr/notificationHeaderIconSize"
- android:src="@drawable/ic_mic"
- android:layout_marginStart="4dp"
- android:visibility="gone"
- android:focusable="false"
- android:contentDescription="@string/notification_appops_microphone_active"
- />
- <ImageView
- android:id="@+id/overlay"
- android:layout_width="?attr/notificationHeaderIconSize"
- android:layout_height="?attr/notificationHeaderIconSize"
- android:src="@drawable/ic_alert_window_layer"
- android:layout_marginStart="4dp"
- android:visibility="gone"
- android:focusable="false"
- android:contentDescription="@string/notification_appops_overlay_active"
- />
- </LinearLayout>
- </LinearLayout>
-
<!-- Messages -->
<com.android.internal.widget.MessagingLinearLayout
android:id="@+id/notification_messaging"
diff --git a/core/res/res/layout/notification_top_line_views.xml b/core/res/res/layout/notification_top_line_views.xml
index 7cda03f8fc4f..7656dd50b2d4 100644
--- a/core/res/res/layout/notification_top_line_views.xml
+++ b/core/res/res/layout/notification_top_line_views.xml
@@ -26,7 +26,7 @@
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:singleLine="true"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:visibility="?attr/notificationHeaderAppNameVisibility"
/>
@@ -34,7 +34,7 @@
android:id="@+id/header_text_secondary_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:text="@string/notification_header_divider_symbol"
@@ -45,7 +45,7 @@
android:id="@+id/header_text_secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:visibility="gone"
@@ -56,7 +56,7 @@
android:id="@+id/header_text_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:text="@string/notification_header_divider_symbol"
@@ -67,7 +67,7 @@
android:id="@+id/header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:visibility="gone"
@@ -78,7 +78,7 @@
android:id="@+id/time_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:text="@string/notification_header_divider_symbol"
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 20a5d379cbcc..5546621b6ee8 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -153,6 +153,11 @@
<color name="notification_action_list_background_color">@null</color>
+ <!-- The color of the Decline and Hang Up actions on a CallStyle notification -->
+ <color name="call_notification_decline_color">#d93025</color>
+ <!-- The color of the Answer action on a CallStyle notification -->
+ <color name="call_notification_answer_color">#1e8e3e</color>
+
<!-- Keyguard colors -->
<color name="keyguard_avatar_frame_color">#ffffffff</color>
<color name="keyguard_avatar_frame_shadow_color">#80000000</color>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index cb16af5a16b0..3b6e41ffa92b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -236,9 +236,20 @@
value is calculated in ConversationLayout#updateActionListPadding() -->
<dimen name="notification_actions_padding_start">36dp</dimen>
+ <!-- The start padding to optionally use (e.g. if there's extra space) for CallStyle
+ notification actions.
+ this = conversation_content_start (80dp) - button inset (4dp) - action padding (12dp) -->
+ <dimen name="call_notification_collapsible_indent">64dp</dimen>
+
<!-- The size of icons for visual actions in the notification_material_action_list -->
<dimen name="notification_actions_icon_size">48dp</dimen>
+ <!-- The size of icons for visual actions in the notification_material_action_list -->
+ <dimen name="notification_actions_icon_drawable_size">20dp</dimen>
+
+ <!-- The corner radius if the emphasized action buttons in a notification -->
+ <dimen name="notification_action_button_radius">8dp</dimen>
+
<!-- Size of the stroke with for the emphasized notification button style -->
<dimen name="emphasized_button_stroke_width">1dp</dimen>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index a4c7293e48b6..ab4e0f3b608e 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -102,6 +102,10 @@
<item type="id" name="selection_end_handle" />
<item type="id" name="insertion_handle" />
<item type="id" name="floating_toolbar_menu_item_image_button" />
+ <item type="id" name="camera" />
+ <item type="id" name="mic" />
+ <item type="id" name="overlay" />
+ <item type="id" name="app_ops" />
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SHOW_ON_SCREEN}. -->
<item type="id" name="accessibilityActionShowOnScreen" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9b5f67091a2a..af5e406979ad 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5039,6 +5039,18 @@
<!-- Tempalate for Notification.MessagingStyle to join a conversation name with the name of the sender of a message, to make a notification title [CHAR LIMIT=NONE] -->
<string name="notification_messaging_title_template"><xliff:g id="conversation_title" example="Tasty Treat Team">%1$s</xliff:g>: <xliff:g id="sender_name" example="Adrian Baker">%2$s</xliff:g></string>
+ <!-- Action text to be displayed for the "answer" action of an incoming call [CHAR LIMIT=13] -->
+ <string name="call_notification_answer_action">Answer</string>
+ <!-- Action text to be displayed for the "decline" action of an incoming call [CHAR LIMIT=13] -->
+ <string name="call_notification_decline_action">Decline</string>
+ <!-- Action text to be displayed for the "hang up" action of an ongoing call [CHAR LIMIT=13] -->
+ <string name="call_notification_hang_up_action">Hang Up</string>
+ <!-- Default notification text to be displayed in incoming call notifications [CHAR LIMIT=40] -->
+ <string name="call_notification_incoming_text">Incoming call</string>
+ <!-- Default notification text to be displayed in ongoing call notifications [CHAR LIMIT=40] -->
+ <string name="call_notification_ongoing_text">Ongoing call</string>
+ <!-- Default notification text to be displayed in screening call notifications [CHAR LIMIT=40] -->
+ <string name="call_notification_screening_text">Screening an incoming call</string>
<!-- Label describing the number of selected items [CHAR LIMIT=48] -->
<plurals name="selected_count">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 41375a7dd419..4109d4c9f6f9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3089,8 +3089,26 @@
<!-- TV Remote Service package -->
<java-symbol type="string" name="config_tvRemoteServicePackage" />
+
+ <!-- Notifications: MessagingStyle -->
<java-symbol type="string" name="notification_messaging_title_template" />
+ <!-- Notifications: CallStyle -->
+ <java-symbol type="layout" name="notification_template_material_call" />
+ <java-symbol type="string" name="call_notification_answer_action" />
+ <java-symbol type="string" name="call_notification_decline_action" />
+ <java-symbol type="string" name="call_notification_hang_up_action" />
+ <java-symbol type="string" name="call_notification_incoming_text" />
+ <java-symbol type="string" name="call_notification_ongoing_text" />
+ <java-symbol type="string" name="call_notification_screening_text" />
+ <java-symbol type="color" name="call_notification_decline_color"/>
+ <java-symbol type="color" name="call_notification_answer_color"/>
+ <java-symbol type="dimen" name="call_notification_collapsible_indent"/>
+ <java-symbol type="drawable" name="ic_call_answer" />
+ <java-symbol type="drawable" name="ic_call_decline" />
+ <java-symbol type="id" name="verification_icon" />
+ <java-symbol type="id" name="verification_text" />
+
<!-- Notification handler / dashboard package -->
<java-symbol type="string" name="config_notificationHandlerPackage" />
@@ -3416,6 +3434,7 @@
<java-symbol type="dimen" name="notification_media_image_max_width"/>
<java-symbol type="dimen" name="notification_media_image_max_height"/>
<java-symbol type="dimen" name="notification_right_icon_size"/>
+ <java-symbol type="dimen" name="notification_actions_icon_drawable_size"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a4ff18fedbf3..12b8ccfd09d6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -163,10 +163,14 @@
<!-- heads up elevation that is added if the view is pinned -->
<dimen name="heads_up_pinned_elevation">16dp</dimen>
- <!-- Height of a messaging notifications with actions at least. Not that this is an upper bound
+ <!-- Height of a messaging notifications with actions at least. Note that this is an upper bound
and the notification won't use this much, but is measured with wrap_content -->
<dimen name="notification_messaging_actions_min_height">196dp</dimen>
+ <!-- Height of a call notification. Note that this is an upper bound
+ and the notification won't use this much, but is measured with wrap_content -->
+ <dimen name="call_notification_full_height">172dp</dimen>
+
<!-- a threshold in dp per second that is considered fast scrolling -->
<dimen name="scroll_fast_threshold">1500dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a03fc136da61..845d321416ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -71,6 +71,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.widget.CachingIconView;
+import com.android.internal.widget.CallLayout;
import com.android.internal.widget.MessagingLayout;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
@@ -165,6 +166,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private int mMaxSmallHeightLarge;
private int mMaxSmallHeightMedia;
private int mMaxExpandedHeight;
+ private int mMaxCallHeight;
private int mIncreasedPaddingBetweenElements;
private int mNotificationLaunchHeight;
private boolean mMustStayOnScreen;
@@ -645,8 +647,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private void updateLimitsForView(NotificationContentView layout) {
- boolean customView = layout.getContractedChild() != null
- && layout.getContractedChild().getId()
+ View contractedView = layout.getContractedChild();
+ boolean customView = contractedView != null
+ && contractedView.getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
@@ -661,7 +664,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
View expandedView = layout.getExpandedChild();
boolean isMediaLayout = expandedView != null
&& expandedView.findViewById(com.android.internal.R.id.media_actions) != null;
- boolean isMessagingLayout = layout.getContractedChild() instanceof MessagingLayout;
+ boolean isMessagingLayout = contractedView instanceof MessagingLayout;
+ boolean isCallLayout = contractedView instanceof CallLayout;
boolean showCompactMediaSeekbar = mMediaManager.getShowCompactMediaSeekbar();
if (customView && beforeS && !mIsSummaryWithChildren) {
@@ -684,6 +688,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
// make sure we don't crop them terribly. We actually need to revisit this and give
// them a headerless design, then remove this hack.
smallHeight = mMaxSmallHeightLarge;
+ } else if (isCallLayout) {
+ smallHeight = mMaxCallHeight;
} else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
smallHeight = mMaxSmallHeightLarge;
} else {
@@ -1645,6 +1651,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
R.dimen.notification_min_height_media);
mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_height);
+ mMaxCallHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.call_notification_full_height);
mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_legacy);
mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
new file mode 100644
index 000000000000..4541ebf4c4f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 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.wrapper
+
+import android.content.Context
+import android.view.View
+import com.android.internal.widget.CachingIconView
+import com.android.internal.widget.CallLayout
+import com.android.systemui.R
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+
+/**
+ * Wraps a notification containing a call template
+ */
+class NotificationCallTemplateViewWrapper constructor(
+ ctx: Context,
+ view: View,
+ row: ExpandableNotificationRow
+) : NotificationTemplateViewWrapper(ctx, view, row) {
+
+ private val minHeightWithActions: Int =
+ NotificationUtils.getFontScaledHeight(ctx, R.dimen.call_notification_full_height)
+ private val callLayout: CallLayout = view as CallLayout
+
+ private lateinit var conversationIconView: CachingIconView
+ private lateinit var conversationBadgeBg: View
+ private lateinit var expandBtn: View
+ private lateinit var appName: View
+ private lateinit var conversationTitleView: View
+
+ private fun resolveViews() {
+ with(callLayout) {
+ conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
+ conversationBadgeBg =
+ requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
+ expandBtn = requireViewById(com.android.internal.R.id.expand_button)
+ appName = requireViewById(com.android.internal.R.id.app_name_text)
+ conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
+ }
+ }
+
+ override fun onContentUpdated(row: ExpandableNotificationRow) {
+ // Reinspect the notification. Before the super call, because the super call also updates
+ // the transformation types and we need to have our values set by then.
+ resolveViews()
+ super.onContentUpdated(row)
+ }
+
+ override fun updateTransformedTypes() {
+ // This also clears the existing types
+ super.updateTransformedTypes()
+ addTransformedViews(
+ appName,
+ conversationTitleView
+ )
+ addViewsTransformingToSimilar(
+ conversationIconView,
+ conversationBadgeBg,
+ expandBtn
+ )
+ }
+
+ override fun disallowSingleClick(x: Float, y: Float): Boolean {
+ val isOnExpandButton = expandBtn.visibility == View.VISIBLE &&
+ isOnView(expandBtn, x, y)
+ return isOnExpandButton || super.disallowSingleClick(x, y)
+ }
+
+ override fun getMinLayoutHeight(): Int = minHeightWithActions
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index c49f6cbda8ac..905bccfa6cdf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.row.wrapper
import android.content.Context
import android.view.View
-import android.view.View.GONE
import android.view.ViewGroup
import com.android.internal.widget.CachingIconView
import com.android.internal.widget.ConversationLayout
@@ -48,8 +47,8 @@ class NotificationConversationTemplateViewWrapper constructor(
private lateinit var conversationIconView: CachingIconView
private lateinit var conversationBadgeBg: View
- private lateinit var expandButton: View
- private lateinit var expandButtonContainer: View
+ private lateinit var expandBtn: View
+ private lateinit var expandBtnContainer: View
private lateinit var imageMessageContainer: ViewGroup
private lateinit var messagingLinearLayout: MessagingLinearLayout
private lateinit var conversationTitleView: View
@@ -66,9 +65,8 @@ class NotificationConversationTemplateViewWrapper constructor(
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)
- expandButtonContainer =
- requireViewById(com.android.internal.R.id.expand_button_container)
+ expandBtn = requireViewById(com.android.internal.R.id.expand_button)
+ expandBtnContainer = 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)
conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
@@ -126,7 +124,7 @@ class NotificationConversationTemplateViewWrapper constructor(
addViewsTransformingToSimilar(
conversationIconView,
conversationBadgeBg,
- expandButton,
+ expandBtn,
importanceRing,
facePileTop,
facePileBottom,
@@ -134,11 +132,9 @@ class NotificationConversationTemplateViewWrapper constructor(
)
}
- override fun getExpandButton() = super.getExpandButton()
-
override fun setShelfIconVisible(visible: Boolean) {
if (conversationLayout.isImportantConversation) {
- if (conversationIconView.visibility != GONE) {
+ if (conversationIconView.visibility != View.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.
@@ -152,7 +148,7 @@ class NotificationConversationTemplateViewWrapper constructor(
override fun getShelfTransformationTarget(): View? =
if (conversationLayout.isImportantConversation)
- if (conversationIconView.visibility != GONE)
+ if (conversationIconView.visibility != View.GONE)
conversationIconView
else
// A notification with a fallback icon was set to important. Currently
@@ -169,8 +165,8 @@ class NotificationConversationTemplateViewWrapper constructor(
conversationLayout.updateExpandability(expandable, onClickListener)
override fun disallowSingleClick(x: Float, y: Float): Boolean {
- val isOnExpandButton = expandButtonContainer.visibility == View.VISIBLE &&
- isOnView(expandButtonContainer, x, y)
+ val isOnExpandButton = expandBtnContainer.visibility == View.VISIBLE &&
+ isOnView(expandBtnContainer, x, y)
return isOnExpandButton || super.disallowSingleClick(x, y)
}
@@ -179,10 +175,4 @@ class NotificationConversationTemplateViewWrapper constructor(
minHeightWithActions
else
super.getMinLayoutHeight()
-
- private fun addTransformedViews(vararg vs: View?) =
- vs.forEach { view -> view?.let(mTransformationHelper::addTransformedView) }
-
- private fun addViewsTransformingToSimilar(vararg vs: View?) =
- vs.forEach { view -> view?.let(mTransformationHelper::addViewTransformingToSimilar) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 97201f5c9a34..34bc5370e8f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -36,7 +36,6 @@ 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.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
@@ -60,6 +59,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
private CachingIconView mIcon;
private NotificationExpandButton mExpandButton;
private View mAltExpandTarget;
+ private View mIconContainer;
protected NotificationHeaderView mNotificationHeader;
protected NotificationTopLineView mNotificationTopLine;
private TextView mHeaderText;
@@ -112,6 +112,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
mAppNameText = mView.findViewById(com.android.internal.R.id.app_name_text);
mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
mAltExpandTarget = mView.findViewById(com.android.internal.R.id.alternate_expand_target);
+ mIconContainer = mView.findViewById(com.android.internal.R.id.conversation_icon_container);
mLeftIcon = mView.findViewById(com.android.internal.R.id.left_icon);
mRightIcon = mView.findViewById(com.android.internal.R.id.right_icon);
mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
@@ -203,11 +204,8 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
public void clearConversationSkin() {
if (mAppNameText != null) {
final ColorStateList colors = mAppNameText.getTextColors();
- final int textAppearance = Utils.getThemeAttr(
- mAppNameText.getContext(),
- com.android.internal.R.attr.notificationHeaderTextAppearance,
+ mAppNameText.setTextAppearance(
com.android.internal.R.style.TextAppearance_DeviceDefault_Notification_Info);
- mAppNameText.setTextAppearance(textAppearance);
mAppNameText.setTextColor(colors);
MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams();
final int marginStart = mAppNameText.getResources().getDimensionPixelSize(
@@ -265,19 +263,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon);
mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_EXPANDER,
mExpandButton);
- if (mWorkProfileImage != null) {
- mTransformationHelper.addViewTransformingToSimilar(mWorkProfileImage);
- }
if (mIsLowPriority && mHeaderText != null) {
mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
mHeaderText);
}
- if (mAudiblyAlertedIcon != null) {
- mTransformationHelper.addViewTransformingToSimilar(mAudiblyAlertedIcon);
- }
- if (mFeedbackIcon != null) {
- mTransformationHelper.addViewTransformingToSimilar(mFeedbackIcon);
- }
+ addViewsTransformingToSimilar(mWorkProfileImage, mAudiblyAlertedIcon, mFeedbackIcon);
}
@Override
@@ -287,6 +277,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
if (mAltExpandTarget != null) {
mAltExpandTarget.setOnClickListener(expandable ? onClickListener : null);
}
+ if (mIconContainer != null) {
+ mIconContainer.setOnClickListener(expandable ? onClickListener : null);
+ }
if (mNotificationHeader != null) {
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
@@ -371,4 +364,20 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
super.setVisible(visible);
mTransformationHelper.setVisible(visible);
}
+
+ protected void addTransformedViews(View... views) {
+ for (View view : views) {
+ if (view != null) {
+ mTransformationHelper.addTransformedView(view);
+ }
+ }
+ }
+
+ protected void addViewsTransformingToSimilar(View... views) {
+ for (View view : views) {
+ if (view != null) {
+ mTransformationHelper.addViewTransformingToSimilar(view);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index b3d1a94beaa9..5fff8c83048f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -74,6 +74,8 @@ public abstract class NotificationViewWrapper implements TransformableView {
return new NotificationMessagingTemplateViewWrapper(ctx, v, row);
} else if ("conversation".equals(v.getTag())) {
return new NotificationConversationTemplateViewWrapper(ctx, v, row);
+ } else if ("call".equals(v.getTag())) {
+ return new NotificationCallTemplateViewWrapper(ctx, v, row);
}
Class<? extends Notification.Style> style =
row.getEntry().getSbn().getNotification().getNotificationStyle();