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

Commit 24b192cd authored by Neil Gu's avatar Neil Gu Committed by Android (Google) Code Review
Browse files

Merge "[Notifications] Added a UiEvent for logging the animated actions and...

Merge "[Notifications] Added a UiEvent for logging the animated actions and animated replies inflated." into main
parents ff225649 64eee616
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.notifications

import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger

/** The notification animated chip UiEvent enums. */
enum class NotificationAnimatedChipEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
    @UiEvent(doc = "The animated reply chip was shown")
    NOTIFICATION_ANIMATED_REPLY_CHIP_VISIBLE(2395),
    @UiEvent(doc = "The animated action chip was shown")
    NOTIFICATION_ANIMATED_ACTION_CHIP_VISIBLE(2396);

    override fun getId() = _id
}
+42 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.policy

import com.android.internal.util.FrameworkStatsLog
import com.android.systemui.notifications.NotificationAnimatedChipEvent

public class SmartReplyLogger {

    fun logAnimatedReplyChipVisibleEvent() {
        FrameworkStatsLog.write(
            FrameworkStatsLog.UI_EVENT_REPORTED,
            NotificationAnimatedChipEvent.NOTIFICATION_ANIMATED_REPLY_CHIP_VISIBLE.getId(),
            0,
            null,
            0,
        )
    }

    fun logAnimatedActionChipVisibleEvent() {
        FrameworkStatsLog.write(
            FrameworkStatsLog.UI_EVENT_REPORTED,
            NotificationAnimatedChipEvent.NOTIFICATION_ANIMATED_ACTION_CHIP_VISIBLE.getId(),
            0,
            null,
            0,
        )
    }
}
+58 −6
Original line number Diff line number Diff line
@@ -70,6 +70,10 @@ public class SmartReplyView extends ViewGroup {

    private View mSmartReplyContainer;

    private boolean mIsLoggedAnimatedReplies = false;

    private boolean mIsLoggedAnimatedActions = false;

    /**
     * Whether the smart replies in this view were generated by the notification assistant. If not
     * they're provided by the app.
@@ -100,6 +104,7 @@ public class SmartReplyView extends ViewGroup {
    private long mLastMeasureTime;
    private int mTotalSqueezeRemeasureAttempts;
    private boolean mDidHideSystemReplies;
    private SmartReplyLogger mSmartReplyLogger = new SmartReplyLogger();

    public SmartReplyView(Context context, AttributeSet attrs) {
        super(context, attrs);
@@ -269,10 +274,11 @@ public class SmartReplyView extends ViewGroup {
        List<View> smartSuggestions = new ArrayList<>();
        List<View> smartActions = filterActionsOrReplies(SmartButtonType.ACTION);
        List<View> smartReplies = filterActionsOrReplies(SmartButtonType.REPLY);

        List<View> animatedReplies = new ArrayList<>();
        List<View> animatedActions = new ArrayList<>();
        if (Flags.notificationAnimatedActionsTreatment()) {
            List<View> animatedReplies = filterActionsOrReplies(SmartButtonType.ANIMATED_REPLY);
            List<View> animatedActions = filterActionsOrReplies(SmartButtonType.ANIMATED_ACTION);
            animatedReplies = filterActionsOrReplies(SmartButtonType.ANIMATED_REPLY);
            animatedActions = filterActionsOrReplies(SmartButtonType.ANIMATED_ACTION);
            smartSuggestions.addAll(animatedReplies);
            smartSuggestions.addAll(animatedActions);
        }
@@ -410,10 +416,13 @@ public class SmartReplyView extends ViewGroup {
                + accumulatedMeasures.mMaxChildHeight + mPaddingBottom);

        setMeasuredDimension(
                resolveSize(Math.max(getSuggestedMinimumWidth(),
                                     accumulatedMeasures.mMeasuredWidth),
                resolveSize(
                        Math.max(getSuggestedMinimumWidth(), accumulatedMeasures.mMeasuredWidth),
                        widthMeasureSpec),
                resolveSize(buttonHeight, heightMeasureSpec));
        if (Flags.notificationAnimatedActionsTreatment() && mSmartRepliesGeneratedByAssistant) {
            logAnimatedRepliesAndActionsImpressions(animatedReplies, animatedActions);
        }
        mLastMeasureTime = SystemClock.elapsedRealtime();
    }

@@ -479,6 +488,49 @@ public class SmartReplyView extends ViewGroup {
        pw.decreaseIndent();
    }

  @VisibleForTesting
  void setSmartReplyLogger(SmartReplyLogger smartReplyLogger) {
        mSmartReplyLogger = smartReplyLogger;
    }

    /**
     * After the views are squeezed, logs the accurate impressions of animated replies and actions.
     */
    private void logAnimatedRepliesAndActionsImpressions(List<View> animatedReplies,
            List<View> animatedActions) {
        if (!mIsLoggedAnimatedReplies) {
            final boolean hasAnimatedReplies = anyViewIsShown(animatedReplies);
            if (hasAnimatedReplies) {
                mIsLoggedAnimatedReplies = true;
                mSmartReplyLogger.logAnimatedReplyChipVisibleEvent();
            }
        }
        if (!mIsLoggedAnimatedActions) {
            final boolean hasAnimatedActions = anyViewIsShown(animatedActions);
            if (hasAnimatedActions) {
                mIsLoggedAnimatedActions = true;
                mSmartReplyLogger.logAnimatedActionChipVisibleEvent();
            }
        }
    }

    /** Returns true if any of the views is shown (not squeezed). */
    private boolean anyViewIsShown(List<View> views) {
        if (views == null) {
            return false;
        }
        for (View view : views) {
            final ViewGroup.LayoutParams viewGroupLayoutParams = view.getLayoutParams();
            if (viewGroupLayoutParams != null && viewGroupLayoutParams instanceof LayoutParams) {
                final LayoutParams lp = (LayoutParams) viewGroupLayoutParams;
                if (lp.show) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Fields we keep track of inside onMeasure() to correctly measure the SmartReplyView depending
     * on which suggestions are added.
+23 −16
Original line number Diff line number Diff line
@@ -15,15 +15,14 @@
package com.android.systemui.statusbar.policy;

import static android.view.View.MeasureSpec;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Notification;
@@ -45,9 +44,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;

import androidx.test.filters.SmallTest;

import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Flags;
@@ -64,17 +61,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;

import kotlin.sequences.Sequence;
import kotlin.sequences.SequencesKt;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -83,6 +69,14 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import kotlin.sequences.Sequence;
import kotlin.sequences.SequencesKt;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -130,6 +124,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
    private KeyguardStateController mKeyguardStateController;
    @Mock
    private SysuiStatusBarStateController mStatusBarStateController;
    @Mock private SmartReplyLogger mSmartReplyLogger;

    @Before
    public void setUp() {
@@ -156,6 +151,7 @@ public class SmartReplyViewTest extends SysuiTestCase {

        mContainer = new View(mContext, null);
        mView = SmartReplyView.inflate(mContext, mConstants);
        mView.setSmartReplyLogger(mSmartReplyLogger);

        final Resources res = mContext.getResources();
        mSpacing = res.getDimensionPixelSize(R.dimen.smart_reply_button_spacing);
@@ -1403,6 +1399,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
        assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
        assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
        assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
        verify(mSmartReplyLogger).logAnimatedReplyChipVisibleEvent();
        verify(mSmartReplyLogger, never()).logAnimatedActionChipVisibleEvent();
    }

    @Test
@@ -1443,7 +1441,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
        assertAnimatedActionButtonShownWithEqualMeasures(
                expectedView.getChildAt(2), mView.getChildAt(2), 2);
        assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));

        verify(mSmartReplyLogger, never()).logAnimatedReplyChipVisibleEvent();
        verify(mSmartReplyLogger).logAnimatedActionChipVisibleEvent();
    }

    @Test
@@ -1477,6 +1476,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
        assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(1));
        assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(2));
        assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(0));
        verify(mSmartReplyLogger).logAnimatedReplyChipVisibleEvent();
        verify(mSmartReplyLogger, never()).logAnimatedActionChipVisibleEvent();
    }

    @Test
@@ -1510,6 +1511,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
        assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(1));
        assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(2));
        assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(3));
        verify(mSmartReplyLogger).logAnimatedReplyChipVisibleEvent();
        verify(mSmartReplyLogger).logAnimatedActionChipVisibleEvent();
    }

    @Test
@@ -1548,6 +1551,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
        assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
        assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
        assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
        verify(mSmartReplyLogger).logAnimatedReplyChipVisibleEvent();
        verify(mSmartReplyLogger, never()).logAnimatedActionChipVisibleEvent();
    }

    @Test
@@ -1586,5 +1591,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
        assertReplyButtonHidden(mView.getChildAt(0));
        assertReplyButtonHidden(mView.getChildAt(1));
        assertReplyButtonHidden(mView.getChildAt(2));
        verify(mSmartReplyLogger, never()).logAnimatedReplyChipVisibleEvent();
        verify(mSmartReplyLogger).logAnimatedActionChipVisibleEvent();
    }
}