Merge changes I28a7b836,I6626351d,Ie4490fb8

* changes:
  AOSP/DeskClock - Add Kotlin files for AutoSizing and CircleView widgets
  AOSP/DeskClock - Add Kotlin files for widget/toast directory
  AOSP/DeskClock - Add Kotlin files for AlarmSelection and Adapter
diff --git a/Android.bp b/Android.bp
index 5bcfd5c..552fc13 100644
--- a/Android.bp
+++ b/Android.bp
@@ -57,6 +57,11 @@
         "src/**/deskclock/stopwatch/*.java",
         "src/**/deskclock/timer/*.java",
         "src/**/deskclock/uidata/*.java",
+        "src/**/deskclock/widget/selector/*.java",
+        "src/**/deskclock/widget/toast/*.java",
+        "src/**/deskclock/widget/AutoSizingTextClock.java",
+        "src/**/deskclock/widget/AutoSizingTextView.java",
+        "src/**/deskclock/widget/CircleView.java",
     ],
     product_specific: true,
     static_libs: [
diff --git a/src/com/android/deskclock/widget/AutoSizingTextClock.kt b/src/com/android/deskclock/widget/AutoSizingTextClock.kt
new file mode 100644
index 0000000..c332762
--- /dev/null
+++ b/src/com/android/deskclock/widget/AutoSizingTextClock.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 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.deskclock.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.TextClock
+
+/**
+ * Wrapper around TextClock that automatically re-sizes itself to fit within the given bounds.
+ */
+class AutoSizingTextClock @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0
+) : TextClock(context, attrs, defStyleAttr) {
+    private val mTextSizeHelper: TextSizeHelper? = TextSizeHelper(this)
+
+    private var mSuppressLayout = false
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        mTextSizeHelper!!.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+    }
+
+    override fun onTextChanged(
+        text: CharSequence,
+        start: Int,
+        lengthBefore: Int,
+        lengthAfter: Int
+    ) {
+        super.onTextChanged(text, start, lengthBefore, lengthAfter)
+        if (mTextSizeHelper != null) {
+            if (lengthBefore != lengthAfter) {
+                mSuppressLayout = false
+            }
+            mTextSizeHelper.onTextChanged(lengthBefore, lengthAfter)
+        } else {
+            requestLayout()
+        }
+    }
+
+    override fun setText(text: CharSequence, type: BufferType) {
+        mSuppressLayout = true
+        super.setText(text, type)
+        mSuppressLayout = false
+    }
+
+    override fun requestLayout() {
+        if (mTextSizeHelper == null || !mTextSizeHelper.shouldIgnoreRequestLayout()) {
+            if (!mSuppressLayout) {
+                super.requestLayout()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/widget/AutoSizingTextView.kt b/src/com/android/deskclock/widget/AutoSizingTextView.kt
new file mode 100644
index 0000000..d393542
--- /dev/null
+++ b/src/com/android/deskclock/widget/AutoSizingTextView.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 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.deskclock.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.appcompat.widget.AppCompatTextView
+
+/**
+ * A TextView which automatically re-sizes its text to fit within its boundaries.
+ */
+class AutoSizingTextView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = android.R.attr.textViewStyle
+) : AppCompatTextView(context, attrs, defStyleAttr) {
+    private val mTextSizeHelper: TextSizeHelper? = TextSizeHelper(this)
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        mTextSizeHelper!!.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+    }
+
+    override fun onTextChanged(
+        text: CharSequence?,
+        start: Int,
+        lengthBefore: Int,
+        lengthAfter: Int
+    ) {
+        super.onTextChanged(text, start, lengthBefore, lengthAfter)
+        if (mTextSizeHelper != null) {
+            mTextSizeHelper.onTextChanged(lengthBefore, lengthAfter)
+        } else {
+            requestLayout()
+        }
+    }
+
+    override fun requestLayout() {
+        if (mTextSizeHelper == null || !mTextSizeHelper.shouldIgnoreRequestLayout()) {
+            super.requestLayout()
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/widget/CircleView.kt b/src/com/android/deskclock/widget/CircleView.kt
new file mode 100644
index 0000000..062a284
--- /dev/null
+++ b/src/com/android/deskclock/widget/CircleView.kt
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2020 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.deskclock.widget
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.util.Property
+import android.view.Gravity
+import android.view.View
+
+import com.android.deskclock.R
+
+import kotlin.math.min
+
+/**
+ * A [View] that draws primitive circles.
+ */
+class CircleView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr) {
+    /** The [Paint] used to draw the circle. */
+    private val mCirclePaint = Paint()
+
+    /** the current [Gravity] used to align/size the circle */
+    var gravity: Int
+        private set
+
+    private var mCenterX: Float
+    private var mCenterY: Float
+
+    /** the radius of the circle */
+    var radius: Float
+        private set
+
+    init {
+        val a = context.obtainStyledAttributes(attrs, R.styleable.CircleView, defStyleAttr, 0)
+
+        gravity = a.getInt(R.styleable.CircleView_android_gravity, Gravity.NO_GRAVITY)
+        mCenterX = a.getDimension(R.styleable.CircleView_centerX, 0.0f)
+        mCenterY = a.getDimension(R.styleable.CircleView_centerY, 0.0f)
+        radius = a.getDimension(R.styleable.CircleView_radius, 0.0f)
+
+        mCirclePaint.color = a.getColor(R.styleable.CircleView_fillColor, Color.WHITE)
+
+        a.recycle()
+    }
+
+    override fun onRtlPropertiesChanged(layoutDirection: Int) {
+        super.onRtlPropertiesChanged(layoutDirection)
+
+        if (gravity != Gravity.NO_GRAVITY) {
+            applyGravity(gravity, layoutDirection)
+        }
+    }
+
+    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+        super.onLayout(changed, left, top, right, bottom)
+
+        if (gravity != Gravity.NO_GRAVITY) {
+            applyGravity(gravity, layoutDirection)
+        }
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+
+        // draw the circle, duh
+        canvas.drawCircle(mCenterX, mCenterY, radius, mCirclePaint)
+    }
+
+    override fun hasOverlappingRendering(): Boolean {
+        // only if we have a background, which we shouldn't...
+        return background != null
+    }
+
+    /**
+     * Describes how to align/size the circle relative to the view's bounds. Defaults to
+     * [Gravity.NO_GRAVITY].
+     *
+     * Note: using [.setCenterX], [.setCenterY], or
+     * [.setRadius] will automatically clear any conflicting gravity bits.
+     *
+     * @param gravity the [Gravity] flags to use
+     * @return this object, allowing calls to methods in this class to be chained
+     * @see R.styleable.CircleView_android_gravity
+     */
+    fun setGravity(gravity: Int): CircleView {
+        if (this.gravity != gravity) {
+            this.gravity = gravity
+
+            if (gravity != Gravity.NO_GRAVITY && isLayoutDirectionResolved) {
+                applyGravity(gravity, layoutDirection)
+            }
+        }
+        return this
+    }
+
+    /**
+     * @return the ARGB color used to fill the circle
+     */
+    val fillColor: Int
+        get() = mCirclePaint.color
+
+    /**
+     * Sets the ARGB color used to fill the circle and invalidates only the affected area.
+     *
+     * @param color the ARGB color to use
+     * @return this object, allowing calls to methods in this class to be chained
+     * @see R.styleable.CircleView_fillColor
+     */
+    fun setFillColor(color: Int): CircleView {
+        if (mCirclePaint.color != color) {
+            mCirclePaint.color = color
+
+            // invalidate the current area
+            invalidate(mCenterX, mCenterY, radius)
+        }
+        return this
+    }
+
+    /**
+     * Sets the x-coordinate for the center of the circle and invalidates only the affected area.
+     *
+     * @param centerX the x-coordinate to use, relative to the view's bounds
+     * @return this object, allowing calls to methods in this class to be chained
+     * @see R.styleable.CircleView_centerX
+     */
+    fun setCenterX(centerX: Float): CircleView {
+        val oldCenterX = mCenterX
+        if (oldCenterX != centerX) {
+            mCenterX = centerX
+
+            // invalidate the old/new areas
+            invalidate(oldCenterX, mCenterY, radius)
+            invalidate(centerX, mCenterY, radius)
+        }
+
+        // clear the horizontal gravity flags
+        gravity = gravity and Gravity.HORIZONTAL_GRAVITY_MASK.inv()
+
+        return this
+    }
+
+    /**
+     * Sets the y-coordinate for the center of the circle and invalidates only the affected area.
+     *
+     * @param centerY the y-coordinate to use, relative to the view's bounds
+     * @return this object, allowing calls to methods in this class to be chained
+     * @see R.styleable.CircleView_centerY
+     */
+    fun setCenterY(centerY: Float): CircleView {
+        val oldCenterY = mCenterY
+        if (oldCenterY != centerY) {
+            mCenterY = centerY
+
+            // invalidate the old/new areas
+            invalidate(mCenterX, oldCenterY, radius)
+            invalidate(mCenterX, centerY, radius)
+        }
+
+        // clear the vertical gravity flags
+        gravity = gravity and Gravity.VERTICAL_GRAVITY_MASK.inv()
+
+        return this
+    }
+
+    /**
+     * Sets the radius of the circle and invalidates only the affected area.
+     *
+     * @param radius the radius to use
+     * @return this object, allowing calls to methods in this class to be chained
+     * @see R.styleable.CircleView_radius
+     */
+    fun setRadius(radius: Float): CircleView {
+        val oldRadius = this.radius
+        if (oldRadius != radius) {
+            this.radius = radius
+
+            // invalidate the old/new areas
+            invalidate(mCenterX, mCenterY, oldRadius)
+            if (radius > oldRadius) {
+                invalidate(mCenterX, mCenterY, radius)
+            }
+        }
+
+        // clear the fill gravity flags
+        if (gravity and Gravity.FILL_HORIZONTAL == Gravity.FILL_HORIZONTAL) {
+            gravity = gravity and Gravity.FILL_HORIZONTAL.inv()
+        }
+        if (gravity and Gravity.FILL_VERTICAL == Gravity.FILL_VERTICAL) {
+            gravity = gravity and Gravity.FILL_VERTICAL.inv()
+        }
+
+        return this
+    }
+
+    /**
+     * Invalidates the rectangular area that circumscribes the circle defined by `centerX`,
+     * `centerY`, and `radius`.
+     */
+    private fun invalidate(centerX: Float, centerY: Float, radius: Float) {
+        invalidate((centerX - radius - 0.5f).toInt(), (centerY - radius - 0.5f).toInt(),
+                (centerX + radius + 0.5f).toInt(), (centerY + radius + 0.5f).toInt())
+    }
+
+    /**
+     * Applies the specified `gravity` and `layoutDirection`, adjusting the alignment
+     * and size of the circle depending on the resolved [Gravity] flags. Also invalidates the
+     * affected area if necessary.
+     *
+     * @param gravity the [Gravity] the [Gravity] flags to use
+     * @param layoutDirection the layout direction used to resolve the absolute gravity
+     */
+    @SuppressLint("RtlHardcoded")
+    private fun applyGravity(gravity: Int, layoutDirection: Int) {
+        val absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection)
+
+        val oldRadius = radius
+        val oldCenterX = mCenterX
+        val oldCenterY = mCenterY
+
+        when (absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK) {
+            Gravity.LEFT -> mCenterX = 0.0f
+            Gravity.CENTER_HORIZONTAL, Gravity.FILL_HORIZONTAL -> mCenterX = width / 2.0f
+            Gravity.RIGHT -> mCenterX = width.toFloat()
+        }
+
+        when (absoluteGravity and Gravity.VERTICAL_GRAVITY_MASK) {
+            Gravity.TOP -> mCenterY = 0.0f
+            Gravity.CENTER_VERTICAL, Gravity.FILL_VERTICAL -> mCenterY = height / 2.0f
+            Gravity.BOTTOM -> mCenterY = height.toFloat()
+        }
+
+        when (absoluteGravity and Gravity.FILL) {
+            Gravity.FILL -> radius = min(width, height) / 2.0f
+            Gravity.FILL_HORIZONTAL -> radius = width / 2.0f
+            Gravity.FILL_VERTICAL -> radius = height / 2.0f
+        }
+
+        if (oldCenterX != mCenterX || oldCenterY != mCenterY || oldRadius != radius) {
+            invalidate(oldCenterX, oldCenterY, oldRadius)
+            invalidate(mCenterX, mCenterY, radius)
+        }
+    }
+
+    companion object {
+        /**
+         * A Property wrapper around the fillColor functionality handled by the
+         * [.setFillColor] and [.getFillColor] methods.
+         */
+        @JvmField
+        val FILL_COLOR: Property<CircleView, Int> =
+                object : Property<CircleView, Int>(Int::class.java, "fillColor") {
+                    override fun get(view: CircleView): Int {
+                        return view.fillColor
+                    }
+
+                    override fun set(view: CircleView, value: Int) {
+                        view.setFillColor(value)
+                    }
+                }
+
+        /**
+         * A Property wrapper around the radius functionality handled by the
+         * [.setRadius] and [.getRadius] methods.
+         */
+        @JvmField
+        val RADIUS: Property<CircleView, Float> =
+                object : Property<CircleView, Float>(Float::class.java, "radius") {
+                    override fun get(view: CircleView): Float {
+                        return view.radius
+                    }
+
+                    override fun set(view: CircleView, value: Float) {
+                        view.setRadius(value)
+                    }
+                }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/widget/selector/AlarmSelection.kt b/src/com/android/deskclock/widget/selector/AlarmSelection.kt
new file mode 100644
index 0000000..bee3285
--- /dev/null
+++ b/src/com/android/deskclock/widget/selector/AlarmSelection.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 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.deskclock.widget.selector
+
+import com.android.deskclock.provider.Alarm
+
+/**
+ * Created a new selectable item with a visual label and an id.
+ * id corresponds to the Alarm id
+ */
+class AlarmSelection(val label: String, val alarm: Alarm)
\ No newline at end of file
diff --git a/src/com/android/deskclock/widget/selector/AlarmSelectionAdapter.kt b/src/com/android/deskclock/widget/selector/AlarmSelectionAdapter.kt
new file mode 100644
index 0000000..8858d64
--- /dev/null
+++ b/src/com/android/deskclock/widget/selector/AlarmSelectionAdapter.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 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.deskclock.widget.selector
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import android.widget.TextView
+
+import com.android.deskclock.R
+import com.android.deskclock.data.DataModel
+import com.android.deskclock.data.Weekdays
+import com.android.deskclock.provider.Alarm
+import com.android.deskclock.widget.TextTime
+
+import java.util.Calendar
+
+class AlarmSelectionAdapter(
+    context: Context,
+    id: Int,
+    alarms: List<AlarmSelection>
+) : ArrayAdapter<AlarmSelection?>(context, id, alarms) {
+    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+        val context = context
+        var row = convertView
+        if (row == null) {
+            val inflater = LayoutInflater.from(context)
+            row = inflater.inflate(R.layout.alarm_row, parent, false)
+        }
+
+        val selection = getItem(position)
+        val alarm = selection?.alarm
+
+        val alarmTime = row!!.findViewById<View>(R.id.digital_clock) as TextTime
+        alarmTime.setTime(alarm!!.hour, alarm.minutes)
+
+        val alarmLabel = row.findViewById<View>(R.id.label) as TextView
+        alarmLabel.text = alarm.label
+
+        // find days when alarm is firing
+        val daysOfWeek: String
+        daysOfWeek = if (!alarm.daysOfWeek.isRepeating) {
+            if (Alarm.isTomorrow(alarm, Calendar.getInstance())) {
+                context.resources.getString(R.string.alarm_tomorrow)
+            } else {
+                context.resources.getString(R.string.alarm_today)
+            }
+        } else {
+            val weekdayOrder: Weekdays.Order = DataModel.dataModel.weekdayOrder
+            alarm.daysOfWeek.toString(context, weekdayOrder)
+        }
+
+        val daysOfWeekView = row.findViewById<View>(R.id.daysOfWeek) as TextView
+        daysOfWeekView.text = daysOfWeek
+
+        return row
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/widget/toast/SnackbarManager.kt b/src/com/android/deskclock/widget/toast/SnackbarManager.kt
new file mode 100644
index 0000000..0d242f2
--- /dev/null
+++ b/src/com/android/deskclock/widget/toast/SnackbarManager.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 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.deskclock.widget.toast
+
+import com.google.android.material.snackbar.Snackbar
+
+import java.lang.ref.WeakReference
+
+/**
+ * Manages visibility of Snackbar and allow preemptive dismiss of current displayed Snackbar.
+ */
+object SnackbarManager {
+    private var sSnackbar: WeakReference<Snackbar>? = null
+
+    @JvmStatic
+    fun show(snackbar: Snackbar) {
+        sSnackbar = WeakReference<Snackbar>(snackbar)
+        snackbar.show()
+    }
+
+    @JvmStatic
+    fun dismiss() {
+        val snackbar: Snackbar? = sSnackbar?.get()
+        if (snackbar != null) {
+            snackbar.dismiss()
+            sSnackbar = null
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/widget/toast/SnackbarSlidingBehavior.kt b/src/com/android/deskclock/widget/toast/SnackbarSlidingBehavior.kt
new file mode 100644
index 0000000..4ebfc7c
--- /dev/null
+++ b/src/com/android/deskclock/widget/toast/SnackbarSlidingBehavior.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 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.deskclock.widget.toast
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.annotation.Keep
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+
+import com.google.android.material.snackbar.Snackbar
+
+import kotlin.math.min
+
+/**
+ * Custom [CoordinatorLayout.Behavior] that slides with the [Snackbar].
+ */
+@Keep
+class SnackbarSlidingBehavior(
+    context: Context?,
+    attrs: AttributeSet?
+) : CoordinatorLayout.Behavior<View?>() {
+    override fun layoutDependsOn(
+        parent: CoordinatorLayout,
+        child: View,
+        dependency: View
+    ): Boolean {
+        return dependency is Snackbar.SnackbarLayout
+    }
+
+    override fun onDependentViewChanged(
+        parent: CoordinatorLayout,
+        child: View,
+        dependency: View
+    ): Boolean {
+        updateTranslationY(parent, child)
+        return false
+    }
+
+    override fun onDependentViewRemoved(parent: CoordinatorLayout, child: View, dependency: View) {
+        updateTranslationY(parent, child)
+    }
+
+    override fun onLayoutChild(
+        parent: CoordinatorLayout,
+        child: View,
+        layoutDirection: Int
+    ): Boolean {
+        updateTranslationY(parent, child)
+        return false
+    }
+
+    private fun updateTranslationY(parent: CoordinatorLayout, child: View) {
+        var translationY = 0f
+        for (dependency in parent.getDependencies(child)) {
+            translationY = min(translationY, dependency.y - child.bottom)
+        }
+        child.translationY = translationY
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/widget/toast/ToastManager.kt b/src/com/android/deskclock/widget/toast/ToastManager.kt
new file mode 100644
index 0000000..49ef9b7
--- /dev/null
+++ b/src/com/android/deskclock/widget/toast/ToastManager.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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.deskclock.widget.toast
+
+import android.widget.Toast
+
+object ToastManager {
+    private var sToast: Toast? = null
+
+    @JvmStatic
+    fun setToast(toast: Toast) {
+        sToast?.cancel()
+        sToast = toast
+    }
+
+    @JvmStatic
+    fun cancelToast() {
+        sToast?.cancel()
+        sToast = null
+    }
+}
\ No newline at end of file