summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialogEventLogger.kt116
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt141
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt91
5 files changed, 402 insertions, 3 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialogEventLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialogEventLogger.kt
new file mode 100644
index 000000000000..e0f9cc28ca62
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialogEventLogger.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 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.qs.external
+
+import android.app.StatusBarManager
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLoggerImpl
+
+class TileRequestDialogEventLogger @VisibleForTesting constructor(
+ private val uiEventLogger: UiEventLogger,
+ private val instanceIdSequence: InstanceIdSequence
+) {
+ companion object {
+ const val MAX_INSTANCE_ID = 1 shl 20
+ }
+
+ constructor() : this(UiEventLoggerImpl(), InstanceIdSequence(MAX_INSTANCE_ID))
+
+ /**
+ * Obtain a new [InstanceId] to log a session for a dialog request.
+ */
+ fun newInstanceId(): InstanceId = instanceIdSequence.newInstanceId()
+
+ /**
+ * Log that the dialog has been shown to the user for a tile in the given [packageName]. This
+ * call should use a new [instanceId].
+ */
+ fun logDialogShown(packageName: String, instanceId: InstanceId) {
+ uiEventLogger.logWithInstanceId(
+ TileRequestDialogEvent.TILE_REQUEST_DIALOG_SHOWN,
+ /* uid */ 0,
+ packageName,
+ instanceId
+ )
+ }
+
+ /**
+ * Log the user response to the dialog being shown. Must follow a call to [logDialogShown] that
+ * used the same [packageName] and [instanceId]. Only the following responses are valid:
+ * * [StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED]
+ * * [StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED]
+ * * [StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED]
+ */
+ fun logUserResponse(
+ @StatusBarManager.RequestResult response: Int,
+ packageName: String,
+ instanceId: InstanceId
+ ) {
+ val event = when (response) {
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED -> {
+ TileRequestDialogEvent.TILE_REQUEST_DIALOG_DISMISSED
+ }
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED -> {
+ TileRequestDialogEvent.TILE_REQUEST_DIALOG_TILE_NOT_ADDED
+ }
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED -> {
+ TileRequestDialogEvent.TILE_REQUEST_DIALOG_TILE_ADDED
+ }
+ else -> {
+ throw IllegalArgumentException("User response not valid: $response")
+ }
+ }
+ uiEventLogger.logWithInstanceId(event, /* uid */ 0, packageName, instanceId)
+ }
+
+ /**
+ * Log that the dialog will not be shown because the tile was already part of the active set.
+ * Corresponds to a response of [StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED].
+ */
+ fun logTileAlreadyAdded(packageName: String, instanceId: InstanceId) {
+ uiEventLogger.logWithInstanceId(
+ TileRequestDialogEvent.TILE_REQUEST_DIALOG_TILE_ALREADY_ADDED,
+ /* uid */ 0,
+ packageName,
+ instanceId
+ )
+ }
+}
+
+enum class TileRequestDialogEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+
+ @UiEvent(doc = "Tile request dialog not shown because tile is already added.")
+ TILE_REQUEST_DIALOG_TILE_ALREADY_ADDED(917),
+
+ @UiEvent(doc = "Tile request dialog shown to user.")
+ TILE_REQUEST_DIALOG_SHOWN(918),
+
+ @UiEvent(doc = "User dismisses dialog without choosing an option.")
+ TILE_REQUEST_DIALOG_DISMISSED(919),
+
+ @UiEvent(doc = "User accepts adding tile from dialog.")
+ TILE_REQUEST_DIALOG_TILE_ADDED(920),
+
+ @UiEvent(doc = "User denies adding tile from dialog.")
+ TILE_REQUEST_DIALOG_TILE_NOT_ADDED(921);
+
+ override fun getId() = _id
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
index 210ee93bb7ef..73d6b971f785 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -44,6 +44,7 @@ class TileServiceRequestController constructor(
private val qsTileHost: QSTileHost,
private val commandQueue: CommandQueue,
private val commandRegistry: CommandRegistry,
+ private val eventLogger: TileRequestDialogEventLogger,
private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsTileHost.context) }
) {
@@ -97,25 +98,31 @@ class TileServiceRequestController constructor(
icon: Icon?,
callback: Consumer<Int>
) {
+ val instanceId = eventLogger.newInstanceId()
+ val packageName = componentName.packageName
if (isTileAlreadyAdded(componentName)) {
callback.accept(TILE_ALREADY_ADDED)
+ eventLogger.logTileAlreadyAdded(packageName, instanceId)
return
}
val dialogResponse = Consumer<Int> { response ->
if (response == ADD_TILE) {
addTile(componentName)
}
+ dialogCanceller = null
+ eventLogger.logUserResponse(response, packageName, instanceId)
callback.accept(response)
}
val tileData = TileRequestDialog.TileData(appName, label, icon)
createDialog(tileData, dialogResponse).also { dialog ->
dialogCanceller = {
- if (componentName.packageName == it) {
+ if (packageName == it) {
dialog.cancel()
}
dialogCanceller = null
}
}.show()
+ eventLogger.logDialogShown(packageName, instanceId)
}
private fun createDialog(
@@ -168,7 +175,12 @@ class TileServiceRequestController constructor(
private val commandRegistry: CommandRegistry
) {
fun create(qsTileHost: QSTileHost): TileServiceRequestController {
- return TileServiceRequestController(qsTileHost, commandQueue, commandRegistry)
+ return TileServiceRequestController(
+ qsTileHost,
+ commandQueue,
+ commandRegistry,
+ TileRequestDialogEventLogger()
+ )
}
}
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt b/packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt
new file mode 100644
index 000000000000..6fbe3ada2406
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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
+
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+
+/**
+ * Fake [InstanceId] generator.
+ */
+class InstanceIdSequenceFake(instanceIdMax: Int) : InstanceIdSequence(instanceIdMax) {
+
+ /**
+ * Last id used to generate a [InstanceId]. `-1` if no [InstanceId] has been generated.
+ */
+ var lastInstanceId = -1
+ private set
+
+ override fun newInstanceId(): InstanceId {
+ if (lastInstanceId == -1 || lastInstanceId == mInstanceIdMax - 1) {
+ lastInstanceId = 1
+ } else {
+ lastInstanceId++
+ }
+ return newInstanceIdInternal(lastInstanceId)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
new file mode 100644
index 000000000000..64796f1a757a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2021 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.qs.external
+
+import android.app.StatusBarManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.InstanceIdSequenceFake
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class TileRequestDialogEventLoggerTest : SysuiTestCase() {
+
+ companion object {
+ private const val PACKAGE_NAME = "package"
+ }
+
+ private lateinit var uiEventLogger: UiEventLoggerFake
+ private val instanceIdSequence =
+ InstanceIdSequenceFake(TileRequestDialogEventLogger.MAX_INSTANCE_ID)
+ private lateinit var logger: TileRequestDialogEventLogger
+
+ @Before
+ fun setUp() {
+ uiEventLogger = UiEventLoggerFake()
+
+ logger = TileRequestDialogEventLogger(uiEventLogger, instanceIdSequence)
+ }
+
+ @Test
+ fun testInstanceIdsFromSequence() {
+ (1..10).forEach {
+ assertThat(logger.newInstanceId().id).isEqualTo(instanceIdSequence.lastInstanceId)
+ }
+ }
+
+ @Test
+ fun testLogTileAlreadyAdded() {
+ val instanceId = instanceIdSequence.newInstanceId()
+ logger.logTileAlreadyAdded(PACKAGE_NAME, instanceId)
+
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ uiEventLogger[0].match(
+ TileRequestDialogEvent.TILE_REQUEST_DIALOG_TILE_ALREADY_ADDED,
+ instanceId
+ )
+ }
+
+ @Test
+ fun testLogDialogShown() {
+ val instanceId = instanceIdSequence.newInstanceId()
+ logger.logDialogShown(PACKAGE_NAME, instanceId)
+
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ uiEventLogger[0].match(TileRequestDialogEvent.TILE_REQUEST_DIALOG_SHOWN, instanceId)
+ }
+
+ @Test
+ fun testLogDialogDismissed() {
+ val instanceId = instanceIdSequence.newInstanceId()
+ logger.logUserResponse(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED,
+ PACKAGE_NAME,
+ instanceId
+ )
+
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ uiEventLogger[0].match(TileRequestDialogEvent.TILE_REQUEST_DIALOG_DISMISSED, instanceId)
+ }
+
+ @Test
+ fun testLogDialogTileNotAdded() {
+ val instanceId = instanceIdSequence.newInstanceId()
+ logger.logUserResponse(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
+ PACKAGE_NAME,
+ instanceId
+ )
+
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ uiEventLogger[0]
+ .match(TileRequestDialogEvent.TILE_REQUEST_DIALOG_TILE_NOT_ADDED, instanceId)
+ }
+
+ @Test
+ fun testLogDialogTileAdded() {
+ val instanceId = instanceIdSequence.newInstanceId()
+ logger.logUserResponse(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED,
+ PACKAGE_NAME,
+ instanceId
+ )
+
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ uiEventLogger[0].match(TileRequestDialogEvent.TILE_REQUEST_DIALOG_TILE_ADDED, instanceId)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testLogResponseInvalid_throws() {
+ val instanceId = instanceIdSequence.newInstanceId()
+ logger.logUserResponse(
+ -1,
+ PACKAGE_NAME,
+ instanceId
+ )
+ }
+
+ private fun UiEventLoggerFake.FakeUiEvent.match(
+ event: UiEventLogger.UiEventEnum,
+ instanceId: InstanceId
+ ) {
+ assertThat(eventId).isEqualTo(event.id)
+ assertThat(uid).isEqualTo(0)
+ assertThat(packageName).isEqualTo(PACKAGE_NAME)
+ assertThat(this.instanceId).isEqualTo(instanceId)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
index 70e971cdbf32..a1c60a648de9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
@@ -16,18 +16,22 @@
package com.android.systemui.qs.external
+import android.app.StatusBarManager
import android.content.ComponentName
import android.content.DialogInterface
import android.graphics.drawable.Icon
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
import com.android.internal.statusbar.IAddTileResultCallback
+import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.qs.QSTileHost
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -63,18 +67,28 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Mock
private lateinit var commandQueue: CommandQueue
@Mock
+ private lateinit var logger: TileRequestDialogEventLogger
+ @Mock
private lateinit var icon: Icon
+ private val instanceIdSequence = InstanceIdSequenceFake(1_000)
private lateinit var controller: TileServiceRequestController
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ `when`(logger.newInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
+
// Tile not present by default
`when`(qsTileHost.indexOf(anyString())).thenReturn(-1)
- controller = TileServiceRequestController(qsTileHost, commandQueue, commandRegistry) {
+ controller = TileServiceRequestController(
+ qsTileHost,
+ commandQueue,
+ commandRegistry,
+ logger
+ ) {
tileRequestDialog
}
@@ -102,6 +116,17 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
}
@Test
+ fun tileAlreadyAdded_logged() {
+ `when`(qsTileHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
+
+ controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+
+ verify(logger).logTileAlreadyAdded(eq<String>(TEST_COMPONENT.packageName), any())
+ verify(logger, never()).logDialogShown(anyString(), any())
+ verify(logger, never()).logUserResponse(anyInt(), anyString(), any())
+ }
+
+ @Test
fun showAllUsers_set() {
controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
verify(tileRequestDialog).setShowForAllUsers(true)
@@ -114,6 +139,13 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
}
@Test
+ fun dialogShown_logged() {
+ controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+
+ verify(logger).logDialogShown(eq<String>(TEST_COMPONENT.packageName), any())
+ }
+
+ @Test
fun cancelListener_dismissResult() {
val cancelListenerCaptor =
ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
@@ -128,6 +160,25 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
}
@Test
+ fun dialogCancelled_logged() {
+ val cancelListenerCaptor =
+ ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+
+ controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
+
+ verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
+ verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
+
+ cancelListenerCaptor.value.onCancel(tileRequestDialog)
+ verify(logger).logUserResponse(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED,
+ TEST_COMPONENT.packageName,
+ instanceId
+ )
+ }
+
+ @Test
fun positiveActionListener_tileAddedResult() {
val clickListenerCaptor =
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
@@ -143,6 +194,25 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
}
@Test
+ fun tileAdded_logged() {
+ val clickListenerCaptor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+
+ controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
+
+ verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
+ verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
+
+ clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
+ verify(logger).logUserResponse(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED,
+ TEST_COMPONENT.packageName,
+ instanceId
+ )
+ }
+
+ @Test
fun negativeActionListener_tileNotAddedResult() {
val clickListenerCaptor =
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
@@ -158,6 +228,25 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
}
@Test
+ fun tileNotAdded_logged() {
+ val clickListenerCaptor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+
+ controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
+
+ verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
+ verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
+
+ clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
+ verify(logger).logUserResponse(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
+ TEST_COMPONENT.packageName,
+ instanceId
+ )
+ }
+
+ @Test
fun commandQueueCallback_registered() {
verify(commandQueue).addCallback(any())
}