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

Commit fb2bc32d authored by Ibrahim Yilmaz's avatar Ibrahim Yilmaz Committed by Android (Google) Code Review
Browse files

Merge "[Conversation Notification] Fix Hybrid Conversation Avatar Loading" into main

parents 1826bc2c 926d361b
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -21,9 +21,9 @@ import android.graphics.drawable.Drawable;
/**
 * @hide
 */
interface ConversationAvatarData {
public interface ConversationAvatarData {
    final class OneToOneConversationAvatarData implements ConversationAvatarData {
        final Drawable mDrawable;
        public final Drawable mDrawable;

        OneToOneConversationAvatarData(Drawable drawable) {
            mDrawable = drawable;
+2 −2
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ import android.annotation.Nullable;
/**
 * @hide
 */
final class ConversationHeaderData {
public final class ConversationHeaderData {
    private final CharSequence mConversationText;

    private final ConversationAvatarData mConversationAvatarData;
@@ -38,7 +38,7 @@ final class ConversationHeaderData {
    }

    @Nullable
    ConversationAvatarData getConversationAvatar() {
    public ConversationAvatarData getConversationAvatar() {
        return mConversationAvatarData;
    }
}
+12 −1
Original line number Diff line number Diff line
@@ -162,6 +162,8 @@ public class ConversationLayout extends FrameLayout
    private TouchDelegateComposite mTouchDelegate = new TouchDelegateComposite(this);
    private ArrayList<MessagingLinearLayout.MessagingChild> mToRecycle = new ArrayList<>();
    private boolean mPrecomputedTextEnabled = false;
    @Nullable
    private ConversationHeaderData mConversationHeaderData;

    public ConversationLayout(@NonNull Context context) {
        super(context);
@@ -651,6 +653,7 @@ public class ConversationLayout extends FrameLayout

    private void setConversationAvatarAndNameFromData(
            ConversationHeaderData conversationHeaderData) {
        mConversationHeaderData = conversationHeaderData;
        final OneToOneConversationAvatarData oneToOneConversationDrawable;
        final GroupConversationAvatarData groupConversationAvatarData;
        final ConversationAvatarData conversationAvatar =
@@ -804,7 +807,10 @@ public class ConversationLayout extends FrameLayout
        bottomBackground.setLayoutParams(layoutParams);
    }

    private void bindFacePileWithDrawable(ImageView bottomBackground, ImageView bottomView,
    /**
     * Binds group avatar drawables to face pile.
     */
    public void bindFacePileWithDrawable(ImageView bottomBackground, ImageView bottomView,
            ImageView topView, GroupConversationAvatarData groupConversationAvatarData) {
        applyNotificationBackgroundColor(bottomBackground);
        bottomView.setImageDrawable(groupConversationAvatarData.mLastIcon);
@@ -1573,6 +1579,11 @@ public class ConversationLayout extends FrameLayout
        return mConversationIcon;
    }

    @Nullable
    public ConversationHeaderData getConversationHeaderData() {
        return mConversationHeaderData;
    }

    private static class TouchDelegateComposite extends TouchDelegate {
        private final ArrayList<TouchDelegate> mDelegates = new ArrayList<>();

+82 −18
Original line number Diff line number Diff line
@@ -30,14 +30,21 @@ import android.widget.ImageView;
import android.widget.TextView;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ConversationAvatarData;
import com.android.internal.widget.ConversationAvatarData.GroupConversationAvatarData;
import com.android.internal.widget.ConversationAvatarData.OneToOneConversationAvatarData;
import com.android.internal.widget.ConversationHeaderData;
import com.android.internal.widget.ConversationLayout;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.row.shared.ConversationStyleSetAvatarAsync;
import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar;
import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile;
import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon;

import java.util.Objects;

/**
 * A hybrid view which may contain information about one ore more conversations.
 */
@@ -111,7 +118,38 @@ public class HybridConversationNotificationView extends HybridNotificationView {
        }

        ConversationLayout conversationLayout = (ConversationLayout) contentView;
        Icon conversationIcon = conversationLayout.getConversationIcon();
        loadConversationAvatar(conversationLayout);
        CharSequence conversationTitle = conversationLayout.getConversationTitle();
        if (TextUtils.isEmpty(conversationTitle)) {
            conversationTitle = title;
        }
        if (conversationLayout.isOneToOne()) {
            mConversationSenderName.setVisibility(GONE);
        } else {
            mConversationSenderName.setVisibility(VISIBLE);
            mConversationSenderName.setText(conversationLayout.getConversationSenderName());
        }
        CharSequence conversationText = conversationLayout.getConversationText();
        if (TextUtils.isEmpty(conversationText)) {
            conversationText = text;
        }
        super.bind(conversationTitle, conversationText, conversationLayout);
    }

    private void loadConversationAvatar(ConversationLayout conversationLayout) {
        AsyncHybridViewInflation.assertInLegacyMode();
        if (ConversationStyleSetAvatarAsync.isEnabled()) {
            loadConversationAvatarWithDrawable(conversationLayout);
        } else {
            loadConversationAvatarWithIcon(conversationLayout);
        }
    }

    @Deprecated
    private void loadConversationAvatarWithIcon(ConversationLayout conversationLayout) {
        ConversationStyleSetAvatarAsync.assertInLegacyMode();
        AsyncHybridViewInflation.assertInLegacyMode();
        final Icon conversationIcon = conversationLayout.getConversationIcon();
        if (conversationIcon != null) {
            mConversationFacePile.setVisibility(GONE);
            mConversationIconView.setVisibility(VISIBLE);
@@ -124,11 +162,11 @@ public class HybridConversationNotificationView extends HybridNotificationView {

            mConversationFacePile =
                    requireViewById(com.android.internal.R.id.conversation_face_pile);
            ImageView facePileBottomBg = mConversationFacePile.requireViewById(
            final ImageView facePileBottomBg = mConversationFacePile.requireViewById(
                    com.android.internal.R.id.conversation_face_pile_bottom_background);
            ImageView facePileBottom = mConversationFacePile.requireViewById(
            final ImageView facePileBottom = mConversationFacePile.requireViewById(
                    com.android.internal.R.id.conversation_face_pile_bottom);
            ImageView facePileTop = mConversationFacePile.requireViewById(
            final ImageView facePileTop = mConversationFacePile.requireViewById(
                    com.android.internal.R.id.conversation_face_pile_top);
            conversationLayout.bindFacePile(facePileBottomBg, facePileBottom, facePileTop);
            setSize(mConversationFacePile, mFacePileSize);
@@ -139,21 +177,47 @@ public class HybridConversationNotificationView extends HybridNotificationView {
            mTransformationHelper.addViewTransformingToSimilar(facePileBottom);
            mTransformationHelper.addViewTransformingToSimilar(facePileBottomBg);
        }
        CharSequence conversationTitle = conversationLayout.getConversationTitle();
        if (TextUtils.isEmpty(conversationTitle)) {
            conversationTitle = title;
    }
        if (conversationLayout.isOneToOne()) {
            mConversationSenderName.setVisibility(GONE);

    private void loadConversationAvatarWithDrawable(ConversationLayout conversationLayout) {
        AsyncHybridViewInflation.assertInLegacyMode();
        final ConversationHeaderData conversationHeaderData = Objects.requireNonNull(
                conversationLayout.getConversationHeaderData(),
                /* message = */ "conversationHeaderData should not be null");
        final ConversationAvatarData conversationAvatar =
                Objects.requireNonNull(conversationHeaderData.getConversationAvatar(),
                        /* message = */"conversationAvatar should not be null");

        if (conversationAvatar instanceof OneToOneConversationAvatarData oneToOneAvatar) {
            mConversationFacePile.setVisibility(GONE);
            mConversationIconView.setVisibility(VISIBLE);
            mConversationIconView.setImageDrawable(oneToOneAvatar.mDrawable);
            setSize(mConversationIconView, mSingleAvatarSize);
        } else {
            mConversationSenderName.setVisibility(VISIBLE);
            mConversationSenderName.setText(conversationLayout.getConversationSenderName());
        }
        CharSequence conversationText = conversationLayout.getConversationText();
        if (TextUtils.isEmpty(conversationText)) {
            conversationText = text;
            // If there isn't an icon, generate a "face pile" based on the sender avatars
            mConversationIconView.setVisibility(GONE);
            mConversationFacePile.setVisibility(VISIBLE);

            final GroupConversationAvatarData groupAvatar =
                    (GroupConversationAvatarData) conversationAvatar;
            mConversationFacePile =
                    requireViewById(com.android.internal.R.id.conversation_face_pile);
            final ImageView facePileBottomBg = mConversationFacePile.requireViewById(
                    com.android.internal.R.id.conversation_face_pile_bottom_background);
            final ImageView facePileBottom = mConversationFacePile.requireViewById(
                    com.android.internal.R.id.conversation_face_pile_bottom);
            final ImageView facePileTop = mConversationFacePile.requireViewById(
                    com.android.internal.R.id.conversation_face_pile_top);
            conversationLayout.bindFacePileWithDrawable(facePileBottomBg, facePileBottom,
                    facePileTop, groupAvatar);
            setSize(mConversationFacePile, mFacePileSize);
            setSize(facePileBottom, mFacePileAvatarSize);
            setSize(facePileTop, mFacePileAvatarSize);
            setSize(facePileBottomBg, mFacePileAvatarSize + 2 * mFacePileProtectionWidth);
            mTransformationHelper.addViewTransformingToSimilar(facePileTop);
            mTransformationHelper.addViewTransformingToSimilar(facePileBottom);
            mTransformationHelper.addViewTransformingToSimilar(facePileBottomBg);
        }
        super.bind(conversationTitle, conversationText, conversationLayout);
    }

    /**
+52 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.shared

import android.widget.flags.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils

/** Helper for reading or using the conversation style set avatar async flag state. */
@Suppress("NOTHING_TO_INLINE")
object ConversationStyleSetAvatarAsync {
    const val FLAG_NAME = Flags.FLAG_CONVERSATION_STYLE_SET_AVATAR_ASYNC

    /** A token used for dependency declaration */
    val token: FlagToken
        get() = FlagToken(FLAG_NAME, isEnabled)

    /** Is async hybrid (single-line) view inflation enabled */
    @JvmStatic
    inline val isEnabled
        get() = Flags.conversationStyleSetAvatarAsync()

    /**
     * Called to ensure code is only run when the flag is enabled. This protects users from the
     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
     * build to ensure that the refactor author catches issues in testing.
     */
    @JvmStatic
    inline fun isUnexpectedlyInLegacyMode() =
        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)

    /**
     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
     * the flag is enabled to ensure that the refactor author catches issues in testing.
     */
    @JvmStatic
    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
}