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

Commit 421a14f3 authored by Evan Laird's avatar Evan Laird Committed by Android Build Coastguard Worker
Browse files

Accessibility for privacy status animations

This CL adds contentDescriptions to the privacy event chip and the
ongoing privacy dot views. This is a partial fix which allows talkback
for the ongoing privacy chip, and allows talkback scrolling to the
privacy dot. However, due to the fact that the dot lives in a different
window, the privacy dot shows up in the wrong part of the accesibility
tree and still needs to be virtually placed in the status bar.

Bug: 187197696
Test: manual
Change-Id: I6210e18c75a1aab61cbcf619a9a31c459fffe544
(cherry picked from commit c14dd631)
parent fb0db265
Loading
Loading
Loading
Loading
+14 −3
Original line number Diff line number Diff line
@@ -497,7 +497,12 @@ class PrivacyDotViewController @Inject constructor(
        }

        if (state.designatedCorner != currentViewState.designatedCorner) {
            currentViewState.designatedCorner?.contentDescription = null
            state.designatedCorner?.contentDescription = state.contentDescription

            updateDesignatedCorner(state.designatedCorner, state.shouldShowDot())
        } else if (state.contentDescription != currentViewState.contentDescription) {
            state.designatedCorner?.contentDescription = state.contentDescription
        }

        val shouldShow = state.shouldShowDot()
@@ -514,9 +519,13 @@ class PrivacyDotViewController @Inject constructor(

    private val systemStatusAnimationCallback: SystemStatusAnimationCallback =
            object : SystemStatusAnimationCallback {
        override fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? {
        override fun onSystemStatusAnimationTransitionToPersistentDot(
            contentDescr: String?
        ): Animator? {
            synchronized(lock) {
                nextViewState = nextViewState.copy(systemPrivacyEventIsActive = true)
                nextViewState = nextViewState.copy(
                        systemPrivacyEventIsActive = true,
                        contentDescription = contentDescr)
            }

            return null
@@ -620,7 +629,9 @@ private data class ViewState(
    val rotation: Int = 0,
    val height: Int = 0,
    val cornerIndex: Int = -1,
    val designatedCorner: View? = null
    val designatedCorner: View? = null,

    val contentDescription: String? = null
) {
    fun shouldShowDot(): Boolean {
        return systemPrivacyEventIsActive && !shadeExpanded && !qsExpanded
+11 −1
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ interface StatusEvent {
    // Whether or not to show an animation for this event
    val showAnimation: Boolean
    val viewCreator: (context: Context) -> View
    var contentDescription: String?

    // Update this event with values from another event.
    fun updateFromEvent(other: StatusEvent?) {
@@ -50,6 +51,7 @@ class BatteryEvent : StatusEvent {
    override val priority = 50
    override val forceVisible = false
    override val showAnimation = true
    override var contentDescription: String? = ""

    override val viewCreator: (context: Context) -> View = { context ->
        val iv = ImageView(context)
@@ -62,7 +64,9 @@ class BatteryEvent : StatusEvent {
        return javaClass.simpleName
    }
}

class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
    override var contentDescription: String? = null
    override val priority = 100
    override val forceVisible = true
    var privacyItems: List<PrivacyItem> = listOf()
@@ -72,6 +76,7 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
        val v = LayoutInflater.from(context)
                .inflate(R.layout.ongoing_privacy_chip, null) as OngoingPrivacyChip
        v.privacyList = privacyItems
        v.contentDescription = contentDescription
        privacyChip = v
        v
    }
@@ -81,7 +86,9 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
    }

    override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean {
        return other is PrivacyEvent && other.privacyItems != privacyItems
        return other is PrivacyEvent &&
                (other.privacyItems != privacyItems ||
                other.contentDescription != contentDescription)
    }

    override fun updateFromEvent(other: StatusEvent?) {
@@ -90,6 +97,9 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
        }

        privacyItems = other.privacyItems
        contentDescription = other.contentDescription

        privacyChip?.contentDescription = other.contentDescription
        privacyChip?.privacyList = other.privacyItems
    }
}
+1 −2
Original line number Diff line number Diff line
@@ -34,8 +34,7 @@ import com.android.systemui.statusbar.phone.StatusBarWindowView
import javax.inject.Inject

/**
 * //TODO: this _probably_ doesn't control a window anymore
 * Controls the window for system event animations.
 * Controls the view for system event animations.
 */
class SystemEventChipAnimationController @Inject constructor(
    private val context: Context,
+10 −1
Original line number Diff line number Diff line
@@ -16,9 +16,12 @@

package com.android.systemui.statusbar.events

import android.content.Context
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_PRIVACY
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.privacy.PrivacyChipBuilder
import com.android.systemui.privacy.PrivacyItem
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.statusbar.policy.BatteryController
@@ -33,7 +36,8 @@ import javax.inject.Inject
class SystemEventCoordinator @Inject constructor(
    private val systemClock: SystemClock,
    private val batteryController: BatteryController,
    private val privacyController: PrivacyItemController
    private val privacyController: PrivacyItemController,
    private val context: Context
) {
    private lateinit var scheduler: SystemStatusAnimationScheduler

@@ -66,6 +70,11 @@ class SystemEventCoordinator @Inject constructor(
    fun notifyPrivacyItemsChanged(showAnimation: Boolean = true) {
        val event = PrivacyEvent(showAnimation)
        event.privacyItems = privacyStateListener.currentPrivacyItems
        event.contentDescription = {
            val items = PrivacyChipBuilder(context, event.privacyItems).joinTypes()
            context.getString(
                    R.string.ongoing_privacy_chip_content_multiple_apps, items)
        }()
        scheduler.onStatusEvent(event)
    }

+22 −22
Original line number Diff line number Diff line
@@ -100,17 +100,20 @@ class SystemStatusAnimationScheduler @Inject constructor(

        // Don't deal with threading for now (no need let's be honest)
        Assert.isMainThread()
        if (event.priority > scheduledEvent?.priority ?: -1 ||
            scheduledEvent?.shouldUpdateFromEvent(event) == true) {
        if ((event.priority > scheduledEvent?.priority ?: -1) &&
                animationState != ANIMATING_OUT &&
                (animationState != SHOWING_PERSISTENT_DOT && event.forceVisible)) {
            // events can only be scheduled if a higher priority or no other event is in progress
            if (DEBUG) {
                Log.d(TAG, "scheduling event $event")
            }
            if (event.showAnimation) {

            scheduleEvent(event)
            } else if (event.forceVisible) {
                hasPersistentDot = true
                notifyTransitionToPersistentDot()
        } else if (scheduledEvent?.shouldUpdateFromEvent(event) == true) {
            if (DEBUG) {
                Log.d(TAG, "updating current event from: $event")
            }
            scheduledEvent?.updateFromEvent(event)
        } else {
            if (DEBUG) {
                Log.d(TAG, "ignoring event $event")
@@ -142,24 +145,18 @@ class SystemStatusAnimationScheduler @Inject constructor(
     * Clear the scheduled event (if any) and schedule a new one
     */
    private fun scheduleEvent(event: StatusEvent) {
        if (animationState == ANIMATING_OUT ||
            (animationState == SHOWING_PERSISTENT_DOT && event.forceVisible)) {
            // do not schedule an event or change the current one
            return
        }

        // If we are showing the chip, possibly update the current event, rather than replacing
        if (scheduledEvent?.shouldUpdateFromEvent(event) == true) {
            scheduledEvent?.updateFromEvent(event)
            return
        } else {
        scheduledEvent = event
        }

        if (scheduledEvent!!.forceVisible) {
        if (event.forceVisible) {
            hasPersistentDot = true
        }

        // If animations are turned off, we'll transition directly to the dot
        if (!event.showAnimation && event.forceVisible) {
            notifyTransitionToPersistentDot()
            return
        }

        // Schedule the animation to start after a debounce period
        cancelExecutionRunnable = executor.executeDelayed({
            cancelExecutionRunnable = null
@@ -218,7 +215,7 @@ class SystemStatusAnimationScheduler @Inject constructor(

    private fun notifyTransitionToPersistentDot(): Animator? {
        val anims: List<Animator> = listeners.mapNotNull {
            it.onSystemStatusAnimationTransitionToPersistentDot()
            it.onSystemStatusAnimationTransitionToPersistentDot(scheduledEvent?.contentDescription)
        }
        if (anims.isNotEmpty()) {
            val aSet = AnimatorSet()
@@ -346,7 +343,10 @@ interface SystemStatusAnimationCallback {
    @JvmDefault fun onSystemChromeAnimationEnd() {}

    // Best method name, change my mind
    @JvmDefault fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? { return null }
    @JvmDefault
    fun onSystemStatusAnimationTransitionToPersistentDot(contentDescription: String?): Animator? {
        return null
    }
    @JvmDefault fun onHidePersistentDot(): Animator? { return null }
}