CheckAccessibilityAvailable.kt
/*
* Copyright 2021 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 androidx.wear.tiles.checkers
import kotlin.jvm.Throws
/**
* Checks a [TimelineBuilders.TimelineEntry] to ensure that at least one element within it has an
* accessibility description registered.
*
* At least one element on each tile should have a machine-readable content description
* associated with it, which can be read out using screen readers.
*/
internal class CheckAccessibilityAvailable : TimelineEntryChecker {
override val name: String
get() = "CheckAccessibilityAvailable"
@Throws(CheckerException::class)
@Suppress("deprecation") // TODO(b/276343540): Use protolayout types.
override fun check(entry: androidx.wear.tiles.TimelineBuilders.TimelineEntry) {
// Do a descent through the tile, checking that at least one element has an a11y tag.
if (entry.layout?.root?.let(this::checkElement) == false) {
throw CheckerException(
"Tile layout does not have any nodes with an accessibility " +
"description. You should add a Semantics Modifier to at least one of your " +
"LayoutElements."
)
}
}
@Suppress("deprecation") // TODO(b/276343540): Use protolayout types.
private fun checkElement(
element: androidx.wear.tiles.LayoutElementBuilders.LayoutElement
): Boolean {
val modifiers =
when (element) {
is androidx.wear.tiles.LayoutElementBuilders.Row -> element.modifiers
is androidx.wear.tiles.LayoutElementBuilders.Column -> element.modifiers
is androidx.wear.tiles.LayoutElementBuilders.Box -> element.modifiers
is androidx.wear.tiles.LayoutElementBuilders.Arc -> element.modifiers
is androidx.wear.tiles.LayoutElementBuilders.Spacer -> element.modifiers
is androidx.wear.tiles.LayoutElementBuilders.Image -> element.modifiers
is androidx.wear.tiles.LayoutElementBuilders.Text -> element.modifiers
is androidx.wear.tiles.LayoutElementBuilders.Spannable -> element.modifiers
else -> null
}
if (modifiers?.semantics != null) {
return true
}
// Descend...
// Note that individual Spannable elements cannot have semantics; the parent should have
// these.
return when (element) {
is androidx.wear.tiles.LayoutElementBuilders.Row ->
element.contents.any(this::checkElement)
is androidx.wear.tiles.LayoutElementBuilders.Column ->
element.contents.any(this::checkElement)
is androidx.wear.tiles.LayoutElementBuilders.Box ->
element.contents.any(this::checkElement)
is androidx.wear.tiles.LayoutElementBuilders.Arc ->
element.contents.any(this::checkArcLayoutElement)
else -> false
}
}
@Suppress("deprecation") // TODO(b/276343540): Use protolayout types.
private fun checkArcLayoutElement(
element: androidx.wear.tiles.LayoutElementBuilders.ArcLayoutElement
): Boolean {
val modifiers =
when (element) {
// Note that ArcAdapter should be handled by taking the modifiers from the inner
// element instead.
is androidx.wear.tiles.LayoutElementBuilders.ArcText -> element.modifiers
is androidx.wear.tiles.LayoutElementBuilders.ArcLine -> element.modifiers
is androidx.wear.tiles.LayoutElementBuilders.ArcSpacer -> element.modifiers
else -> null
}
if (modifiers?.semantics != null) {
return true
}
return if (element is androidx.wear.tiles.LayoutElementBuilders.ArcAdapter) {
element.content?.let(this::checkElement) ?: false
} else {
false
}
}
}