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

Commit ebc0c099 authored by Darrell Shi's avatar Darrell Shi Committed by Android (Google) Code Review
Browse files

Merge "Log communal widgets snapshot" into main

parents 72eb4a3f 4a05f9be
Loading
Loading
Loading
Loading
+138 −0
Original line number Original line 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.communal

import android.app.StatsManager
import android.app.StatsManager.StatsPullAtomCallback
import android.content.pm.UserInfo
import android.platform.test.annotations.EnableFlags
import android.util.StatsEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify

@SmallTest
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
@RunWith(AndroidJUnit4::class)
class CommunalMetricsStartableTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    private val metricsLogger = mock<CommunalMetricsLogger>()
    private val statsManager = mock<StatsManager>()

    private val callbackCaptor = argumentCaptor<StatsPullAtomCallback>()

    private val userTracker = kosmos.fakeUserTracker
    private val userRepository = kosmos.fakeUserRepository
    private val widgetsRepository = kosmos.fakeCommunalWidgetRepository

    private lateinit var underTest: CommunalMetricsStartable

    @Before
    fun setUp() {
        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)

        // Set up an existing user, which is required for widgets to show
        val userInfos = listOf(UserInfo(0, "main", UserInfo.FLAG_MAIN))
        userRepository.setUserInfos(userInfos)
        userTracker.set(
            userInfos = userInfos,
            selectedUserIndex = 0,
        )

        underTest =
            CommunalMetricsStartable(
                kosmos.fakeExecutor,
                kosmos.communalSettingsInteractor,
                kosmos.communalInteractor,
                statsManager,
                metricsLogger,
            )
    }

    @Test
    fun start_communalFlagDisabled_doNotSetPullAtomCallback() {
        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)

        underTest.start()

        verify(statsManager, never()).setPullAtomCallback(anyInt(), anyOrNull(), any(), any())
    }

    @Test
    fun onPullAtom_atomTagDoesNotMatch_pullSkip() {
        underTest.start()

        verify(statsManager)
            .setPullAtomCallback(anyInt(), anyOrNull(), any(), callbackCaptor.capture())
        val callback = callbackCaptor.firstValue

        // Atom tag doesn't match COMMUNAL_HUB_SNAPSHOT
        val result =
            callback.onPullAtom(SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED, mutableListOf())

        assertThat(result).isEqualTo(StatsManager.PULL_SKIP)
    }

    @Test
    fun onPullAtom_atomTagMatches_pullSuccess() =
        testScope.runTest {
            underTest.start()

            verify(statsManager)
                .setPullAtomCallback(anyInt(), anyOrNull(), any(), callbackCaptor.capture())
            val callback = callbackCaptor.firstValue

            // Populate some widgets
            widgetsRepository.addWidget(appWidgetId = 1, componentName = "pkg_1/cls_1")
            widgetsRepository.addWidget(appWidgetId = 2, componentName = "pkg_2/cls_2")

            val statsEvents = mutableListOf<StatsEvent>()
            val result = callback.onPullAtom(SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT, statsEvents)

            verify(metricsLogger)
                .logWidgetsSnapshot(statsEvents, listOf("pkg_1/cls_1", "pkg_2/cls_2"))

            assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS)
        }
}
+26 −0
Original line number Original line Diff line number Diff line
@@ -16,11 +16,13 @@


package com.android.systemui.communal.log
package com.android.systemui.communal.log


import android.util.StatsEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.shared.system.SysUiStatsLog
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Before
import org.junit.Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
@@ -90,4 +92,28 @@ class CommunalMetricsLoggerTest : SysuiTestCase() {
                2,
                2,
            )
            )
    }
    }

    @Test
    fun logWidgetsSnapshot_logOnlyLoggableComponents() {
        val statsEvents = mutableListOf<StatsEvent>()
        underTest.logWidgetsSnapshot(
            statsEvents,
            listOf(
                "com.blue.package/my_test_widget_1",
                "com.green.package/my_test_widget_2",
                "com.red.package/my_test_widget_3",
                "com.yellow.package/my_test_widget_4",
            ),
        )
        verify(statsLogProxy)
            .buildCommunalHubSnapshotStatsEvent(
                componentNames =
                    arrayOf(
                        "com.blue.package/my_test_widget_1",
                        "com.red.package/my_test_widget_3",
                    ),
                widgetCount = 4,
            )
        assertThat(statsEvents).hasSize(1)
    }
}
}
+72 −0
Original line number Original line 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.communal

import android.app.StatsManager
import android.util.StatsEvent
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shared.system.SysUiStatsLog
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking

@SysUISingleton
class CommunalMetricsStartable
@Inject
constructor(
    @Background private val bgExecutor: Executor,
    private val communalSettingsInteractor: CommunalSettingsInteractor,
    private val communalInteractor: CommunalInteractor,
    private val statsManager: StatsManager,
    private val metricsLogger: CommunalMetricsLogger,
) : CoreStartable, StatsManager.StatsPullAtomCallback {
    override fun start() {
        if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
            return
        }

        statsManager.setPullAtomCallback(
            /* atomTag = */ SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT,
            /* metadata = */ null,
            /* executor = */ bgExecutor,
            /* callback = */ this,
        )
    }

    override fun onPullAtom(atomTag: Int, statsEvents: MutableList<StatsEvent>): Int {
        if (atomTag != SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT) {
            return StatsManager.PULL_SKIP
        }

        metricsLogger.logWidgetsSnapshot(
            statsEvents,
            componentNames =
                runBlocking {
                    communalInteractor.widgetContent.first().map {
                        it.componentName.flattenToString()
                    }
                },
        )
        return StatsManager.PULL_SUCCESS
    }
}
+6 −0
Original line number Original line Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.communal.dagger
import com.android.systemui.CoreStartable
import com.android.systemui.CoreStartable
import com.android.systemui.communal.CommunalBackupRestoreStartable
import com.android.systemui.communal.CommunalBackupRestoreStartable
import com.android.systemui.communal.CommunalDreamStartable
import com.android.systemui.communal.CommunalDreamStartable
import com.android.systemui.communal.CommunalMetricsStartable
import com.android.systemui.communal.CommunalOngoingContentStartable
import com.android.systemui.communal.CommunalOngoingContentStartable
import com.android.systemui.communal.CommunalSceneStartable
import com.android.systemui.communal.CommunalSceneStartable
import com.android.systemui.communal.log.CommunalLoggerStartable
import com.android.systemui.communal.log.CommunalLoggerStartable
@@ -59,4 +60,9 @@ interface CommunalStartableModule {
    @IntoMap
    @IntoMap
    @ClassKey(CommunalOngoingContentStartable::class)
    @ClassKey(CommunalOngoingContentStartable::class)
    fun bindCommunalOngoingContentStartable(impl: CommunalOngoingContentStartable): CoreStartable
    fun bindCommunalOngoingContentStartable(impl: CommunalOngoingContentStartable): CoreStartable

    @Binds
    @IntoMap
    @ClassKey(CommunalMetricsStartable::class)
    fun bindCommunalMetricsStartable(impl: CommunalMetricsStartable): CoreStartable
}
}
+32 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.systemui.communal.shared.log
package com.android.systemui.communal.shared.log


import android.util.StatsEvent
import com.android.systemui.communal.dagger.CommunalModule.Companion.LOGGABLE_PREFIXES
import com.android.systemui.communal.dagger.CommunalModule.Companion.LOGGABLE_PREFIXES
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.shared.system.SysUiStatsLog
@@ -55,6 +56,20 @@ constructor(
        )
        )
    }
    }


    /** Logs loggable widgets and the total widget count as a [StatsEvent]. */
    fun logWidgetsSnapshot(
        statsEvents: MutableList<StatsEvent>,
        componentNames: List<String>,
    ) {
        val loggableComponentNames = componentNames.filter { it.isLoggable() }.toTypedArray()
        statsEvents.add(
            statsLogProxy.buildCommunalHubSnapshotStatsEvent(
                componentNames = loggableComponentNames,
                widgetCount = componentNames.size,
            )
        )
    }

    /** Whether the component name matches any of the loggable prefixes. */
    /** Whether the component name matches any of the loggable prefixes. */
    private fun String.isLoggable(): Boolean {
    private fun String.isLoggable(): Boolean {
        return loggablePrefixes.any { loggablePrefix -> startsWith(loggablePrefix) }
        return loggablePrefixes.any { loggablePrefix -> startsWith(loggablePrefix) }
@@ -68,6 +83,12 @@ constructor(
            componentName: String,
            componentName: String,
            rank: Int,
            rank: Int,
        )
        )

        /** Builds a [SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT] stats event. */
        fun buildCommunalHubSnapshotStatsEvent(
            componentNames: Array<String>,
            widgetCount: Int,
        ): StatsEvent
    }
    }
}
}


@@ -86,4 +107,15 @@ class CommunalStatsLogProxyImpl @Inject constructor() : CommunalMetricsLogger.St
            rank,
            rank,
        )
        )
    }
    }

    override fun buildCommunalHubSnapshotStatsEvent(
        componentNames: Array<String>,
        widgetCount: Int,
    ): StatsEvent {
        return SysUiStatsLog.buildStatsEvent(
            SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT,
            componentNames,
            widgetCount,
        )
    }
}
}