diff options
| author | 2022-10-04 21:01:29 +0000 | |
|---|---|---|
| committer | 2022-10-07 16:53:04 +0000 | |
| commit | cd5a7d1a326172407dc74880f2e5226a057310fc (patch) | |
| tree | 9ada81638940973db2e466094bed22673264fa8c | |
| parent | 7d9b0e622e2f9574e5bcafd15dddef6a465f5c8b (diff) | |
[Media TTT] Set specific touchable regions for the media chips so that
other touches will get correctly passed through to the window below.
Fixes: 249639836
Fixes: 249632655
Test: manual: On lockscreen, display media sender chip then verify you
can swipe down to open QS
Test: manual: On homescreen, display media receiver chip then verify you
can swipe up to see recent apps
Test: manual: verify you can still click the Undo button on the
"Transfer succeeded" chip
Test: atest SystemUITests
Change-Id: Idce510fc5b4e0c5ccd74d2b56e1d23db183ebe69
10 files changed, 272 insertions, 6 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 8fc5519cc73e..04d66af038b4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.taptotransfer.receiver import android.annotation.SuppressLint import android.app.StatusBarManager import android.content.Context +import android.graphics.Rect import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.media.MediaRoute2Info @@ -44,6 +45,7 @@ import com.android.systemui.temporarydisplay.TemporaryViewDisplayController import com.android.systemui.temporarydisplay.TemporaryViewInfo import com.android.systemui.util.animation.AnimationUtil.Companion.frames import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.view.ViewUtil import javax.inject.Inject /** @@ -63,6 +65,7 @@ class MediaTttChipControllerReceiver @Inject constructor( powerManager: PowerManager, @Main private val mainHandler: Handler, private val uiEventLogger: MediaTttReceiverUiEventLogger, + private val viewUtil: ViewUtil, ) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>( context, logger, @@ -83,7 +86,6 @@ class MediaTttChipControllerReceiver @Inject constructor( height = WindowManager.LayoutParams.MATCH_PARENT layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS fitInsetsTypes = 0 // Ignore insets from all system bars - flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE } private val commandQueueCallbacks = object : CommandQueue.Callbacks { @@ -154,14 +156,14 @@ class MediaTttChipControllerReceiver @Inject constructor( context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding) } - val iconView = currentView.requireViewById<CachingIconView>(R.id.app_icon) + val iconView = currentView.getAppIconView() iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding) iconView.setImageDrawable(iconDrawable) iconView.contentDescription = iconContentDescription } override fun animateViewIn(view: ViewGroup) { - val appIconView = view.requireViewById<View>(R.id.app_icon) + val appIconView = view.getAppIconView() appIconView.animate() .translationYBy(-1 * getTranslationAmount().toFloat()) .setDuration(30.frames) @@ -175,6 +177,12 @@ class MediaTttChipControllerReceiver @Inject constructor( startRipple(view.requireViewById(R.id.ripple)) } + override fun getTouchableRegion(view: View, outRect: Rect) { + // Even though the app icon view isn't touchable, users might think it is. So, use it as the + // touchable region to ensure that touches don't get passed to the window below. + viewUtil.setRectToViewWindowLocation(view.getAppIconView(), outRect) + } + /** Returns the amount that the chip will be translated by in its intro animation. */ private fun getTranslationAmount(): Int { return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation) @@ -212,6 +220,10 @@ class MediaTttChipControllerReceiver @Inject constructor( val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent) rippleView.setColor(color, 70) } + + private fun View.getAppIconView(): CachingIconView { + return this.requireViewById(R.id.app_icon) + } } data class ChipReceiverInfo( 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 11c55285c00b..56e3333f8c9b 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 @@ -18,6 +18,7 @@ package com.android.systemui.media.taptotransfer.sender import android.app.StatusBarManager import android.content.Context +import android.graphics.Rect import android.media.MediaRoute2Info import android.os.PowerManager import android.util.Log @@ -46,6 +47,7 @@ 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 com.android.systemui.util.view.ViewUtil import dagger.Lazy import javax.inject.Inject @@ -68,6 +70,7 @@ open class MediaTttChipControllerSender @Inject constructor( // And overcome performance issue, check [b/247817628] for details. private val falsingManager: Lazy<FalsingManager>, private val falsingCollector: Lazy<FalsingCollector>, + private val viewUtil: ViewUtil, ) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>( context, logger, @@ -224,6 +227,10 @@ open class MediaTttChipControllerSender @Inject constructor( return false } + override fun getTouchableRegion(view: View, outRect: Rect) { + viewUtil.setRectToViewWindowLocation(view, outRect) + } + private fun Boolean.visibleIfTrue(): Int { return if (this) { View.VISIBLE diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index 91e20ee30976..132d45d0fb18 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -20,10 +20,12 @@ import android.annotation.LayoutRes import android.annotation.SuppressLint import android.content.Context import android.graphics.PixelFormat +import android.graphics.Rect import android.graphics.drawable.Drawable import android.os.PowerManager import android.os.SystemClock import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager @@ -70,7 +72,8 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora width = WindowManager.LayoutParams.WRAP_CONTENT height = WindowManager.LayoutParams.WRAP_CONTENT type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY - flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL title = windowTitle format = PixelFormat.TRANSLUCENT setTrustedOverlay() @@ -87,9 +90,15 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora /** The view currently being displayed. Null if the view is not being displayed. */ private var view: ViewGroup? = null + /** The view controller for [view]. Null if the view is not being displayed. */ + private var viewController: TouchableRegionViewController? = null + /** The info currently being displayed. Null if the view is not being displayed. */ internal var info: T? = null + // TODO(b/245610654): We should probably group [view], [viewController], and [info] together + // into one object since they're either all null or all non-null. + /** A [Runnable] that, when run, will cancel the pending timeout of the view. */ private var cancelViewTimeout: Runnable? = null @@ -141,6 +150,11 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora .from(context) .inflate(viewLayoutRes, null) as ViewGroup view = newView + + val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion) + newViewController.init() + viewController = newViewController + updateView(newInfo, newView) windowManager.addView(newView, windowLayoutParams) animateViewIn(newView) @@ -181,6 +195,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora // that if a new view event comes in while this view is animating out, we still display the // new view appropriately. view = null + viewController = null info = null // No need to time the view out since it's already gone cancelViewTimeout?.run() @@ -202,6 +217,12 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora } /** + * Fills [outRect] with the touchable region of this view. This will be used by WindowManager + * to decide which touch events go to the view. + */ + abstract fun getTouchableRegion(view: View, outRect: Rect) + + /** * A method that can be implemented by subclasses to do custom animations for when the view * appears. */ diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TouchableRegionViewController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TouchableRegionViewController.kt new file mode 100644 index 000000000000..60241a9684d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TouchableRegionViewController.kt @@ -0,0 +1,57 @@ +/* + * 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.temporarydisplay + +import android.graphics.Rect +import android.view.View +import android.view.ViewTreeObserver +import com.android.systemui.util.ViewController + +/** + * A view controller that will notify the [ViewTreeObserver] about the touchable region for this + * view. This will be used by WindowManager to decide which touch events go to the view and which + * pass through to the window below. + * + * @param touchableRegionSetter a function that, given the view and an out rect, fills the rect with + * the touchable region of this view. + */ +class TouchableRegionViewController( + view: View, + touchableRegionSetter: (View, Rect) -> Unit, +) : ViewController<View>(view) { + + private val tempRect = Rect() + + private val internalInsetsListener = + ViewTreeObserver.OnComputeInternalInsetsListener { inoutInfo -> + inoutInfo.setTouchableInsets( + ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION + ) + + tempRect.setEmpty() + touchableRegionSetter.invoke(mView, tempRect) + inoutInfo.touchableRegion.set(tempRect) + } + + public override fun onViewAttached() { + mView.viewTreeObserver.addOnComputeInternalInsetsListener(internalInsetsListener) + } + + public override fun onViewDetached() { + mView.viewTreeObserver.removeOnComputeInternalInsetsListener(internalInsetsListener) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt b/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt index 613a797f020c..6160b00379ef 100644 --- a/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt +++ b/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt @@ -1,5 +1,22 @@ +/* + * 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.util.view +import android.graphics.Rect import android.view.View import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject @@ -23,4 +40,22 @@ class ViewUtil @Inject constructor() { top <= y && y <= top + view.height } + + /** + * Sets [outRect] to be the view's location within its window. + */ + fun setRectToViewWindowLocation(view: View, outRect: Rect) { + val locInWindow = IntArray(2) + view.getLocationInWindow(locInWindow) + + val x = locInWindow[0] + val y = locInWindow[1] + + outRect.set( + x, + y, + x + view.width, + y + view.height, + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 775dc11f6edd..f7b30919738c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.util.view.ViewUtil import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -71,6 +72,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Mock private lateinit var powerManager: PowerManager @Mock + private lateinit var viewUtil: ViewUtil + @Mock private lateinit var windowManager: WindowManager @Mock private lateinit var commandQueue: CommandQueue @@ -104,7 +107,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { configurationController, powerManager, Handler.getMain(), - receiverUiEventLogger + receiverUiEventLogger, + viewUtil, ) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) 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 eca3bedee108..213b74a6e8a3 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 @@ -47,6 +47,7 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.util.view.ViewUtil import com.google.common.truth.Truth.assertThat import dagger.Lazy import org.junit.Before @@ -90,6 +91,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { private lateinit var lazyFalsingCollector: Lazy<FalsingCollector> @Mock private lateinit var falsingCollector: FalsingCollector + @Mock + private lateinit var viewUtil: ViewUtil private lateinit var commandQueueCallback: CommandQueue.Callbacks private lateinit var fakeAppIconDrawable: Drawable private lateinit var fakeClock: FakeSystemClock @@ -130,7 +133,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { powerManager, senderUiEventLogger, lazyFalsingManager, - lazyFalsingCollector + lazyFalsingCollector, + viewUtil, ) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) @@ -837,6 +841,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { uiEventLogger: MediaTttSenderUiEventLogger, falsingManager: Lazy<FalsingManager>, falsingCollector: Lazy<FalsingCollector>, + viewUtil: ViewUtil, ) : MediaTttChipControllerSender( commandQueue, context, @@ -849,6 +854,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { uiEventLogger, falsingManager, falsingCollector, + viewUtil, ) { override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { // Just bypass the animation in tests diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt index 7cb28068fe6c..245ab63b3051 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt @@ -17,7 +17,9 @@ package com.android.systemui.temporarydisplay import android.content.Context +import android.graphics.Rect import android.os.PowerManager +import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager @@ -266,6 +268,10 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { override fun shouldIgnoreViewRemoval(removalReason: String): Boolean { return shouldIgnoreViewRemoval } + + override fun getTouchableRegion(view: View, outRect: Rect) { + outRect.setEmpty() + } } inner class ViewInfo(val name: String) : TemporaryViewInfo { diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt new file mode 100644 index 000000000000..7586fe48b308 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt @@ -0,0 +1,81 @@ +/* + * 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.temporarydisplay + +import android.graphics.Rect +import android.view.View +import android.view.ViewTreeObserver +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.any +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +class TouchableRegionViewControllerTest : SysuiTestCase() { + + @Mock private lateinit var view: View + @Mock private lateinit var viewTreeObserver: ViewTreeObserver + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(view.viewTreeObserver).thenReturn(viewTreeObserver) + } + + @Test + fun viewAttached_listenerAdded() { + val controller = TouchableRegionViewController(view) { _, _ -> } + + controller.onViewAttached() + + verify(viewTreeObserver).addOnComputeInternalInsetsListener(any()) + } + + @Test + fun viewDetached_listenerRemoved() { + val controller = TouchableRegionViewController(view) { _, _ -> } + + controller.onViewDetached() + + verify(viewTreeObserver).removeOnComputeInternalInsetsListener(any()) + } + + @Test + fun listener_usesPassedInFunction() { + val controller = + TouchableRegionViewController(view) { _, outRect -> outRect.set(1, 2, 3, 4) } + + controller.onViewAttached() + + val captor = + ArgumentCaptor.forClass(ViewTreeObserver.OnComputeInternalInsetsListener::class.java) + verify(viewTreeObserver).addOnComputeInternalInsetsListener(captor.capture()) + val listener = captor.value!! + + val inoutInfo = ViewTreeObserver.InternalInsetsInfo() + listener.onComputeInternalInsets(inoutInfo) + + assertThat(inoutInfo.touchableRegion.bounds).isEqualTo(Rect(1, 2, 3, 4)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt index dead1592992d..e3cd9b2d6eaf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt @@ -1,12 +1,31 @@ +/* + * 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.util.view +import android.graphics.Rect import android.view.View import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.mockito.Mockito.doAnswer import org.mockito.Mockito.spy import org.mockito.Mockito.`when` @@ -25,6 +44,12 @@ class ViewUtilTest : SysuiTestCase() { location[0] = VIEW_LEFT location[1] = VIEW_TOP `when`(view.locationOnScreen).thenReturn(location) + doAnswer { invocation -> + val pos = invocation.arguments[0] as IntArray + pos[0] = VIEW_LEFT + pos[1] = VIEW_TOP + null + }.`when`(view).getLocationInWindow(any()) } @Test @@ -64,6 +89,18 @@ class ViewUtilTest : SysuiTestCase() { fun touchIsWithinView_yTooLarge_returnsFalse() { assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT + 1f, VIEW_BOTTOM + 1f)).isFalse() } + + @Test + fun setRectToViewWindowLocation_rectHasLocation() { + val outRect = Rect() + + viewUtil.setRectToViewWindowLocation(view, outRect) + + assertThat(outRect.left).isEqualTo(VIEW_LEFT) + assertThat(outRect.right).isEqualTo(VIEW_RIGHT) + assertThat(outRect.top).isEqualTo(VIEW_TOP) + assertThat(outRect.bottom).isEqualTo(VIEW_BOTTOM) + } } private const val VIEW_LEFT = 30 |