Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cd5a7d1a authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[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
parent 7d9b0e62
Loading
Loading
Loading
Loading
+15 −3
Original line number Diff line number Diff line
@@ -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(
+7 −0
Original line number Diff line number Diff line
@@ -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
+22 −1
Original line number Diff line number Diff line
@@ -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()
@@ -201,6 +216,12 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
        info = newInfo
    }

    /**
     * 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.
+57 −0
Original line number Diff line number Diff line
/*
 * 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)
    }
}
+35 −0
Original line number Diff line number Diff line
/*
 * 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,
        )
    }
}
Loading