• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.deskclock
18 
19 import android.annotation.SuppressLint
20 import android.annotation.TargetApi
21 import android.app.AlarmManager
22 import android.app.AlarmManager.AlarmClockInfo
23 import android.app.PendingIntent
24 import android.appwidget.AppWidgetManager
25 import android.appwidget.AppWidgetProviderInfo
26 import android.content.ContentResolver
27 import android.content.Context
28 import android.content.Intent
29 import android.content.res.Configuration
30 import android.graphics.Bitmap
31 import android.graphics.Canvas
32 import android.graphics.Color
33 import android.graphics.Paint
34 import android.graphics.PorterDuff
35 import android.graphics.PorterDuffColorFilter
36 import android.graphics.Typeface
37 import android.net.Uri
38 import android.os.Build
39 import android.os.Looper
40 import android.provider.Settings
41 import android.text.Spannable
42 import android.text.SpannableString
43 import android.text.TextUtils
44 import android.text.format.DateFormat
45 import android.text.format.DateUtils
46 import android.text.style.RelativeSizeSpan
47 import android.text.style.StyleSpan
48 import android.text.style.TypefaceSpan
49 import android.util.ArraySet
50 import android.view.View
51 import android.widget.TextClock
52 import android.widget.TextView
53 import androidx.annotation.AnyRes
54 import androidx.annotation.DrawableRes
55 import androidx.annotation.StringRes
56 import androidx.core.os.BuildCompat
57 import androidx.core.view.AccessibilityDelegateCompat
58 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
59 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
60 import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
61 
62 import com.android.deskclock.data.DataModel
63 import com.android.deskclock.provider.AlarmInstance
64 import com.android.deskclock.uidata.UiDataModel
65 
66 import java.text.NumberFormat
67 import java.text.SimpleDateFormat
68 import java.util.Calendar
69 import java.util.Date
70 import java.util.Locale
71 import java.util.TimeZone
72 
73 import kotlin.math.abs
74 import kotlin.math.max
75 
76 object Utils {
77     /**
78      * [Uri] signifying the "silent" ringtone.
79      */
80     @JvmField
81     val RINGTONE_SILENT = Uri.EMPTY
82 
enforceMainLoopernull83     fun enforceMainLooper() {
84         if (Looper.getMainLooper() != Looper.myLooper()) {
85             throw IllegalAccessError("May only call from main thread.")
86         }
87     }
88 
enforceNotMainLoopernull89     fun enforceNotMainLooper() {
90         if (Looper.getMainLooper() == Looper.myLooper()) {
91             throw IllegalAccessError("May not call from main thread.")
92         }
93     }
94 
indexOfnull95     fun indexOf(array: Array<out Any>, item: Any): Int {
96         for (i in array.indices) {
97             if (array[i] == item) {
98                 return i
99             }
100         }
101         return -1
102     }
103 
104     /**
105      * @return `true` if the device is prior to [Build.VERSION_CODES.LOLLIPOP]
106      */
107     val isPreL: Boolean
108         get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
109 
110     /**
111      * @return `true` if the device is [Build.VERSION_CODES.LOLLIPOP] or
112      * [Build.VERSION_CODES.LOLLIPOP_MR1]
113      */
114     val isLOrLMR1: Boolean
115         get() {
116             val sdkInt = Build.VERSION.SDK_INT
117             return sdkInt == Build.VERSION_CODES.LOLLIPOP ||
118                     sdkInt == Build.VERSION_CODES.LOLLIPOP_MR1
119         }
120 
121     /**
122      * @return `true` if the device is [Build.VERSION_CODES.LOLLIPOP] or later
123      */
124     val isLOrLater: Boolean
125         get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
126 
127     /**
128      * @return `true` if the device is [Build.VERSION_CODES.LOLLIPOP_MR1] or later
129      */
130     val isLMR1OrLater: Boolean
131         get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1
132 
133     /**
134      * @return `true` if the device is [Build.VERSION_CODES.M] or later
135      */
136     val isMOrLater: Boolean
137         get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
138 
139     /**
140      * @return `true` if the device is [Build.VERSION_CODES.N] or later
141      */
142     val isNOrLater: Boolean
143         get() = BuildCompat.isAtLeastN()
144 
145     /**
146      * @return `true` if the device is [Build.VERSION_CODES.N_MR1] or later
147      */
148     val isNMR1OrLater: Boolean
149         get() = BuildCompat.isAtLeastNMR1()
150 
151     /**
152      * @return `true` if the device is [Build.VERSION_CODES.O] or later
153      */
154     val isOOrLater: Boolean
155         get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
156 
157     /**
158      * @param resourceId identifies an application resource
159      * @return the Uri by which the application resource is accessed
160      */
getResourceUrinull161     fun getResourceUri(context: Context, @AnyRes resourceId: Int): Uri {
162         return Uri.Builder()
163                 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
164                 .authority(context.packageName)
165                 .path(resourceId.toString())
166                 .build()
167     }
168 
169     /**
170      * @param view the scrollable view to test
171      * @return `true` iff the `view` content is currently scrolled to the top
172      */
isScrolledToTopnull173     fun isScrolledToTop(view: View): Boolean {
174         return !view.canScrollVertically(-1)
175     }
176 
177     /**
178      * Calculate the amount by which the radius of a CircleTimerView should be offset by any
179      * of the extra painted objects.
180      */
calculateRadiusOffsetnull181     fun calculateRadiusOffset(
182         strokeSize: Float,
183         dotStrokeSize: Float,
184         markerStrokeSize: Float
185     ): Float {
186         return max(strokeSize, max(dotStrokeSize, markerStrokeSize))
187     }
188 
189     /**
190      * Configure the clock that is visible to display seconds. The clock that is not visible never
191      * displays seconds to avoid it scheduling unnecessary ticking runnables.
192      */
setClockSecondsEnablednull193     fun setClockSecondsEnabled(digitalClock: TextClock, analogClock: AnalogClock) {
194         val displaySeconds: Boolean = DataModel.dataModel.displayClockSeconds
195         when (DataModel.dataModel.clockStyle) {
196             DataModel.ClockStyle.ANALOG -> {
197                 setTimeFormat(digitalClock, false)
198                 analogClock.enableSeconds(displaySeconds)
199             }
200             DataModel.ClockStyle.DIGITAL -> {
201                 analogClock.enableSeconds(false)
202                 setTimeFormat(digitalClock, displaySeconds)
203             }
204         }
205     }
206 
207     /**
208      * Set whether the digital or analog clock should be displayed in the application.
209      * Returns the view to be displayed.
210      */
setClockStylenull211     fun setClockStyle(digitalClock: View, analogClock: View): View {
212         return when (DataModel.dataModel.clockStyle) {
213             DataModel.ClockStyle.ANALOG -> {
214                 digitalClock.visibility = View.GONE
215                 analogClock.visibility = View.VISIBLE
216                 analogClock
217             }
218             DataModel.ClockStyle.DIGITAL -> {
219                 digitalClock.visibility = View.VISIBLE
220                 analogClock.visibility = View.GONE
221                 digitalClock
222             }
223         }
224     }
225 
226     /**
227      * For screensavers to set whether the digital or analog clock should be displayed.
228      * Returns the view to be displayed.
229      */
setScreensaverClockStylenull230     fun setScreensaverClockStyle(digitalClock: View, analogClock: View): View {
231         return when (DataModel.dataModel.screensaverClockStyle) {
232             DataModel.ClockStyle.ANALOG -> {
233                 digitalClock.visibility = View.GONE
234                 analogClock.visibility = View.VISIBLE
235                 analogClock
236             }
237             DataModel.ClockStyle.DIGITAL -> {
238                 digitalClock.visibility = View.VISIBLE
239                 analogClock.visibility = View.GONE
240                 digitalClock
241             }
242         }
243     }
244 
245     /**
246      * For screensavers to dim the lights if necessary.
247      */
dimClockViewnull248     fun dimClockView(dim: Boolean, clockView: View) {
249         val paint = Paint()
250         paint.color = Color.WHITE
251         paint.colorFilter = PorterDuffColorFilter(
252                 if (dim) 0x40FFFFFF else -0x3f000001,
253                 PorterDuff.Mode.MULTIPLY)
254         clockView.setLayerType(View.LAYER_TYPE_HARDWARE, paint)
255     }
256 
257     /**
258      * Update and return the PendingIntent corresponding to the given `intent`.
259      *
260      * @param context the Context in which the PendingIntent should start the service
261      * @param intent an Intent describing the service to be started
262      * @return a PendingIntent that will start a service
263      */
pendingServiceIntentnull264     fun pendingServiceIntent(context: Context, intent: Intent): PendingIntent {
265         return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
266     }
267 
268     /**
269      * Update and return the PendingIntent corresponding to the given `intent`.
270      *
271      * @param context the Context in which the PendingIntent should start the activity
272      * @param intent an Intent describing the activity to be started
273      * @return a PendingIntent that will start an activity
274      */
pendingActivityIntentnull275     fun pendingActivityIntent(context: Context, intent: Intent): PendingIntent {
276         return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
277     }
278 
279     /**
280      * @return The next alarm from [AlarmManager]
281      */
getNextAlarmnull282     fun getNextAlarm(context: Context): String? {
283         return if (isPreL) getNextAlarmPreL(context) else getNextAlarmLOrLater(context)
284     }
285 
286     @TargetApi(Build.VERSION_CODES.KITKAT)
getNextAlarmPreLnull287     private fun getNextAlarmPreL(context: Context): String {
288         val cr = context.contentResolver
289         return Settings.System.getString(cr, Settings.System.NEXT_ALARM_FORMATTED)
290     }
291 
292     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
getNextAlarmLOrLaternull293     private fun getNextAlarmLOrLater(context: Context): String? {
294         val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
295         val info = getNextAlarmClock(am)
296         if (info != null) {
297             val triggerTime = info.triggerTime
298             val alarmTime = Calendar.getInstance()
299             alarmTime.timeInMillis = triggerTime
300             return AlarmUtils.getFormattedTime(context, alarmTime)
301         }
302 
303         return null
304     }
305 
306     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
getNextAlarmClocknull307     private fun getNextAlarmClock(am: AlarmManager): AlarmClockInfo? {
308         return am.nextAlarmClock
309     }
310 
311     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
updateNextAlarmnull312     fun updateNextAlarm(am: AlarmManager, info: AlarmClockInfo?, op: PendingIntent?) {
313         am.setAlarmClock(info, op)
314     }
315 
isAlarmWithin24Hoursnull316     fun isAlarmWithin24Hours(alarmInstance: AlarmInstance): Boolean {
317         val nextAlarmTime: Calendar = alarmInstance.alarmTime
318         val nextAlarmTimeMillis = nextAlarmTime.timeInMillis
319         return nextAlarmTimeMillis - System.currentTimeMillis() <= DateUtils.DAY_IN_MILLIS
320     }
321 
322     /**
323      * Clock views can call this to refresh their alarm to the next upcoming value.
324      */
refreshAlarmnull325     fun refreshAlarm(context: Context, clock: View?) {
326         val nextAlarmIconView = clock?.findViewById<View>(R.id.nextAlarmIcon) as TextView
327         val nextAlarmView = clock.findViewById<View>(R.id.nextAlarm) as TextView? ?: return
328 
329         val alarm = getNextAlarm(context)
330         if (!TextUtils.isEmpty(alarm)) {
331             val description = context.getString(R.string.next_alarm_description, alarm)
332             nextAlarmView.text = alarm
333             nextAlarmView.contentDescription = description
334             nextAlarmView.visibility = View.VISIBLE
335             nextAlarmIconView.visibility = View.VISIBLE
336             nextAlarmIconView.contentDescription = description
337         } else {
338             nextAlarmView.visibility = View.GONE
339             nextAlarmIconView.visibility = View.GONE
340         }
341     }
342 
setClockIconTypefacenull343     fun setClockIconTypeface(clock: View?) {
344         val nextAlarmIconView = clock?.findViewById<View>(R.id.nextAlarmIcon) as TextView?
345         nextAlarmIconView?.typeface = UiDataModel.uiDataModel.alarmIconTypeface
346     }
347 
348     /**
349      * Clock views can call this to refresh their date.
350      */
updateDatenull351     fun updateDate(dateSkeleton: String?, descriptionSkeleton: String?, clock: View?) {
352         val dateDisplay = clock?.findViewById<View>(R.id.date) as TextView? ?: return
353 
354         val l = Locale.getDefault()
355         val datePattern = DateFormat.getBestDateTimePattern(l, dateSkeleton)
356         val descriptionPattern = DateFormat.getBestDateTimePattern(l, descriptionSkeleton)
357 
358         val now = Date()
359         dateDisplay.text = SimpleDateFormat(datePattern, l).format(now)
360         dateDisplay.visibility = View.VISIBLE
361         dateDisplay.contentDescription = SimpleDateFormat(descriptionPattern, l).format(now)
362     }
363 
364     /***
365      * Formats the time in the TextClock according to the Locale with a special
366      * formatting treatment for the am/pm label.
367      *
368      * @param clock TextClock to format
369      * @param includeSeconds whether or not to include seconds in the clock's time
370      */
setTimeFormatnull371     fun setTimeFormat(clock: TextClock?, includeSeconds: Boolean) {
372         // Get the best format for 12 hours mode according to the locale
373         clock?.format12Hour = get12ModeFormat(amPmRatio = 0.4f, includeSeconds = includeSeconds)
374         // Get the best format for 24 hours mode according to the locale
375         clock?.format24Hour = get24ModeFormat(includeSeconds)
376     }
377 
378     /**
379      * @param amPmRatio a value between 0 and 1 that is the ratio of the relative size of the
380      * am/pm string to the time string
381      * @param includeSeconds whether or not to include seconds in the time string
382      * @return format string for 12 hours mode time, not including seconds
383      */
get12ModeFormatnull384     fun get12ModeFormat(amPmRatio: Float, includeSeconds: Boolean): CharSequence {
385         var pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(),
386                 if (includeSeconds) "hmsa" else "hma")
387         if (amPmRatio <= 0) {
388             pattern = pattern.replace("a".toRegex(), "").trim { it <= ' ' }
389         }
390 
391         // Replace spaces with "Hair Space"
392         pattern = pattern.replace(" ".toRegex(), "\u200A")
393         // Build a spannable so that the am/pm will be formatted
394         val amPmPos = pattern.indexOf('a')
395         if (amPmPos == -1) {
396             return pattern
397         }
398 
399         val sp: Spannable = SpannableString(pattern)
400         sp.setSpan(RelativeSizeSpan(amPmRatio), amPmPos, amPmPos + 1,
401                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
402         sp.setSpan(StyleSpan(Typeface.NORMAL), amPmPos, amPmPos + 1,
403                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
404         sp.setSpan(TypefaceSpan("sans-serif"), amPmPos, amPmPos + 1,
405                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
406 
407         return sp
408     }
409 
get24ModeFormatnull410     fun get24ModeFormat(includeSeconds: Boolean): CharSequence {
411         return DateFormat.getBestDateTimePattern(Locale.getDefault(),
412                 if (includeSeconds) "Hms" else "Hm")
413     }
414 
415     /**
416      * Returns string denoting the timezone hour offset (e.g. GMT -8:00)
417      *
418      * @param useShortForm Whether to return a short form of the header that rounds to the
419      * nearest hour and excludes the "GMT" prefix
420      */
getGMTHourOffsetnull421     fun getGMTHourOffset(timezone: TimeZone, useShortForm: Boolean): String {
422         val gmtOffset = timezone.rawOffset
423         val hour = gmtOffset / DateUtils.HOUR_IN_MILLIS
424         val min = abs(gmtOffset) % DateUtils.HOUR_IN_MILLIS / DateUtils.MINUTE_IN_MILLIS
425 
426         return if (useShortForm) {
427             String.format(Locale.ENGLISH, "%+d", hour)
428         } else {
429             String.format(Locale.ENGLISH, "GMT %+d:%02d", hour, min)
430         }
431     }
432 
433     /**
434      * Given a point in time, return the subsequent moment any of the time zones changes days.
435      * e.g. Given 8:00pm on 1/1/2016 and time zones in LA and NY this method would return a Date for
436      * midnight on 1/2/2016 in the NY timezone since it changes days first.
437      *
438      * @param time a point in time from which to compute midnight on the subsequent day
439      * @param zones a collection of time zones
440      * @return the nearest point in the future at which any of the time zones changes days
441      */
getNextDaynull442     fun getNextDay(time: Date, zones: Collection<TimeZone>): Date {
443         var next: Calendar? = null
444         for (tz in zones) {
445             val c = Calendar.getInstance(tz)
446             c.time = time
447 
448             // Advance to the next day.
449             c.add(Calendar.DAY_OF_YEAR, 1)
450 
451             // Reset the time to midnight.
452             c[Calendar.HOUR_OF_DAY] = 0
453             c[Calendar.MINUTE] = 0
454             c[Calendar.SECOND] = 0
455             c[Calendar.MILLISECOND] = 0
456 
457             if (next == null || c < next) {
458                 next = c
459             }
460         }
461 
462         return next!!.time
463     }
464 
getNumberFormattedQuantityStringnull465     fun getNumberFormattedQuantityString(context: Context, id: Int, quantity: Int): String {
466         val localizedQuantity = NumberFormat.getInstance().format(quantity.toLong())
467         return context.resources.getQuantityString(id, quantity, localizedQuantity)
468     }
469 
470     /**
471      * @return `true` iff the widget is being hosted in a container where tapping is allowed
472      */
isWidgetClickablenull473     fun isWidgetClickable(widgetManager: AppWidgetManager, widgetId: Int): Boolean {
474         val wo = widgetManager.getAppWidgetOptions(widgetId)
475         return (wo != null &&
476                 wo.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1)
477                 != AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
478     }
479 
480     /**
481      * @return a vector-drawable inflated from the given `resId`
482      */
getVectorDrawablenull483     fun getVectorDrawable(context: Context, @DrawableRes resId: Int): VectorDrawableCompat? {
484         return VectorDrawableCompat.create(context.resources, resId, context.theme)
485     }
486 
487     /**
488      * This method assumes the given `view` has already been layed out.
489      *
490      * @return a Bitmap containing an image of the `view` at its current size
491      */
createBitmapnull492     fun createBitmap(view: View): Bitmap {
493         val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
494         val canvas = Canvas(bitmap)
495         view.draw(canvas)
496         return bitmap
497     }
498 
499     /**
500      * [ArraySet] is @hide prior to [Build.VERSION_CODES.M].
501      */
502     @SuppressLint("NewApi")
newArraySetnull503     fun <E> newArraySet(collection: Collection<E>): ArraySet<E> {
504         val arraySet = ArraySet<E>(collection.size)
505         arraySet.addAll(collection)
506         return arraySet
507     }
508 
509     /**
510      * @param context from which to query the current device configuration
511      * @return `true` if the device is currently in portrait or reverse portrait orientation
512      */
isPortraitnull513     fun isPortrait(context: Context): Boolean {
514         return context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
515     }
516 
517     /**
518      * @param context from which to query the current device configuration
519      * @return `true` if the device is currently in landscape or reverse landscape orientation
520      */
isLandscapenull521     fun isLandscape(context: Context): Boolean {
522         return context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
523     }
524 
nownull525     fun now(): Long = DataModel.dataModel.elapsedRealtime()
526 
527     fun wallClock(): Long = DataModel.dataModel.currentTimeMillis()
528 
529     /**
530      * @param context to obtain strings.
531      * @param displayMinutes whether or not minutes should be included
532      * @param isAhead `true` if the time should be marked 'ahead', else 'behind'
533      * @param hoursDifferent the number of hours the time is ahead/behind
534      * @param minutesDifferent the number of minutes the time is ahead/behind
535      * @return String describing the hours/minutes ahead or behind
536      */
537     fun createHoursDifferentString(
538         context: Context,
539         displayMinutes: Boolean,
540         isAhead: Boolean,
541         hoursDifferent: Int,
542         minutesDifferent: Int
543     ): String {
544         val timeString: String
545         timeString = if (displayMinutes && hoursDifferent != 0) {
546             // Both minutes and hours
547             val hoursShortQuantityString = getNumberFormattedQuantityString(context,
548                     R.plurals.hours_short, abs(hoursDifferent))
549             val minsShortQuantityString = getNumberFormattedQuantityString(context,
550                     R.plurals.minutes_short, abs(minutesDifferent))
551             @StringRes val stringType = if (isAhead) {
552                 R.string.world_hours_minutes_ahead
553             } else {
554                 R.string.world_hours_minutes_behind
555             }
556             context.getString(stringType, hoursShortQuantityString,
557                     minsShortQuantityString)
558         } else {
559             // Minutes alone or hours alone
560             val hoursQuantityString = getNumberFormattedQuantityString(
561                     context, R.plurals.hours, abs(hoursDifferent))
562             val minutesQuantityString = getNumberFormattedQuantityString(
563                     context, R.plurals.minutes, abs(minutesDifferent))
564             @StringRes val stringType = if (isAhead) {
565                 R.string.world_time_ahead
566             } else {
567                 R.string.world_time_behind
568             }
569             context.getString(stringType, if (displayMinutes) {
570                 minutesQuantityString
571             } else {
572                 hoursQuantityString
573             })
574         }
575         return timeString
576     }
577 
578     /**
579      * @param context The context from which to obtain strings
580      * @param hours Hours to display (if any)
581      * @param minutes Minutes to display (if any)
582      * @param seconds Seconds to display
583      * @return Provided time formatted as a String
584      */
getTimeStringnull585     fun getTimeString(context: Context, hours: Int, minutes: Int, seconds: Int): String {
586         if (hours != 0) {
587             return context.getString(R.string.hours_minutes_seconds, hours, minutes, seconds)
588         }
589         return if (minutes != 0) {
590             context.getString(R.string.minutes_seconds, minutes, seconds)
591         } else {
592             context.getString(R.string.seconds, seconds)
593         }
594     }
595 
596     class ClickAccessibilityDelegate @JvmOverloads constructor(
597         /** The label for talkback to apply to the view  */
598         private val mLabel: String,
599         /** Whether or not to always make the view visible to talkback  */
600         private val mIsAlwaysAccessibilityVisible: Boolean = false
601     ) : AccessibilityDelegateCompat() {
602 
onInitializeAccessibilityNodeInfonull603         override fun onInitializeAccessibilityNodeInfo(
604             host: View,
605             info: AccessibilityNodeInfoCompat
606         ) {
607             super.onInitializeAccessibilityNodeInfo(host, info)
608             if (mIsAlwaysAccessibilityVisible) {
609                 info.setVisibleToUser(true)
610             }
611             info.addAction(AccessibilityActionCompat(
612                     AccessibilityActionCompat.ACTION_CLICK.getId(), mLabel))
613         }
614     }
615 }