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

Commit 926d361b authored by Ibrahim Yilmaz's avatar Ibrahim Yilmaz
Browse files

[Conversation Notification] Fix Hybrid Conversation Avatar Loading

HybridConversationNotificationView uses Avatar information of ConversationLayout during binding. When async avatar loading is enabled, icon information is not available but drawable.
This CL fixes that issue by covering drawable case.

Bug: 305540309
Fixes: 330494691
Test: Choose Messaging Style Notifications and Click Add Group in Notify. Observe images are loaded correctly
Flag: ACONFIG android.widget.flags.conversation_style_set_avatar_async TEAMFOOD
Change-Id: I46c328361e117a27ab94a0e98325cfbe23532a98
parent ea141d2f
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)
}