diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index b598335038dcb..3c6805b4e8811 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -21,6 +21,7 @@ import android.content.Context import android.media.MediaRoute2Info import android.view.View import androidx.annotation.StringRes +import com.android.internal.logging.UiEventLogger import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R import com.android.systemui.media.taptotransfer.common.DEFAULT_TIMEOUT_MILLIS @@ -39,6 +40,7 @@ import com.android.systemui.media.taptotransfer.common.DEFAULT_TIMEOUT_MILLIS */ enum class ChipStateSender( @StatusBarManager.MediaTransferSenderState val stateInt: Int, + val uiEvent: UiEventLogger.UiEventEnum, @StringRes val stringResId: Int?, val isMidTransfer: Boolean = false, val isTransferFailure: Boolean = false, @@ -51,6 +53,7 @@ enum class ChipStateSender( */ ALMOST_CLOSE_TO_START_CAST( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST, R.string.media_move_closer_to_start_cast, ), @@ -62,6 +65,7 @@ enum class ChipStateSender( */ ALMOST_CLOSE_TO_END_CAST( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST, R.string.media_move_closer_to_end_cast, ), @@ -71,6 +75,7 @@ enum class ChipStateSender( */ TRANSFER_TO_RECEIVER_TRIGGERED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED, R.string.media_transfer_playing_different_device, isMidTransfer = true, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS @@ -82,6 +87,7 @@ enum class ChipStateSender( */ TRANSFER_TO_THIS_DEVICE_TRIGGERED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED, R.string.media_transfer_playing_this_device, isMidTransfer = true, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS @@ -92,17 +98,22 @@ enum class ChipStateSender( */ TRANSFER_TO_RECEIVER_SUCCEEDED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED, R.string.media_transfer_playing_different_device ) { override fun undoClickListener( controllerSender: MediaTttChipControllerSender, routeInfo: MediaRoute2Info, undoCallback: IUndoMediaTransferCallback?, + uiEventLogger: MediaTttSenderUiEventLogger ): View.OnClickListener? { if (undoCallback == null) { return null } return View.OnClickListener { + uiEventLogger.logUndoClicked( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED + ) undoCallback.onUndoTriggered() // The external service should eventually send us a TransferToThisDeviceTriggered // state, but that may take too long to go through the binder and the user may be @@ -122,17 +133,22 @@ enum class ChipStateSender( */ TRANSFER_TO_THIS_DEVICE_SUCCEEDED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, R.string.media_transfer_playing_this_device ) { override fun undoClickListener( controllerSender: MediaTttChipControllerSender, routeInfo: MediaRoute2Info, undoCallback: IUndoMediaTransferCallback?, + uiEventLogger: MediaTttSenderUiEventLogger ): View.OnClickListener? { if (undoCallback == null) { return null } return View.OnClickListener { + uiEventLogger.logUndoClicked( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED + ) undoCallback.onUndoTriggered() // The external service should eventually send us a TransferToReceiverTriggered // state, but that may take too long to go through the binder and the user may be @@ -150,6 +166,7 @@ enum class ChipStateSender( /** A state representing that a transfer to the receiver device has failed. */ TRANSFER_TO_RECEIVER_FAILED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED, R.string.media_transfer_failed, isTransferFailure = true ), @@ -157,6 +174,7 @@ enum class ChipStateSender( /** A state representing that a transfer back to this device has failed. */ TRANSFER_TO_THIS_DEVICE_FAILED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED, R.string.media_transfer_failed, isTransferFailure = true ), @@ -164,6 +182,7 @@ enum class ChipStateSender( /** A state representing that this device is far away from any receiver device. */ FAR_FROM_RECEIVER( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER, stringResId = null ); @@ -191,7 +210,8 @@ enum class ChipStateSender( open fun undoClickListener( controllerSender: MediaTttChipControllerSender, routeInfo: MediaRoute2Info, - undoCallback: IUndoMediaTransferCallback? + undoCallback: IUndoMediaTransferCallback?, + uiEventLogger: MediaTttSenderUiEventLogger ): View.OnClickListener? = null companion object { diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 3b38946f64832..3e4cf9921bad9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -51,6 +51,7 @@ class MediaTttChipControllerSender @Inject constructor( viewUtil: ViewUtil, @Main mainExecutor: DelayableExecutor, tapGestureDetector: TapGestureDetector, + private val uiEventLogger: MediaTttSenderUiEventLogger ) : MediaTttChipControllerCommon( context, logger, @@ -91,6 +92,7 @@ class MediaTttChipControllerSender @Inject constructor( Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState") return } + uiEventLogger.logSenderStateChange(chipState) if (chipState == ChipStateSender.FAR_FROM_RECEIVER) { removeChip(removalReason = ChipStateSender.FAR_FROM_RECEIVER::class.simpleName!!) @@ -123,7 +125,7 @@ class MediaTttChipControllerSender @Inject constructor( // Undo val undoView = currentChipView.requireViewById(R.id.undo) val undoClickListener = chipState.undoClickListener( - this, chipInfo.routeInfo, chipInfo.undoCallback + this, chipInfo.routeInfo, chipInfo.undoCallback, uiEventLogger ) undoView.setOnClickListener(undoClickListener) undoView.visibility = (undoClickListener != null).visibleIfTrue() diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt new file mode 100644 index 0000000000000..af3c1b60bacfa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022 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.media.taptotransfer.sender + +import android.util.Log +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** A class for analytics logging for the media tap-to-transfer chip on the sender device. */ +@SysUISingleton +class MediaTttSenderUiEventLogger @Inject constructor(private val logger: UiEventLogger) { + /** Logs that the sender chip has changed states. */ + fun logSenderStateChange(chipState: ChipStateSender) { + logger.log(chipState.uiEvent) + } + + /** + * Logs that the undo button was clicked. + * + * @param undoUiEvent the uiEvent specific to which undo button was clicked. + */ + fun logUndoClicked(undoUiEvent: UiEventLogger.UiEventEnum) { + val isUndoEvent = + undoUiEvent == MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED + || undoUiEvent == + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED + if (!isUndoEvent) { + Log.w( + MediaTttSenderUiEventLogger::class.simpleName!!, + "Must pass an undo-specific UiEvent." + ) + return + } + logger.log(undoUiEvent) + } +} + +enum class MediaTttSenderUiEvents(val metricId: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "The undo button on the media ttt chip on the sender device was clicked " + + "to undo the transfer to the receiver device") + MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED(971), + @UiEvent(doc = "The undo button on the media ttt chip on the sender device was clicked " + + "to undo the transfer back to this device") + MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED(972), + + @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_SENDER_* docs") + MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST(973), + @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_SENDER_* docs") + MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST(974), + @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_SENDER_* docs") + MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED(975), + @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_SENDER_* docs") + MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED(976), + @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_SENDER_* docs") + MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED(977), + @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_SENDER_* docs") + MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED(978), + @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_SENDER_* docs") + MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED(979), + @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_SENDER_* docs") + MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED(980), + @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_SENDER_* docs") + MEDIA_TTT_SENDER_FAR_FROM_RECEIVER(981); + + override fun getId() = metricId +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 9a4aa63ca496d..4ac7709071c81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -29,6 +29,7 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import androidx.test.filters.SmallTest +import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -74,6 +75,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { private lateinit var fakeAppIconDrawable: Drawable private lateinit var fakeClock: FakeSystemClock private lateinit var fakeExecutor: FakeExecutor + private lateinit var uiEventLoggerFake: UiEventLoggerFake + private lateinit var senderUiEventLogger: MediaTttSenderUiEventLogger @Before fun setUp() { @@ -89,6 +92,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { fakeClock = FakeSystemClock() fakeExecutor = FakeExecutor(fakeClock) + uiEventLoggerFake = UiEventLoggerFake() + senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake) controllerSender = MediaTttChipControllerSender( commandQueue, @@ -97,7 +102,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { windowManager, viewUtil, fakeExecutor, - TapGestureDetector(context) + TapGestureDetector(context), + senderUiEventLogger ) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) @@ -116,6 +122,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(getChipView().getChipText()).isEqualTo( almostCloseToStartCast().state.getChipTextString(context, OTHER_DEVICE_NAME) ) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id + ) } @Test @@ -129,6 +138,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(getChipView().getChipText()).isEqualTo( almostCloseToEndCast().state.getChipTextString(context, OTHER_DEVICE_NAME) ) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id + ) } @Test @@ -142,6 +154,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(getChipView().getChipText()).isEqualTo( transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME) ) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id + ) } @Test @@ -155,6 +170,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(getChipView().getChipText()).isEqualTo( transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME) ) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id + ) } @Test @@ -168,6 +186,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(getChipView().getChipText()).isEqualTo( transferToReceiverSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME) ) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id + ) } @Test @@ -181,6 +202,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(getChipView().getChipText()).isEqualTo( transferToThisDeviceSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME) ) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id + ) } @Test @@ -194,6 +218,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(getChipView().getChipText()).isEqualTo( transferToReceiverFailed().state.getChipTextString(context, OTHER_DEVICE_NAME) ) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id + ) } @Test @@ -207,6 +234,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(getChipView().getChipText()).isEqualTo( transferToThisDeviceFailed().state.getChipTextString(context, OTHER_DEVICE_NAME) ) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id + ) } @Test @@ -218,6 +248,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { ) verify(windowManager, never()).addView(any(), any()) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER.id + ) } @Test @@ -376,6 +409,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(getChipView().getChipText()).isEqualTo( transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME) ) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id + ) } @Test @@ -440,6 +476,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(getChipView().getChipText()).isEqualTo( transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME) ) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt new file mode 100644 index 0000000000000..263637a6b6e5c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt @@ -0,0 +1,46 @@ +package com.android.systemui.media.taptotransfer.sender + +import androidx.test.filters.SmallTest +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test + +@SmallTest +class MediaTttSenderUiEventLoggerTest : SysuiTestCase() { + private lateinit var uiEventLoggerFake: UiEventLoggerFake + private lateinit var logger: MediaTttSenderUiEventLogger + + @Before + fun setUp () { + uiEventLoggerFake = UiEventLoggerFake() + logger = MediaTttSenderUiEventLogger(uiEventLoggerFake) + } + + @Test + fun logSenderStateChange_eventAssociatedWithStateIsLogged() { + val state = ChipStateSender.ALMOST_CLOSE_TO_END_CAST + logger.logSenderStateChange(state) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(state.uiEvent.id) + } + + @Test + fun logUndoClicked_undoEventLogged() { + val undoEvent = MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED + + logger.logUndoClicked(undoEvent) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(undoEvent.id) + } + + @Test + fun logUndoClicked_notUndoEvent_eventNotLogged() { + logger.logUndoClicked(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0) + } +}