Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 38de36f4 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Group chat flyout support"

parents ce7f6e89 df898fd6
Loading
Loading
Loading
Loading
+34 −9
Original line number Diff line number Diff line
@@ -15,17 +15,40 @@
  -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <FrameLayout
    <LinearLayout
        android:id="@+id/bubble_flyout_text_container"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:orientation="horizontal"
        android:clipToPadding="false"
        android:paddingLeft="@dimen/bubble_flyout_padding_x"
        android:paddingRight="@dimen/bubble_flyout_padding_x"
        android:clipChildren="false"
        android:paddingStart="@dimen/bubble_flyout_padding_x"
        android:paddingEnd="@dimen/bubble_flyout_padding_x"
        android:paddingTop="@dimen/bubble_flyout_padding_y"
        android:paddingBottom="@dimen/bubble_flyout_padding_y"
        android:translationZ="@dimen/bubble_flyout_elevation">

        <ImageView
            android:id="@+id/bubble_flyout_avatar"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginEnd="@dimen/bubble_flyout_avatar_message_space"
            android:scaleType="centerInside"
            android:src="@drawable/ic_create_bubble"/>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/bubble_flyout_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
                android:maxLines="1"
                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>

            <TextView
                android:id="@+id/bubble_flyout_text"
                android:layout_width="wrap_content"
@@ -34,6 +57,8 @@
                android:maxLines="2"
                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>

    </FrameLayout>
        </LinearLayout>

    </LinearLayout>

</merge>
 No newline at end of file
+4 −2
Original line number Diff line number Diff line
@@ -1097,13 +1097,15 @@
    <!-- How much the bubble flyout text container is elevated. -->
    <dimen name="bubble_flyout_elevation">4dp</dimen>
    <!-- How much padding is around the left and right sides of the flyout text. -->
    <dimen name="bubble_flyout_padding_x">16dp</dimen>
    <dimen name="bubble_flyout_padding_x">12dp</dimen>
    <!-- How much padding is around the top and bottom of the flyout text. -->
    <dimen name="bubble_flyout_padding_y">8dp</dimen>
    <dimen name="bubble_flyout_padding_y">10dp</dimen>
    <!-- Size of the triangle that points from the flyout to the bubble stack. -->
    <dimen name="bubble_flyout_pointer_size">6dp</dimen>
    <!-- How much space to leave between the flyout (tip of the arrow) and the bubble stack. -->
    <dimen name="bubble_flyout_space_from_bubble">8dp</dimen>
    <!-- How much space to leave between the flyout text and the avatar displayed in the flyout. -->
    <dimen name="bubble_flyout_avatar_message_space">6dp</dimen>
    <!-- Padding between status bar and bubbles when displayed in expanded state -->
    <dimen name="bubble_padding_top">16dp</dimen>
    <!-- Size of individual bubbles. -->
+18 −70
Original line number Diff line number Diff line
@@ -32,20 +32,17 @@ import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;

/**
@@ -85,6 +82,18 @@ class Bubble {
    /** Whether flyout text should be suppressed, regardless of any other flags or state. */
    private boolean mSuppressFlyout;

    /**
     * Presentational info about the flyout.
     */
    public static class FlyoutMessage {
        @Nullable public Drawable senderAvatar;
        @Nullable public CharSequence senderName;
        @Nullable public CharSequence message;
        @Nullable public boolean isGroupChat;
    }

    private FlyoutMessage mFlyoutMessage;

    public static String groupId(NotificationEntry entry) {
        UserHandle user = entry.getSbn().getUser();
        return user.getIdentifier() + "|" + entry.getSbn().getPackageName();
@@ -194,6 +203,7 @@ class Bubble {

        mShortcutInfo = info.shortcutInfo;
        mAppName = info.appName;
        mFlyoutMessage = info.flyoutMessage;

        mExpandedView.update(this);
        mIconView.update(this, info.badgedBubbleImage, info.dotColor, info.dotPath);
@@ -307,6 +317,10 @@ class Bubble {
        mSuppressFlyout = suppressFlyout;
    }

    FlyoutMessage getFlyoutMessage() {
        return mFlyoutMessage;
    }

    /**
     * Returns whether the notification for this bubble is a foreground service. It shows that this
     * is an ongoing bubble.
@@ -368,72 +382,6 @@ class Bubble {
        return intent;
    }

    /**
     * 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.
     */
    CharSequence getUpdateMessage(Context context) {
        final Notification underlyingNotif = mEntry.getSbn().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;
    }

    private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) {
        PackageManager pm = context.getPackageManager();
        Resources r;
+52 −12
Original line number Diff line number Diff line
@@ -32,11 +32,13 @@ import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.ShapeDrawable;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.Nullable;
@@ -65,7 +67,9 @@ public class BubbleFlyoutView extends FrameLayout {
    private final float mCornerRadius;

    private final ViewGroup mFlyoutTextContainer;
    private final TextView mFlyoutText;
    private final ImageView mSenderAvatar;
    private final TextView mSenderText;
    private final TextView mMessageText;

    /** Values related to the 'new' dot which we use to figure out where to collapse the flyout. */
    private final float mNewDotRadius;
@@ -142,7 +146,9 @@ public class BubbleFlyoutView extends FrameLayout {
        LayoutInflater.from(context).inflate(R.layout.bubble_flyout, this, true);

        mFlyoutTextContainer = findViewById(R.id.bubble_flyout_text_container);
        mFlyoutText = mFlyoutTextContainer.findViewById(R.id.bubble_flyout_text);
        mSenderText = findViewById(R.id.bubble_flyout_name);
        mSenderAvatar = findViewById(R.id.bubble_flyout_avatar);
        mMessageText = mFlyoutTextContainer.findViewById(R.id.bubble_flyout_text);

        final Resources res = getResources();
        mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
@@ -204,9 +210,34 @@ public class BubbleFlyoutView extends FrameLayout {

    /** Configures the flyout, collapsed into to dot form. */
    void setupFlyoutStartingAsDot(
            CharSequence updateMessage, PointF stackPos, float parentWidth,
            boolean arrowPointingLeft, int dotColor, @Nullable Runnable onLayoutComplete,
            @Nullable Runnable onHide, float[] dotCenter, boolean hideDot) {
            Bubble.FlyoutMessage flyoutMessage,
            PointF stackPos,
            float parentWidth,
            boolean arrowPointingLeft,
            int dotColor,
            @Nullable Runnable onLayoutComplete,
            @Nullable Runnable onHide,
            float[] dotCenter,
            boolean hideDot) {

        if (flyoutMessage.senderAvatar != null && flyoutMessage.isGroupChat) {
            mSenderAvatar.setVisibility(VISIBLE);
            mSenderAvatar.setImageDrawable(flyoutMessage.senderAvatar);
        } else {
            mSenderAvatar.setVisibility(GONE);
            mSenderAvatar.setTranslationX(0);
            mMessageText.setTranslationX(0);
            mSenderText.setTranslationX(0);
        }

        // Name visibility
        if (!TextUtils.isEmpty(flyoutMessage.senderName)) {
            mSenderText.setText(flyoutMessage.senderName);
            mSenderText.setVisibility(VISIBLE);
        } else {
            mSenderText.setVisibility(GONE);
        }

        mArrowPointingLeft = arrowPointingLeft;
        mDotColor = dotColor;
        mOnHide = onHide;
@@ -217,15 +248,15 @@ public class BubbleFlyoutView extends FrameLayout {
        // Set the flyout TextView's max width in terms of percent, and then subtract out the
        // padding so that the entire flyout view will be the desired width (rather than the
        // TextView being the desired width + extra padding).
        mFlyoutText.setMaxWidth(
        mMessageText.setMaxWidth(
                (int) (parentWidth * FLYOUT_MAX_WIDTH_PERCENT) - mFlyoutPadding * 2);
        mFlyoutText.setText(updateMessage);
        mMessageText.setText(flyoutMessage.message);

        // Wait for the TextView to lay out so we know its line count.
        post(() -> {
            float restingTranslationY;
            // Multi line flyouts get top-aligned to the bubble.
            if (mFlyoutText.getLineCount() > 1) {
            if (mMessageText.getLineCount() > 1) {
                restingTranslationY = stackPos.y + mBubbleIconTopPadding;
            } else {
                // Single line flyouts are vertically centered with respect to the bubble.
@@ -289,11 +320,20 @@ public class BubbleFlyoutView extends FrameLayout {
        mPercentStillFlyout = (1f - mPercentTransitionedToDot);

        // Move and fade out the text.
        mFlyoutText.setTranslationX(
                (mArrowPointingLeft ? -getWidth() : getWidth()) * mPercentTransitionedToDot);
        mFlyoutText.setAlpha(clampPercentage(
        final float translationX = mPercentTransitionedToDot
                * (mArrowPointingLeft ? -getWidth() : getWidth());
        final float alpha = clampPercentage(
                (mPercentStillFlyout - (1f - BubbleStackView.FLYOUT_DRAG_PERCENT_DISMISS))
                        / BubbleStackView.FLYOUT_DRAG_PERCENT_DISMISS));
                        / BubbleStackView.FLYOUT_DRAG_PERCENT_DISMISS);

        mMessageText.setTranslationX(translationX);
        mMessageText.setAlpha(alpha);

        mSenderText.setTranslationX(translationX);
        mSenderText.setAlpha(alpha);

        mSenderAvatar.setTranslationX(translationX);
        mSenderAvatar.setAlpha(alpha);

        // Reduce the elevation towards that of the topmost bubble.
        setTranslationZ(
+5 −4
Original line number Diff line number Diff line
@@ -1377,9 +1377,10 @@ public class BubbleStackView extends FrameLayout {
     */
    @VisibleForTesting
    void animateInFlyoutForBubble(Bubble bubble) {
        final CharSequence updateMessage = bubble.getUpdateMessage(getContext());
        Bubble.FlyoutMessage flyoutMessage = bubble.getFlyoutMessage();
        final BadgedImageView bubbleView = bubble.getIconView();
        if (updateMessage == null
        if (flyoutMessage == null
                || flyoutMessage.message == null
                || !bubble.showFlyout()
                || isExpanded()
                || mIsExpansionAnimating
@@ -1432,8 +1433,8 @@ public class BubbleStackView extends FrameLayout {
                };
                mFlyout.postDelayed(mAnimateInFlyout, 200);
            };
            mFlyout.setupFlyoutStartingAsDot(
                    updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
            mFlyout.setupFlyoutStartingAsDot(flyoutMessage,
                    mStackAnimationController.getStackPosition(), getWidth(),
                    mStackAnimationController.isStackOnLeftSide(),
                    bubble.getIconView().getDotColor() /* dotColor */,
                    expandFlyoutAfterDelay /* onLayoutComplete */,
Loading