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

Commit de797e11 authored by Sunny Shao's avatar Sunny Shao
Browse files

Add the SPA page enter/leave logging metrcis.

- Add the SpaLogProvider and SpaLogData for logging writing.
- Write the SPA page enter/leave logging.

Fixes: 271793388
Bug: 253979024
Test: atest SpaActivityTest SpaLogDataTest MetricsDataModelTest
Change-Id: I0ad5af39ba207ac00d58f6392496effa3adc42f4
parent a5cb399e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider
import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider
import com.android.settings.spa.core.instrumentation.SpaLogProvider
import com.android.settings.spa.development.UsageStatsPageProvider
import com.android.settings.spa.home.HomePageProvider
import com.android.settings.spa.network.NetworkAndInternetPageProvider
@@ -87,4 +88,5 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) {
            ),
        )
    }
    override val logger = SpaLogProvider
}
+21 −0
Original line number Diff line number Diff line
@@ -16,18 +16,29 @@

package com.android.settings.spa

import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
import com.android.settingslib.spa.framework.BrowseActivity
import com.android.settingslib.spa.framework.util.SESSION_BROWSE
import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL
import com.android.settingslib.spa.framework.util.appendSpaParams

class SpaActivity : BrowseActivity() {
    companion object {
        private const val TAG = "SpaActivity"
        @JvmStatic
        fun Context.startSpaActivity(destination: String) {
            val intent = Intent(this, SpaActivity::class.java)
                .appendSpaParams(destination = destination)
            if (isLaunchedFromInternal()) {
                intent.appendSpaParams(sessionName = SESSION_BROWSE)
            } else {
                intent.appendSpaParams(sessionName = SESSION_EXTERNAL)
            }
            startActivity(intent)
        }

@@ -37,5 +48,15 @@ class SpaActivity : BrowseActivity() {
            startSpaActivity("$destinationPrefix/$packageName/${UserHandle.myUserId()}")
            return true
        }

        fun Context.isLaunchedFromInternal(): Boolean {
            var pkg: String? = null
            try {
                pkg = ActivityManager.getService().getLaunchedFromPackage(getActivityToken())
            } catch (e: RemoteException) {
                Log.v(TAG, "Could not talk to activity manager.", e)
            }
            return applicationContext.packageName == pkg
        }
    }
}
+41 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.settings.spa.core.instrumentation

import androidx.annotation.VisibleForTesting

/**
 * This class stores some metrics temporary data. Such as the timestamp of the page enter for
 * calculating the duration time on page.
 */
class MetricsDataModel {
    @VisibleForTesting
    val pageTimeStampList = mutableListOf<PageTimeStamp>()

    fun addTimeStamp(dataItem: PageTimeStamp){
        pageTimeStampList.add(dataItem)
    }

    fun getPageDuration(pageId: String, removed: Boolean = true): String {
        val lastItem = pageTimeStampList.findLast { it.pageId == pageId }
        if (removed && lastItem != null) {
            pageTimeStampList.remove(lastItem)
        }
        return if (lastItem == null) "0"
            else (System.currentTimeMillis() - lastItem.timeStamp).toString()
    }
}
+132 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.settings.spa.core.instrumentation

import android.app.settings.SettingsEnums
import android.os.Bundle
import androidx.annotation.VisibleForTesting
import com.android.settings.core.instrumentation.ElapsedTimeUtils
import com.android.settings.core.instrumentation.SettingsStatsLog
import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SpaLogger
import com.android.settingslib.spa.framework.util.SESSION_BROWSE
import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL
import com.android.settingslib.spa.framework.util.SESSION_SEARCH
import com.android.settingslib.spa.framework.util.SESSION_SLICE
import com.android.settingslib.spa.framework.util.SESSION_UNKNOWN

/**
 * To receive the events from spa framework and logging the these events.
 */
object SpaLogProvider : SpaLogger {
    private val dataModel = MetricsDataModel()

    override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
        when(event) {
            LogEvent.PAGE_ENTER, LogEvent.PAGE_LEAVE ->
                write(SpaLogData(id, event, extraData, dataModel))
            else -> return  //TODO(b/253979024): Will be implemented in subsequent CLs.
        }
    }

    private fun write(data: SpaLogData) {
        with(data) {
            SettingsStatsLog.write(
                SettingsStatsLog.SETTINGS_SPA_REPORTED /* atomName */,
                getSessionType(),
                getPageId(),
                getTarget(),
                getAction(),
                getKey(),
                getValue(),
                getPreValue(),
                getElapsedTime()
            )
        }
    }
}

@VisibleForTesting
class SpaLogData(val id: String, val event: LogEvent,
                         val extraData: Bundle, val dataModel: MetricsDataModel) {

    fun getSessionType(): Int {
        if (!extraData.containsKey(LOG_DATA_SESSION_NAME)) {
            return SettingsEnums.SESSION_UNKNOWN
        }
        val sessionSource = extraData.getString(LOG_DATA_SESSION_NAME)
        return when(sessionSource) {
            SESSION_BROWSE -> SettingsEnums.BROWSE
            SESSION_SEARCH -> SettingsEnums.SEARCH
            SESSION_SLICE -> SettingsEnums.SLICE_TYPE
            SESSION_EXTERNAL -> SettingsEnums.EXTERNAL
            else -> SettingsEnums.SESSION_UNKNOWN
        }
    }

    fun getPageId(): String {
        return when(event) {
            LogEvent.PAGE_ENTER, LogEvent.PAGE_LEAVE -> id
            else -> getPageIdByEntryId(id)
        }
    }

    //TODO(b/253979024): Will be implemented in subsequent CLs.
    fun getTarget(): String? {
        return null
    }

    fun getAction(): Int {
        return event.action
    }

    //TODO(b/253979024): Will be implemented in subsequent CLs.
    fun getKey(): String? {
        return null
    }

    fun getValue(): String? {
        when(event) {
            LogEvent.PAGE_ENTER -> dataModel.addTimeStamp(
                PageTimeStamp(id, System.currentTimeMillis()))
            LogEvent.PAGE_LEAVE -> return dataModel.getPageDuration(id)
            else -> {} //TODO(b/253979024): Will be implemented in subsequent CLs.
        }
        return null
    }

    //TODO(b/253979024): Will be implemented in subsequent CLs.
    fun getPreValue(): String? {
        return null
    }

    fun getElapsedTime(): Long {
        return ElapsedTimeUtils.getElapsedTime(System.currentTimeMillis())
    }

    //TODO(b/253979024): Will be implemented in subsequent CLs.
    private fun getPageIdByEntryId(id: String): String {
        return ""
    }
}

/**
 * The buffer is keeping the time stamp while spa page entering.
 */
data class PageTimeStamp(val pageId: String, val timeStamp: Long)
+9 −1
Original line number Diff line number Diff line
@@ -25,12 +25,15 @@ import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.SpaActivity.Companion.startSpaActivityForApp
import com.android.settingslib.spa.framework.util.KEY_DESTINATION
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule

@@ -39,9 +42,14 @@ class SpaActivityTest {
    @get:Rule
    val mockito: MockitoRule = MockitoJUnit.rule()

    @Mock
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private lateinit var context: Context

    @Before
    fun setUp() {
        `when`(context.applicationContext.packageName).thenReturn("com.android.settings")
    }

    @Test
    fun startSpaActivity() {
        context.startSpaActivity(DESTINATION)
Loading