diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml index d88680669fe01..ae8e38e2634b0 100644 --- a/packages/SystemUI/res/layout/media_ttt_chip.xml +++ b/packages/SystemUI/res/layout/media_ttt_chip.xml @@ -16,7 +16,7 @@ - - + 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 a153cb6c0d31b..f93c671f67402 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 @@ -26,6 +26,7 @@ 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 +import com.android.systemui.plugins.FalsingManager /** * A class enumerating all the possible states of the media tap-to-transfer chip on the sender @@ -106,12 +107,15 @@ enum class ChipStateSender( controllerSender: MediaTttChipControllerSender, routeInfo: MediaRoute2Info, undoCallback: IUndoMediaTransferCallback?, - uiEventLogger: MediaTttSenderUiEventLogger + uiEventLogger: MediaTttSenderUiEventLogger, + falsingManager: FalsingManager, ): View.OnClickListener? { if (undoCallback == null) { return null } return View.OnClickListener { + if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener + uiEventLogger.logUndoClicked( MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED ) @@ -141,12 +145,15 @@ enum class ChipStateSender( controllerSender: MediaTttChipControllerSender, routeInfo: MediaRoute2Info, undoCallback: IUndoMediaTransferCallback?, - uiEventLogger: MediaTttSenderUiEventLogger + uiEventLogger: MediaTttSenderUiEventLogger, + falsingManager: FalsingManager, ): View.OnClickListener? { if (undoCallback == null) { return null } return View.OnClickListener { + if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener + uiEventLogger.logUndoClicked( MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED ) @@ -212,7 +219,8 @@ enum class ChipStateSender( controllerSender: MediaTttChipControllerSender, routeInfo: MediaRoute2Info, undoCallback: IUndoMediaTransferCallback?, - uiEventLogger: MediaTttSenderUiEventLogger + uiEventLogger: MediaTttSenderUiEventLogger, + falsingManager: FalsingManager, ): 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 b94b8bfabfc11..ab0f6cc543aab 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 @@ -22,21 +22,25 @@ import android.media.MediaRoute2Info import android.os.PowerManager import android.util.Log import android.view.Gravity +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager import android.widget.TextView import com.android.internal.statusbar.IUndoMediaTransferCallback +import com.android.systemui.Gefingerpoken import com.android.systemui.R import com.android.systemui.animation.Interpolators import com.android.systemui.animation.ViewHierarchyAnimator +import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.ChipInfoCommon import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.media.taptotransfer.common.MediaTttRemovalReason +import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor @@ -58,7 +62,9 @@ class MediaTttChipControllerSender @Inject constructor( accessibilityManager: AccessibilityManager, configurationController: ConfigurationController, powerManager: PowerManager, - private val uiEventLogger: MediaTttSenderUiEventLogger + private val uiEventLogger: MediaTttSenderUiEventLogger, + private val falsingManager: FalsingManager, + private val falsingCollector: FalsingCollector, ) : MediaTttChipControllerCommon( context, logger, @@ -70,6 +76,9 @@ class MediaTttChipControllerSender @Inject constructor( powerManager, R.layout.media_ttt_chip, ) { + + private lateinit var parent: MediaTttChipRootView + override val windowLayoutParams = commonWindowLayoutParams.apply { gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) } @@ -121,6 +130,15 @@ class MediaTttChipControllerSender @Inject constructor( val chipState = newChipInfo.state + // Detect falsing touches on the chip. + parent = currentChipView as MediaTttChipRootView + parent.touchHandler = object : Gefingerpoken { + override fun onTouchEvent(ev: MotionEvent?): Boolean { + falsingCollector.onTouchEvent(ev) + return false + } + } + // App icon val iconName = setIcon(currentChipView, newChipInfo.routeInfo.packageName) @@ -136,7 +154,11 @@ class MediaTttChipControllerSender @Inject constructor( // Undo val undoView = currentChipView.requireViewById(R.id.undo) val undoClickListener = chipState.undoClickListener( - this, newChipInfo.routeInfo, newChipInfo.undoCallback, uiEventLogger + this, + newChipInfo.routeInfo, + newChipInfo.undoCallback, + uiEventLogger, + falsingManager, ) undoView.setOnClickListener(undoClickListener) undoView.visibility = (undoClickListener != null).visibleIfTrue() diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt new file mode 100644 index 0000000000000..3373159fba4e6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt @@ -0,0 +1,38 @@ +/* + * 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.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import android.widget.FrameLayout +import com.android.systemui.Gefingerpoken + +/** A simple subclass that allows for observing touch events on chip. */ +class MediaTttChipRootView( + context: Context, + attrs: AttributeSet? +) : FrameLayout(context, attrs) { + + /** Assign this field to observe touch events. */ + var touchHandler: Gefingerpoken? = null + + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { + touchHandler?.onTouchEvent(ev) + return super.dispatchTouchEvent(ev) + } +} 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 cd8ee732e1132..b177e1b517b46 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 @@ -35,7 +35,9 @@ import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector import com.android.systemui.media.taptotransfer.common.MediaTttLogger +import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor @@ -48,11 +50,12 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @@ -78,6 +81,10 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { private lateinit var viewUtil: ViewUtil @Mock private lateinit var commandQueue: CommandQueue + @Mock + private lateinit var falsingManager: FalsingManager + @Mock + private lateinit var falsingCollector: FalsingCollector private lateinit var commandQueueCallback: CommandQueue.Callbacks private lateinit var fakeAppIconDrawable: Drawable private lateinit var fakeClock: FakeSystemClock @@ -115,7 +122,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { accessibilityManager, configurationController, powerManager, - senderUiEventLogger + senderUiEventLogger, + falsingManager, + falsingCollector ) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) @@ -420,6 +429,38 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(undoCallbackCalled).isTrue() } + @Test + fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() { + whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true) + var undoCallbackCalled = false + val undoCallback = object : IUndoMediaTransferCallback.Stub() { + override fun onUndoTriggered() { + undoCallbackCalled = true + } + } + + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) + getChipView().getUndoButton().performClick() + + assertThat(undoCallbackCalled).isFalse() + } + + @Test + fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() { + whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false) + var undoCallbackCalled = false + val undoCallback = object : IUndoMediaTransferCallback.Stub() { + override fun onUndoTriggered() { + undoCallbackCalled = true + } + } + + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) + getChipView().getUndoButton().performClick() + + assertThat(undoCallbackCalled).isTrue() + } + @Test fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() { val undoCallback = object : IUndoMediaTransferCallback.Stub() {