Adds the flyout view.

This moves the view itself from BubbleView to BubbleStackView, since there will never be multiple flyouts and it simplifies layout. This also adds getUpdateMessage to NotificationEntry which is used to generate the flyout text.

Test: atest SystemUITests
Change-Id: Ief2fcfb2b12b927fdd68f737d49080335c884bef
This commit is contained in:
Joshua Tsuji
2019-03-26 13:57:05 -04:00
parent e1e0528d85
commit 614b1df084
12 changed files with 449 additions and 43 deletions

View File

@@ -0,0 +1,29 @@
<!--
~ Copyright (C) 2019 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
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- TODO: Add the triangle pointing to the bubble stack. -->
<item>
<shape android:shape="rectangle">
<solid android:color="?android:attr/colorBackgroundFloating" />
<corners
android:bottomLeftRadius="?android:attr/dialogCornerRadius"
android:topLeftRadius="?android:attr/dialogCornerRadius"
android:bottomRightRadius="?android:attr/dialogCornerRadius"
android:topRightRadius="?android:attr/dialogCornerRadius"
/>
</shape>
</item>
</layer-list>

View File

@@ -0,0 +1,33 @@
<!--
~ Copyright (C) 2019 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/bubble_flyout"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="@drawable/bubble_flyout"
android:padding="@dimen/bubble_flyout_padding"
android:translationZ="@dimen/bubble_flyout_elevation">
<TextView
android:id="@+id/bubble_flyout_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:maxWidth="@dimen/bubble_flyout_maxwidth"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" />
</FrameLayout>

View File

@@ -27,12 +27,4 @@
android:padding="@dimen/bubble_view_padding"
android:clipToPadding="false"/>
<TextView
android:id="@+id/message_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="@dimen/bubble_message_min_width"
android:maxWidth="@dimen/bubble_message_max_width"
android:padding="@dimen/bubble_message_padding"/>
</com.android.systemui.bubbles.BubbleView>

View File

@@ -1027,6 +1027,12 @@
<!-- How much each bubble is elevated. -->
<dimen name="bubble_elevation">1dp</dimen>
<!-- How much the bubble flyout text container is elevated. -->
<dimen name="bubble_flyout_elevation">4dp</dimen>
<!-- How much padding is around the flyout text. -->
<dimen name="bubble_flyout_padding">16dp</dimen>
<!-- The maximum width of a bubble flyout. -->
<dimen name="bubble_flyout_maxwidth">200dp</dimen>
<!-- Padding around a collapsed bubble -->
<dimen name="bubble_view_padding">0dp</dimen>
<!-- Padding between bubbles when displayed in expanded state -->

View File

@@ -670,6 +670,9 @@
<item quantity="other"><xliff:g id="number" example="3">%s</xliff:g> more notifications inside.</item>
</plurals>
<!-- Format to use to summarize a message from a contact in a single line of text. For example: "Julia: How's it going?". [CHAR LIMIT=NONE] -->
<string name="notification_summary_message_format"><xliff:g id="contact_name" example="Julia">%1$s</xliff:g>: <xliff:g id="message_content" example="How is it going?">%2$s</xliff:g></string>
<!-- Content description of button in notification inspector for system settings relating to
notifications from this application [CHAR LIMIT=NONE] -->
<string name="status_bar_notification_inspect_item_title">Notification settings</string>
@@ -2401,5 +2404,4 @@
<string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string>
<!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]-->
<string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string>
</resources>

View File

@@ -42,7 +42,9 @@ import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
@@ -70,6 +72,13 @@ public class BubbleStackView extends FrameLayout {
private static final String TAG = "BubbleStackView";
private static final boolean DEBUG = false;
/** Duration of the flyout alpha animations. */
private static final int FLYOUT_ALPHA_ANIMATION_DURATION = 100;
/** How long to wait, in milliseconds, before hiding the flyout. */
@VisibleForTesting
static final int FLYOUT_HIDE_AFTER = 5000;
/**
* Interface to synchronize {@link View} state and the screen.
*
@@ -119,6 +128,14 @@ public class BubbleStackView extends FrameLayout {
private FrameLayout mExpandedViewContainer;
private View mFlyout;
private TextView mFlyoutText;
/** Spring animation for the flyout. */
private SpringAnimation mFlyoutSpring;
/** Runnable that fades out the flyout and then sets it to GONE. */
private Runnable mHideFlyout =
() -> mFlyout.animate().alpha(0f).withEndAction(() -> mFlyout.setVisibility(GONE));
private int mBubbleSize;
private int mBubblePadding;
private int mExpandedAnimateXDistance;
@@ -131,6 +148,9 @@ public class BubbleStackView extends FrameLayout {
private boolean mIsExpanded;
private boolean mImeVisible;
/** Whether the stack is currently being dragged. */
private boolean mIsDragging = false;
private BubbleTouchHandler mTouchHandler;
private BubbleController.BubbleExpandListener mExpandListener;
private BubbleExpandedView.OnBubbleBlockedListener mBlockedListener;
@@ -221,6 +241,17 @@ public class BubbleStackView extends FrameLayout {
mExpandedViewContainer.setClipChildren(false);
addView(mExpandedViewContainer);
mFlyout = mInflater.inflate(R.layout.bubble_flyout, this, false);
mFlyout.setVisibility(GONE);
mFlyout.animate()
.setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
.setInterpolator(new AccelerateDecelerateInterpolator());
addView(mFlyout);
mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
mFlyoutSpring = new SpringAnimation(mFlyout, DynamicAnimation.TRANSLATION_X);
mExpandedViewXAnim =
new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
mExpandedViewXAnim.setSpring(
@@ -448,6 +479,8 @@ public class BubbleStackView extends FrameLayout {
requestUpdate();
logBubbleEvent(b, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
animateInFlyoutForBubble(b);
}
/**
@@ -549,6 +582,7 @@ public class BubbleStackView extends FrameLayout {
mBubbleContainer.moveViewTo(b.iconView, 0);
}
requestUpdate();
animateInFlyoutForBubble(b /* bubble */);
}
if (mIsExpanded && entry.equals(mExpandedBubble.entry)) {
entry.setShowInShadeWhenBubble(false);
@@ -577,11 +611,18 @@ public class BubbleStackView extends FrameLayout {
}
// Outside parts of view we care about.
return null;
} else if (isIntersecting(mFlyout, x, y)) {
return mFlyout;
}
// If we're collapsed, the stack is always the target.
// If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack.
return this;
}
public View getFlyoutView() {
return mFlyout;
}
/**
* Collapses the stack of bubbles.
* <p>
@@ -622,6 +663,8 @@ public class BubbleStackView extends FrameLayout {
*/
private void animateExpansion(boolean shouldExpand) {
if (mIsExpanded != shouldExpand) {
hideFlyoutImmediate();
mIsExpanded = shouldExpand;
updateExpandedBubble();
applyCurrentState();
@@ -735,6 +778,9 @@ public class BubbleStackView extends FrameLayout {
mStackAnimationController.cancelStackPositionAnimations();
mBubbleContainer.setController(mStackAnimationController);
hideFlyoutImmediate();
mIsDragging = true;
}
void onDragged(float x, float y) {
@@ -747,6 +793,7 @@ public class BubbleStackView extends FrameLayout {
void onDragFinish(float x, float y, float velX, float velY) {
// TODO: Add fling to bottom to dismiss.
mIsDragging = false;
if (mIsExpanded || mIsExpansionAnimating) {
return;
@@ -797,6 +844,47 @@ public class BubbleStackView extends FrameLayout {
}
}
/**
* Animates in the flyout for the given bubble, if available, and then hides it after some time.
*/
@VisibleForTesting
void animateInFlyoutForBubble(Bubble bubble) {
final CharSequence updateMessage = bubble.entry.getUpdateMessage(getContext());
// Show the message if one exists, and we're not expanded or animating expansion.
if (updateMessage != null && !isExpanded() && !mIsExpansionAnimating && !mIsDragging) {
final PointF stackPos = mStackAnimationController.getStackPosition();
mFlyoutText.setText(updateMessage);
mFlyout.measure(WRAP_CONTENT, WRAP_CONTENT);
mFlyout.post(() -> {
final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
final float destinationX = onLeft
? stackPos.x + mBubbleSize + mBubblePadding
: stackPos.x - mFlyout.getMeasuredWidth();
// Translate towards the stack slightly, then spring out from the stack.
mFlyout.setTranslationX(destinationX + (onLeft ? -mBubblePadding : mBubblePadding));
mFlyout.setTranslationY(stackPos.y);
mFlyout.setAlpha(0f);
mFlyout.setVisibility(VISIBLE);
mFlyout.animate().alpha(1f);
mFlyoutSpring.animateToFinalPosition(destinationX);
mFlyout.removeCallbacks(mHideFlyout);
mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
});
}
}
/** Hide the flyout immediately and cancel any pending hide runnables. */
private void hideFlyoutImmediate() {
mFlyout.removeCallbacks(mHideFlyout);
mHideFlyout.run();
}
@Override
public void getBoundsOnScreen(Rect outRect) {
if (!mIsExpanded) {
@@ -806,6 +894,12 @@ public class BubbleStackView extends FrameLayout {
} else {
mBubbleContainer.getBoundsOnScreen(outRect);
}
if (mFlyout.getVisibility() == View.VISIBLE) {
final Rect flyoutBounds = new Rect();
mFlyout.getBoundsOnScreen(flyoutBounds);
outRect.union(flyoutBounds);
}
}
private int getStatusBarHeight() {

View File

@@ -86,6 +86,7 @@ class BubbleTouchHandler implements View.OnTouchListener {
}
final boolean isStack = mStack.equals(mTouchedView);
final boolean isFlyout = mStack.getFlyoutView().equals(mTouchedView);
final float rawX = event.getRawX();
final float rawY = event.getRawY();
@@ -104,6 +105,8 @@ class BubbleTouchHandler implements View.OnTouchListener {
if (isStack) {
mViewPositionOnTouchDown.set(mStack.getStackPosition());
mStack.onDragStart();
} else if (isFlyout) {
// TODO(b/129768381): Make the flyout dismissable with a gesture.
} else {
mViewPositionOnTouchDown.set(
mTouchedView.getTranslationX(), mTouchedView.getTranslationY());
@@ -123,6 +126,8 @@ class BubbleTouchHandler implements View.OnTouchListener {
if (mMovedEnough) {
if (isStack) {
mStack.onDragged(viewX, viewY);
} else if (isFlyout) {
// TODO(b/129768381): Make the flyout dismissable with a gesture.
} else {
mStack.onBubbleDragged(mTouchedView, viewX, viewY);
}
@@ -141,6 +146,11 @@ class BubbleTouchHandler implements View.OnTouchListener {
trackMovement(event);
if (mInDismissTarget && isStack) {
mController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
} else if (isFlyout) {
// TODO(b/129768381): Expand if tapped, dismiss if swiped away.
if (!mStack.isExpanded()) {
mStack.expandStack();
}
} else if (mMovedEnough) {
mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
final float velX = mVelocityTracker.getXVelocity();

View File

@@ -27,7 +27,6 @@ import android.graphics.drawable.Icon;
import android.graphics.drawable.InsetDrawable;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.internal.graphics.ColorUtils;
import com.android.systemui.Interpolators;
@@ -49,7 +48,6 @@ public class BubbleView extends FrameLayout {
private Context mContext;
private BadgedImageView mBadgedImageView;
private TextView mMessageView;
private int mPadding;
private int mIconInset;
@@ -78,10 +76,7 @@ public class BubbleView extends FrameLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mBadgedImageView = (BadgedImageView) findViewById(R.id.bubble_image);
mMessageView = (TextView) findViewById(R.id.message_view);
mMessageView.setVisibility(GONE);
mMessageView.setPivotX(0);
mBadgedImageView = findViewById(R.id.bubble_image);
}
@Override
@@ -89,33 +84,6 @@ public class BubbleView extends FrameLayout {
super.onAttachedToWindow();
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
measureChild(mBadgedImageView, widthSpec, heightSpec);
measureChild(mMessageView, widthSpec, heightSpec);
boolean messageGone = mMessageView.getVisibility() == GONE;
int imageHeight = mBadgedImageView.getMeasuredHeight();
int imageWidth = mBadgedImageView.getMeasuredWidth();
int messageHeight = messageGone ? 0 : mMessageView.getMeasuredHeight();
int messageWidth = messageGone ? 0 : mMessageView.getMeasuredWidth();
setMeasuredDimension(
getPaddingStart() + imageWidth + mPadding + messageWidth + getPaddingEnd(),
getPaddingTop() + Math.max(imageHeight, messageHeight) + getPaddingBottom());
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
left = getPaddingStart();
top = getPaddingTop();
int imageWidth = mBadgedImageView.getMeasuredWidth();
int imageHeight = mBadgedImageView.getMeasuredHeight();
int messageWidth = mMessageView.getMeasuredWidth();
int messageHeight = mMessageView.getMeasuredHeight();
mBadgedImageView.layout(left, top, left + imageWidth, top + imageHeight);
mMessageView.layout(left + imageWidth + mPadding, top,
left + imageWidth + mPadding + messageWidth, top + messageHeight);
}
/**
* Populates this view with a notification.
* <p>

View File

@@ -157,6 +157,15 @@ public class StackAnimationController extends
return mStackPosition;
}
/** Whether the stack is on the left side of the screen. */
public boolean isStackOnLeftSide() {
if (mLayout != null) {
return mStackPosition.x - mIndividualBubbleSize / 2 < mLayout.getWidth() / 2;
} else {
return false;
}
}
/**
* Flings the stack starting with the given velocities, springing it to the nearest edge
* afterward.

View File

@@ -41,6 +41,7 @@ import android.os.SystemClock;
import android.service.notification.NotificationListenerService;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.View;
import android.widget.ImageView;
@@ -51,6 +52,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.R;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.InflationException;
@@ -392,6 +394,72 @@ public final class NotificationEntry {
return mCachedContrastColor;
}
/**
* Returns our best guess for the most relevant text summary of the latest update to this
* notification, based on its type. Returns null if there should not be an update message.
*/
public CharSequence getUpdateMessage(Context context) {
final Notification underlyingNotif = notification.getNotification();
final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
try {
if (Notification.BigTextStyle.class.equals(style)) {
// Return the big text, it is big so probably important. If it's not there use the
// normal text.
CharSequence bigText =
underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
return !TextUtils.isEmpty(bigText)
? bigText
: underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
} else if (Notification.MessagingStyle.class.equals(style)) {
final List<Notification.MessagingStyle.Message> messages =
Notification.MessagingStyle.Message.getMessagesFromBundleArray(
(Parcelable[]) underlyingNotif.extras.get(
Notification.EXTRA_MESSAGES));
final Notification.MessagingStyle.Message latestMessage =
Notification.MessagingStyle.findLatestIncomingMessage(messages);
if (latestMessage != null) {
final CharSequence personName = latestMessage.getSenderPerson() != null
? latestMessage.getSenderPerson().getName()
: null;
// Prepend the sender name if available since group chats also use messaging
// style.
if (!TextUtils.isEmpty(personName)) {
return context.getResources().getString(
R.string.notification_summary_message_format,
personName,
latestMessage.getText());
} else {
return latestMessage.getText();
}
}
} else if (Notification.InboxStyle.class.equals(style)) {
CharSequence[] lines =
underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
// Return the last line since it should be the most recent.
if (lines != null && lines.length > 0) {
return lines[lines.length - 1];
}
} else if (Notification.MediaStyle.class.equals(style)) {
// Return nothing, media updates aren't typically useful as a text update.
return null;
} else {
// Default to text extra.
return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
}
} catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
// No use crashing, we'll just return null and the caller will assume there's no update
// message.
e.printStackTrace();
}
return null;
}
/**
* Abort all existing inflation tasks
*/

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2019 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.bubbles;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import android.widget.TextView;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BubbleStackViewTest extends SysuiTestCase {
private BubbleStackView mStackView;
@Mock private Bubble mBubble;
@Mock private NotificationEntry mNotifEntry;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mStackView = new BubbleStackView(mContext, new BubbleData(), null);
mBubble.entry = mNotifEntry;
}
@Test
public void testAnimateInFlyoutForBubble() throws InterruptedException {
when(mNotifEntry.getUpdateMessage(any())).thenReturn("Test Flyout Message.");
mStackView.animateInFlyoutForBubble(mBubble);
// Wait for the fade in.
Thread.sleep(200);
// Flyout should be visible and showing our text.
assertEquals(1f, mStackView.findViewById(R.id.bubble_flyout).getAlpha(), .01f);
assertEquals("Test Flyout Message.",
((TextView) mStackView.findViewById(R.id.bubble_flyout_text)).getText());
// Wait until it should have gone away.
Thread.sleep(BubbleStackView.FLYOUT_HIDE_AFTER + 200);
// Flyout should be gone.
assertEquals(View.GONE, mStackView.findViewById(R.id.bubble_flyout).getVisibility());
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2019 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.collection;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class NotificationEntryTest extends SysuiTestCase {
@Mock
private StatusBarNotification mStatusBarNotification;
@Mock
private Notification mNotif;
private NotificationEntry mEntry;
private Bundle mExtras;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mStatusBarNotification.getKey()).thenReturn("key");
when(mStatusBarNotification.getNotification()).thenReturn(mNotif);
mExtras = new Bundle();
mNotif.extras = mExtras;
mEntry = new NotificationEntry(mStatusBarNotification);
}
@Test
public void testGetUpdateMessage_default() {
final String msg = "Hello there!";
doReturn(Notification.Style.class).when(mNotif).getNotificationStyle();
mExtras.putCharSequence(Notification.EXTRA_TEXT, msg);
assertEquals(msg, mEntry.getUpdateMessage(mContext));
}
@Test
public void testGetUpdateMessage_bigText() {
final String msg = "A big hello there!";
doReturn(Notification.BigTextStyle.class).when(mNotif).getNotificationStyle();
mExtras.putCharSequence(Notification.EXTRA_TEXT, "A small hello there.");
mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg);
// Should be big text, not the small text.
assertEquals(msg, mEntry.getUpdateMessage(mContext));
}
@Test
public void testGetUpdateMessage_media() {
doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle();
// Media notifs don't get update messages.
assertNull(mEntry.getUpdateMessage(mContext));
}
@Test
public void testGetUpdateMessage_inboxStyle() {
doReturn(Notification.InboxStyle.class).when(mNotif).getNotificationStyle();
mExtras.putCharSequenceArray(
Notification.EXTRA_TEXT_LINES,
new CharSequence[]{
"How do you feel about tests?",
"They're okay, I guess.",
"I hate when they're flaky.",
"Really? I prefer them that way."});
// Should be the last one only.
assertEquals("Really? I prefer them that way.", mEntry.getUpdateMessage(mContext));
}
@Test
public void testGetUpdateMessage_messagingStyle() {
doReturn(Notification.MessagingStyle.class).when(mNotif).getNotificationStyle();
mExtras.putParcelableArray(
Notification.EXTRA_MESSAGES,
new Bundle[]{
new Notification.MessagingStyle.Message(
"Hello", 0, "Josh").toBundle(),
new Notification.MessagingStyle.Message(
"Oh, hello!", 0, "Mady").toBundle()});
// Should be the last one only.
assertEquals("Mady: Oh, hello!", mEntry.getUpdateMessage(mContext));
}
}