Add falsing to undo button

Add falsing to undo button in Media TTT view for sender.
When it appears on lock screen, we prevent unintended taps by user.
Delayed the creation of Falsing Manager to overcome the regression issue
happened in ag/19584050.

Bug: 244330924
Bug: 236956557
Test: atest MediaTttChipControllerSenderTest
Change-Id: I4d65d9a4c1e9d94a7720615be9d3a9693e04efd5
This commit is contained in:
Michael Mikhail
2022-09-15 18:04:18 +00:00
parent 335ea33268
commit 6bb9c49d4d
5 changed files with 127 additions and 8 deletions

View File

@@ -16,7 +16,7 @@
<!-- Wrap in a frame layout so that we can update the margins on the inner layout. (Since this view
is the root view of a window, we cannot change the root view's margins.) -->
<!-- Alphas start as 0 because the view will be animated in. -->
<FrameLayout
<com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/media_ttt_sender_chip"
@@ -97,4 +97,4 @@
/>
</LinearLayout>
</FrameLayout>
</com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView>

View File

@@ -25,6 +25,7 @@ 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.plugins.FalsingManager
import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
/**
@@ -107,12 +108,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
)
@@ -143,12 +147,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
)
@@ -215,7 +222,8 @@ enum class ChipStateSender(
controllerSender: MediaTttChipControllerSender,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
uiEventLogger: MediaTttSenderUiEventLogger
uiEventLogger: MediaTttSenderUiEventLogger,
falsingManager: FalsingManager,
): View.OnClickListener? = null
companion object {

View File

@@ -22,25 +22,30 @@ 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.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import javax.inject.Inject
/**
@@ -57,7 +62,11 @@ class MediaTttChipControllerSender @Inject constructor(
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
powerManager: PowerManager,
private val uiEventLogger: MediaTttSenderUiEventLogger
private val uiEventLogger: MediaTttSenderUiEventLogger,
// Added Lazy<> to delay the time we create Falsing instances.
// And overcome performance issue, check [b/247817628] for details.
private val falsingManager: Lazy<FalsingManager>,
private val falsingCollector: Lazy<FalsingCollector>,
) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
context,
logger,
@@ -70,6 +79,9 @@ class MediaTttChipControllerSender @Inject constructor(
MediaTttUtils.WINDOW_TITLE,
MediaTttUtils.WAKE_REASON,
) {
private lateinit var parent: MediaTttChipRootView
override val windowLayoutParams = commonWindowLayoutParams.apply {
gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
}
@@ -120,6 +132,15 @@ class MediaTttChipControllerSender @Inject constructor(
val chipState = newInfo.state
// Detect falsing touches on the chip.
parent = currentView.requireViewById(R.id.media_ttt_sender_chip)
parent.touchHandler = object : Gefingerpoken {
override fun onTouchEvent(ev: MotionEvent?): Boolean {
falsingCollector.get().onTouchEvent(ev)
return false
}
}
// App icon
val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context, newInfo.routeInfo.clientPackageName, logger
@@ -142,7 +163,11 @@ class MediaTttChipControllerSender @Inject constructor(
// Undo
val undoView = currentView.requireViewById<View>(R.id.undo)
val undoClickListener = chipState.undoClickListener(
this, newInfo.routeInfo, newInfo.undoCallback, uiEventLogger
this,
newInfo.routeInfo,
newInfo.undoCallback,
uiEventLogger,
falsingManager.get(),
)
undoView.setOnClickListener(undoClickListener)
undoView.visibility = (undoClickListener != null).visibleIfTrue()

View File

@@ -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)
}
}

View File

@@ -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
@@ -43,10 +45,12 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
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
@@ -75,6 +79,14 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
private lateinit var windowManager: WindowManager
@Mock
private lateinit var commandQueue: CommandQueue
@Mock
private lateinit var lazyFalsingManager: Lazy<FalsingManager>
@Mock
private lateinit var falsingManager: FalsingManager
@Mock
private lateinit var lazyFalsingCollector: Lazy<FalsingCollector>
@Mock
private lateinit var falsingCollector: FalsingCollector
private lateinit var commandQueueCallback: CommandQueue.Callbacks
private lateinit var fakeAppIconDrawable: Drawable
private lateinit var fakeClock: FakeSystemClock
@@ -101,6 +113,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
whenever(lazyFalsingManager.get()).thenReturn(falsingManager)
whenever(lazyFalsingCollector.get()).thenReturn(falsingCollector)
controllerSender = MediaTttChipControllerSender(
commandQueue,
@@ -111,7 +125,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
accessibilityManager,
configurationController,
powerManager,
senderUiEventLogger
senderUiEventLogger,
lazyFalsingManager,
lazyFalsingCollector
)
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
@@ -416,6 +432,38 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
assertThat(undoCallbackCalled).isTrue()
}
@Test
fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(true)
var undoCallbackCalled = false
val undoCallback = object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {
undoCallbackCalled = true
}
}
controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
getChipView().getUndoButton().performClick()
assertThat(undoCallbackCalled).isFalse()
}
@Test
fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(false)
var undoCallbackCalled = false
val undoCallback = object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {
undoCallbackCalled = true
}
}
controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
getChipView().getUndoButton().performClick()
assertThat(undoCallbackCalled).isTrue()
}
@Test
fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
val undoCallback = object : IUndoMediaTransferCallback.Stub() {