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 Original line Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.media.taptotransfer.receiver
import android.annotation.SuppressLint
import android.annotation.SuppressLint
import android.app.StatusBarManager
import android.app.StatusBarManager
import android.content.Context
import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
import android.media.MediaRoute2Info
@@ -44,6 +45,7 @@ import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
import javax.inject.Inject


/**
/**
@@ -63,6 +65,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
        powerManager: PowerManager,
        powerManager: PowerManager,
        @Main private val mainHandler: Handler,
        @Main private val mainHandler: Handler,
        private val uiEventLogger: MediaTttReceiverUiEventLogger,
        private val uiEventLogger: MediaTttReceiverUiEventLogger,
        private val viewUtil: ViewUtil,
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
        context,
        context,
        logger,
        logger,
@@ -83,7 +86,6 @@ class MediaTttChipControllerReceiver @Inject constructor(
        height = WindowManager.LayoutParams.MATCH_PARENT
        height = WindowManager.LayoutParams.MATCH_PARENT
        layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
        layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
        fitInsetsTypes = 0 // Ignore insets from all system bars
        fitInsetsTypes = 0 // Ignore insets from all system bars
        flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
    }
    }


    private val commandQueueCallbacks = object : CommandQueue.Callbacks {
    private val commandQueueCallbacks = object : CommandQueue.Callbacks {
@@ -154,14 +156,14 @@ class MediaTttChipControllerReceiver @Inject constructor(
                context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding)
                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.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
        iconView.setImageDrawable(iconDrawable)
        iconView.setImageDrawable(iconDrawable)
        iconView.contentDescription = iconContentDescription
        iconView.contentDescription = iconContentDescription
    }
    }


    override fun animateViewIn(view: ViewGroup) {
    override fun animateViewIn(view: ViewGroup) {
        val appIconView = view.requireViewById<View>(R.id.app_icon)
        val appIconView = view.getAppIconView()
        appIconView.animate()
        appIconView.animate()
                .translationYBy(-1 * getTranslationAmount().toFloat())
                .translationYBy(-1 * getTranslationAmount().toFloat())
                .setDuration(30.frames)
                .setDuration(30.frames)
@@ -175,6 +177,12 @@ class MediaTttChipControllerReceiver @Inject constructor(
        startRipple(view.requireViewById(R.id.ripple))
        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. */
    /** Returns the amount that the chip will be translated by in its intro animation. */
    private fun getTranslationAmount(): Int {
    private fun getTranslationAmount(): Int {
        return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
        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)
        val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
        rippleView.setColor(color, 70)
        rippleView.setColor(color, 70)
    }
    }

    private fun View.getAppIconView(): CachingIconView {
        return this.requireViewById(R.id.app_icon)
    }
}
}


data class ChipReceiverInfo(
data class ChipReceiverInfo(
+7 −0
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.media.taptotransfer.sender


import android.app.StatusBarManager
import android.app.StatusBarManager
import android.content.Context
import android.content.Context
import android.graphics.Rect
import android.media.MediaRoute2Info
import android.media.MediaRoute2Info
import android.os.PowerManager
import android.os.PowerManager
import android.util.Log
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.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import dagger.Lazy
import dagger.Lazy
import javax.inject.Inject
import javax.inject.Inject


@@ -68,6 +70,7 @@ open class MediaTttChipControllerSender @Inject constructor(
        // And overcome performance issue, check [b/247817628] for details.
        // And overcome performance issue, check [b/247817628] for details.
        private val falsingManager: Lazy<FalsingManager>,
        private val falsingManager: Lazy<FalsingManager>,
        private val falsingCollector: Lazy<FalsingCollector>,
        private val falsingCollector: Lazy<FalsingCollector>,
        private val viewUtil: ViewUtil,
) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
        context,
        context,
        logger,
        logger,
@@ -224,6 +227,10 @@ open class MediaTttChipControllerSender @Inject constructor(
        return false
        return false
    }
    }


    override fun getTouchableRegion(view: View, outRect: Rect) {
        viewUtil.setRectToViewWindowLocation(view, outRect)
    }

    private fun Boolean.visibleIfTrue(): Int {
    private fun Boolean.visibleIfTrue(): Int {
        return if (this) {
        return if (this) {
            View.VISIBLE
            View.VISIBLE
+22 −1
Original line number Original line Diff line number Diff line
@@ -20,10 +20,12 @@ import android.annotation.LayoutRes
import android.annotation.SuppressLint
import android.annotation.SuppressLint
import android.content.Context
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.Drawable
import android.os.PowerManager
import android.os.PowerManager
import android.os.SystemClock
import android.os.SystemClock
import android.view.LayoutInflater
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup
import android.view.WindowManager
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager
@@ -70,7 +72,8 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
        width = WindowManager.LayoutParams.WRAP_CONTENT
        width = WindowManager.LayoutParams.WRAP_CONTENT
        height = WindowManager.LayoutParams.WRAP_CONTENT
        height = WindowManager.LayoutParams.WRAP_CONTENT
        type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
        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
        title = windowTitle
        format = PixelFormat.TRANSLUCENT
        format = PixelFormat.TRANSLUCENT
        setTrustedOverlay()
        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. */
    /** The view currently being displayed. Null if the view is not being displayed. */
    private var view: ViewGroup? = null
    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. */
    /** The info currently being displayed. Null if the view is not being displayed. */
    internal var info: T? = null
    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. */
    /** A [Runnable] that, when run, will cancel the pending timeout of the view. */
    private var cancelViewTimeout: Runnable? = null
    private var cancelViewTimeout: Runnable? = null


@@ -141,6 +150,11 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
                .from(context)
                .from(context)
                .inflate(viewLayoutRes, null) as ViewGroup
                .inflate(viewLayoutRes, null) as ViewGroup
        view = newView
        view = newView

        val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
        newViewController.init()
        viewController = newViewController

        updateView(newInfo, newView)
        updateView(newInfo, newView)
        windowManager.addView(newView, windowLayoutParams)
        windowManager.addView(newView, windowLayoutParams)
        animateViewIn(newView)
        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
        // that if a new view event comes in while this view is animating out, we still display the
        // new view appropriately.
        // new view appropriately.
        view = null
        view = null
        viewController = null
        info = null
        info = null
        // No need to time the view out since it's already gone
        // No need to time the view out since it's already gone
        cancelViewTimeout?.run()
        cancelViewTimeout?.run()
@@ -201,6 +216,12 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
        info = newInfo
        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
     * A method that can be implemented by subclasses to do custom animations for when the view
     * appears.
     * appears.
+57 −0
Original line number Original line 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 Original line 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
package com.android.systemui.util.view


import android.graphics.Rect
import android.view.View
import android.view.View
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import javax.inject.Inject
@@ -23,4 +40,22 @@ class ViewUtil @Inject constructor() {
                top <= y &&
                top <= y &&
                y <= top + view.height
                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