Merge "Add the SPA page enter/leave logging metrcis." into udc-dev

This commit is contained in:
Sunny Shao
2023-03-07 10:12:55 +00:00
committed by Android (Google) Code Review
7 changed files with 383 additions and 1 deletions

View File

@@ -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.SpecialAppAccessPageProvider
import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider 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.development.UsageStatsPageProvider
import com.android.settings.spa.home.HomePageProvider import com.android.settings.spa.home.HomePageProvider
import com.android.settings.spa.network.NetworkAndInternetPageProvider import com.android.settings.spa.network.NetworkAndInternetPageProvider
@@ -87,4 +88,5 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) {
), ),
) )
} }
override val logger = SpaLogProvider
} }

View File

@@ -16,18 +16,29 @@
package com.android.settings.spa package com.android.settings.spa
import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.RemoteException
import android.os.UserHandle import android.os.UserHandle
import android.util.Log
import com.android.settingslib.spa.framework.BrowseActivity 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 import com.android.settingslib.spa.framework.util.appendSpaParams
class SpaActivity : BrowseActivity() { class SpaActivity : BrowseActivity() {
companion object { companion object {
private const val TAG = "SpaActivity"
@JvmStatic @JvmStatic
fun Context.startSpaActivity(destination: String) { fun Context.startSpaActivity(destination: String) {
val intent = Intent(this, SpaActivity::class.java) val intent = Intent(this, SpaActivity::class.java)
.appendSpaParams(destination = destination) .appendSpaParams(destination = destination)
if (isLaunchedFromInternal()) {
intent.appendSpaParams(sessionName = SESSION_BROWSE)
} else {
intent.appendSpaParams(sessionName = SESSION_EXTERNAL)
}
startActivity(intent) startActivity(intent)
} }
@@ -37,5 +48,15 @@ class SpaActivity : BrowseActivity() {
startSpaActivity("$destinationPrefix/$packageName/${UserHandle.myUserId()}") startSpaActivity("$destinationPrefix/$packageName/${UserHandle.myUserId()}")
return true 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
}
} }
} }

View File

@@ -0,0 +1,41 @@
/*
* 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()
}
}

View File

@@ -0,0 +1,132 @@
/*
* 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)

View File

@@ -25,12 +25,15 @@ import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.SpaActivity.Companion.startSpaActivityForApp import com.android.settings.spa.SpaActivity.Companion.startSpaActivityForApp
import com.android.settingslib.spa.framework.util.KEY_DESTINATION import com.android.settingslib.spa.framework.util.KEY_DESTINATION
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Answers
import org.mockito.ArgumentCaptor import org.mockito.ArgumentCaptor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule import org.mockito.junit.MockitoRule
@@ -39,9 +42,14 @@ class SpaActivityTest {
@get:Rule @get:Rule
val mockito: MockitoRule = MockitoJUnit.rule() val mockito: MockitoRule = MockitoJUnit.rule()
@Mock @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var context: Context private lateinit var context: Context
@Before
fun setUp() {
`when`(context.applicationContext.packageName).thenReturn("com.android.settings")
}
@Test @Test
fun startSpaActivity() { fun startSpaActivity() {
context.startSpaActivity(DESTINATION) context.startSpaActivity(DESTINATION)

View File

@@ -0,0 +1,95 @@
/*
* 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.os.SystemClock
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
/** Tests for {@link MetricsDataModel}. */
@RunWith(AndroidJUnit4::class)
class MetricsDataModelTest {
private val TEST_PID = "pseudo_page_id"
private lateinit var metricsDataModel: MetricsDataModel
@Before
fun setUp() {
metricsDataModel = MetricsDataModel()
}
@Test
fun initMetricsDataModel() {
assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(0)
}
@Test
fun addTimeStamp_addOnePageTimeStamp_sizeShouldBeOne() {
metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis()))
assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(1)
}
@Test
fun addTimeStamp_addTwoSamePageTimeStamp_sizeShouldBeTwo() {
metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis()))
metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis()))
assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(2)
}
@Test
fun getPageDuration_getExistPageId_mustFoundValue() {
metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis()))
SystemClock.sleep(5)
assertThat(metricsDataModel.getPageDuration(TEST_PID).toInt()).isGreaterThan(0)
assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(0)
}
@Test
fun getPageDuration_getNonExistPageId_valueShouldBeZero() {
metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis()))
assertThat(metricsDataModel.getPageDuration("WRONG_ID").toLong()).isEqualTo(0L)
}
@Test
fun getPageDuration_getExistPageIdAndDonotRemoved_sizeShouldBeOne() {
metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis()))
SystemClock.sleep(5)
assertThat(metricsDataModel.getPageDuration(TEST_PID, false).toLong()).isGreaterThan(0L)
assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(1)
}
@Test
fun getPageDuration_getTwoExistPageId_theOrderIsLIFO() {
metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, 10000L))
metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, 20000L))
// The formula is d1 = t1 - 20000, d2 = t2 - 10000
// d2 - d1 = t2 - t1 + 10000, because t2 > t1 the result of d2 - d1 is greater 10000
val duration1 = metricsDataModel.getPageDuration(TEST_PID).toLong()
SystemClock.sleep(5)
val duration2 = metricsDataModel.getPageDuration(TEST_PID).toLong()
assertThat(duration2 - duration1).isGreaterThan(10000L)
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.util.SESSION_BROWSE
import com.android.settingslib.spa.framework.util.SESSION_SEARCH
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
/** Tests for {@link SpaLogData}. */
@RunWith(AndroidJUnit4::class)
class SpaLogDataTest {
private val TEST_PID = "pseudo_page_id"
private lateinit var bundle: Bundle
private lateinit var dataModel: MetricsDataModel
@Before
fun setUp() {
bundle = Bundle()
dataModel = MetricsDataModel()
}
@Test
fun getSessionType_withoutSessionExtraData_returnSessionUnknow() {
val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel)
assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.SESSION_UNKNOWN)
}
@Test
fun getSessionType_hasSessionBrowseExtraData_returnSessionBrowse() {
bundle.putString(LOG_DATA_SESSION_NAME, SESSION_BROWSE)
val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel)
assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.BROWSE)
}
@Test
fun getSessionType_hasSessionSearchExtraData_returnSessionSearch() {
bundle.putString(LOG_DATA_SESSION_NAME, SESSION_SEARCH)
val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel)
assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.SEARCH)
}
@Test
fun getSessionType_hasSessionUnknownExtraData_returnSessionUnknow() {
bundle.putString(LOG_DATA_SESSION_NAME, "SESSION_OTHER")
val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel)
assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.SESSION_UNKNOWN)
}
@Test
fun getPageId_withPageEvent_returnInputId() {
val spaLogData1 = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel)
assertThat(spaLogData1.getPageId()).isEqualTo(TEST_PID)
val spaLogData2 = SpaLogData(TEST_PID, LogEvent.PAGE_LEAVE, bundle, dataModel)
assertThat(spaLogData2.getPageId()).isEqualTo(TEST_PID)
}
}