/*
* 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.alarms
import android.accessibilityservice.AccessibilityServiceInfo
import android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.PropertyValuesHolder
import android.animation.TimeInterpolator
import android.animation.ValueAnimator
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.ServiceConnection
import android.content.pm.ActivityInfo
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.media.AudioManager
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import android.widget.TextClock
import android.widget.TextView
import androidx.core.graphics.ColorUtils
import androidx.core.view.animation.PathInterpolatorCompat
import com.android.deskclock.AnimatorUtils
import com.android.deskclock.BaseActivity
import com.android.deskclock.LogUtils
import com.android.deskclock.data.DataModel
import com.android.deskclock.data.DataModel.AlarmVolumeButtonBehavior
import com.android.deskclock.events.Events
import com.android.deskclock.provider.AlarmInstance
import com.android.deskclock.provider.ClockContract.InstancesColumns
import com.android.deskclock.R
import com.android.deskclock.ThemeUtils
import com.android.deskclock.Utils
import com.android.deskclock.widget.CircleView
import kotlin.math.max
import kotlin.math.sqrt
class AlarmActivity : BaseActivity(), View.OnClickListener, View.OnTouchListener {
private val mHandler: Handler = Handler(Looper.myLooper()!!)
private val mReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
val action: String? = intent.getAction()
LOGGER.v("Received broadcast: %s", action)
if (!mAlarmHandled) {
when (action) {
AlarmService.ALARM_SNOOZE_ACTION -> snooze()
AlarmService.ALARM_DISMISS_ACTION -> dismiss()
AlarmService.ALARM_DONE_ACTION -> finish()
else -> LOGGER.i("Unknown broadcast: %s", action)
}
} else {
LOGGER.v("Ignored broadcast: %s", action)
}
}
}
private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
LOGGER.i("Finished binding to AlarmService")
}
override fun onServiceDisconnected(name: ComponentName?) {
LOGGER.i("Disconnected from AlarmService")
}
}
private var mAlarmInstance: AlarmInstance? = null
private var mAlarmHandled = false
private var mVolumeBehavior: AlarmVolumeButtonBehavior? = null
private var mCurrentHourColor = 0
private var mReceiverRegistered = false
/** Whether the AlarmService is currently bound */
private var mServiceBound = false
private var mAccessibilityManager: AccessibilityManager? = null
private lateinit var mAlertView: ViewGroup
private lateinit var mAlertTitleView: TextView
private lateinit var mAlertInfoView: TextView
private lateinit var mContentView: ViewGroup
private lateinit var mAlarmButton: ImageView
private lateinit var mSnoozeButton: ImageView
private lateinit var mDismissButton: ImageView
private lateinit var mHintView: TextView
private lateinit var mAlarmAnimator: ValueAnimator
private lateinit var mSnoozeAnimator: ValueAnimator
private lateinit var mDismissAnimator: ValueAnimator
private lateinit var mPulseAnimator: ValueAnimator
private var mInitialPointerIndex: Int = MotionEvent.INVALID_POINTER_ID
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setVolumeControlStream(AudioManager.STREAM_ALARM)
val instanceId = AlarmInstance.getId(getIntent().getData()!!)
mAlarmInstance = AlarmInstance.getInstance(getContentResolver(), instanceId)
if (mAlarmInstance == null) {
// The alarm was deleted before the activity got created, so just finish()
LOGGER.e("Error displaying alarm for intent: %s", getIntent())
finish()
return
} else if (mAlarmInstance!!.mAlarmState != InstancesColumns.FIRED_STATE) {
LOGGER.i("Skip displaying alarm for instance: %s", mAlarmInstance)
finish()
return
}
LOGGER.i("Displaying alarm for instance: %s", mAlarmInstance)
// Get the volume/camera button behavior setting
mVolumeBehavior = DataModel.dataModel.alarmVolumeButtonBehavior
if (Utils.isOOrLater) {
setShowWhenLocked(true)
setTurnScreenOn(true)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON)
} else {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON)
}
// Hide navigation bar to minimize accidental tap on Home key
hideNavigationBar()
// Close dialogs and window shade, so this is fully visible
sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
// Honor rotation on tablets; fix the orientation on phones.
if (!getResources().getBoolean(R.bool.rotateAlarmAlert)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR)
}
mAccessibilityManager = getSystemService(ACCESSIBILITY_SERVICE) as AccessibilityManager?
setContentView(R.layout.alarm_activity)
mAlertView = findViewById(R.id.alert) as ViewGroup
mAlertTitleView = mAlertView.findViewById(R.id.alert_title) as TextView
mAlertInfoView = mAlertView.findViewById(R.id.alert_info) as TextView
mContentView = findViewById(R.id.content) as ViewGroup
mAlarmButton = mContentView.findViewById(R.id.alarm) as ImageView
mSnoozeButton = mContentView.findViewById(R.id.snooze) as ImageView
mDismissButton = mContentView.findViewById(R.id.dismiss) as ImageView
mHintView = mContentView.findViewById(R.id.hint) as TextView
val titleView: TextView = mContentView.findViewById(R.id.title) as TextView
val digitalClock: TextClock = mContentView.findViewById(R.id.digital_clock) as TextClock
val pulseView = mContentView.findViewById(R.id.pulse) as CircleView
titleView.setText(mAlarmInstance!!.getLabelOrDefault(this))
Utils.setTimeFormat(digitalClock, false)
mCurrentHourColor = ThemeUtils.resolveColor(this, android.R.attr.windowBackground)
getWindow().setBackgroundDrawable(ColorDrawable(mCurrentHourColor))
mAlarmButton.setOnTouchListener(this)
mSnoozeButton.setOnClickListener(this)
mDismissButton.setOnClickListener(this)
mAlarmAnimator = AnimatorUtils.getScaleAnimator(mAlarmButton, 1.0f, 0.0f)
mSnoozeAnimator = getButtonAnimator(mSnoozeButton, Color.WHITE)
mDismissAnimator = getButtonAnimator(mDismissButton, mCurrentHourColor)
mPulseAnimator = ObjectAnimator.ofPropertyValuesHolder(pulseView,
PropertyValuesHolder.ofFloat(CircleView.RADIUS, 0.0f, pulseView.radius),
PropertyValuesHolder.ofObject(CircleView.FILL_COLOR, AnimatorUtils.ARGB_EVALUATOR,
ColorUtils.setAlphaComponent(pulseView.fillColor, 0)))
mPulseAnimator.setDuration(PULSE_DURATION_MILLIS.toLong())
mPulseAnimator.setInterpolator(PULSE_INTERPOLATOR)
mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE)
mPulseAnimator.start()
}
override fun onResume() {
super.onResume()
// Re-query for AlarmInstance in case the state has changed externally
val instanceId = AlarmInstance.getId(getIntent().getData()!!)
mAlarmInstance = AlarmInstance.getInstance(getContentResolver(), instanceId)
if (mAlarmInstance == null) {
LOGGER.i("No alarm instance for instanceId: %d", instanceId)
finish()
return
}
// Verify that the alarm is still firing before showing the activity
if (mAlarmInstance!!.mAlarmState != InstancesColumns.FIRED_STATE) {
LOGGER.i("Skip displaying alarm for instance: %s", mAlarmInstance)
finish()
return
}
if (!mReceiverRegistered) {
// Register to get the alarm done/snooze/dismiss intent.
val filter = IntentFilter(AlarmService.ALARM_DONE_ACTION)
filter.addAction(AlarmService.ALARM_SNOOZE_ACTION)
filter.addAction(AlarmService.ALARM_DISMISS_ACTION)
registerReceiver(mReceiver, filter)
mReceiverRegistered = true
}
bindAlarmService()
resetAnimations()
}
override fun onPause() {
super.onPause()
unbindAlarmService()
// Skip if register didn't happen to avoid IllegalArgumentException
if (mReceiverRegistered) {
unregisterReceiver(mReceiver)
mReceiverRegistered = false
}
}
override fun dispatchKeyEvent(keyEvent: KeyEvent): Boolean {
// Do this in dispatch to intercept a few of the system keys.
LOGGER.v("dispatchKeyEvent: %s", keyEvent)
val keyCode: Int = keyEvent.getKeyCode()
when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP,
KeyEvent.KEYCODE_VOLUME_DOWN,
KeyEvent.KEYCODE_VOLUME_MUTE,
KeyEvent.KEYCODE_HEADSETHOOK,
KeyEvent.KEYCODE_CAMERA,
KeyEvent.KEYCODE_FOCUS -> if (!mAlarmHandled) {
when (mVolumeBehavior) {
AlarmVolumeButtonBehavior.SNOOZE -> {
if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
snooze()
}
return true
}
AlarmVolumeButtonBehavior.DISMISS -> {
if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
dismiss()
}
return true
}
AlarmVolumeButtonBehavior.NOTHING -> {
}
}
}
}
return super.dispatchKeyEvent(keyEvent)
}
override fun onBackPressed() {
// Don't allow back to dismiss.
}
override fun onClick(view: View) {
if (mAlarmHandled) {
LOGGER.v("onClick ignored: %s", view)
return
}
LOGGER.v("onClick: %s", view)
// If in accessibility mode, allow snooze/dismiss by double tapping on respective icons.
if (isAccessibilityEnabled) {
if (view == mSnoozeButton) {
snooze()
} else if (view == mDismissButton) {
dismiss()
}
return
}
if (view == mSnoozeButton) {
hintSnooze()
} else if (view == mDismissButton) {
hintDismiss()
}
}
override fun onTouch(view: View?, event: MotionEvent): Boolean {
if (mAlarmHandled) {
LOGGER.v("onTouch ignored: %s", event)
return false
}
val action: Int = event.getActionMasked()
if (action == MotionEvent.ACTION_DOWN) {
LOGGER.v("onTouch started: %s", event)
// Track the pointer that initiated the touch sequence.
mInitialPointerIndex = event.getPointerId(event.getActionIndex())
// Stop the pulse, allowing the last pulse to finish.
mPulseAnimator.setRepeatCount(0)
} else if (action == MotionEvent.ACTION_CANCEL) {
LOGGER.v("onTouch canceled: %s", event)
// Clear the pointer index.
mInitialPointerIndex = MotionEvent.INVALID_POINTER_ID
// Reset everything.
resetAnimations()
}
val actionIndex: Int = event.getActionIndex()
if (mInitialPointerIndex == MotionEvent.INVALID_POINTER_ID ||
mInitialPointerIndex != event.getPointerId(actionIndex)) {
// Ignore any pointers other than the initial one, bail early.
return true
}
val contentLocation = intArrayOf(0, 0)
mContentView.getLocationOnScreen(contentLocation)
val x: Float = event.getRawX() - contentLocation[0]
val y: Float = event.getRawY() - contentLocation[1]
val alarmLeft: Int = mAlarmButton.getLeft() + mAlarmButton.getPaddingLeft()
val alarmRight: Int = mAlarmButton.getRight() - mAlarmButton.getPaddingRight()
val snoozeFraction: Float
val dismissFraction: Float
if (mContentView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
snoozeFraction =
getFraction(alarmRight.toFloat(), mSnoozeButton.getLeft().toFloat(), x)
dismissFraction =
getFraction(alarmLeft.toFloat(), mDismissButton.getRight().toFloat(), x)
} else {
snoozeFraction = getFraction(alarmLeft.toFloat(), mSnoozeButton.getRight().toFloat(), x)
dismissFraction =
getFraction(alarmRight.toFloat(), mDismissButton.getLeft().toFloat(), x)
}
setAnimatedFractions(snoozeFraction, dismissFraction)
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
LOGGER.v("onTouch ended: %s", event)
mInitialPointerIndex = MotionEvent.INVALID_POINTER_ID
if (snoozeFraction == 1.0f) {
snooze()
} else if (dismissFraction == 1.0f) {
dismiss()
} else {
if (snoozeFraction > 0.0f || dismissFraction > 0.0f) {
// Animate back to the initial state.
AnimatorUtils.reverse(mAlarmAnimator, mSnoozeAnimator, mDismissAnimator)
} else if (mAlarmButton.getTop() <= y && y <= mAlarmButton.getBottom()) {
// User touched the alarm button, hint the dismiss action.
hintDismiss()
}
// Restart the pulse.
mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE)
if (!mPulseAnimator.isStarted()) {
mPulseAnimator.start()
}
}
}
return true
}
private fun hideNavigationBar() {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
}
/**
* Returns `true` if accessibility is enabled, to enable alternate behavior for click
* handling, etc.
*/
private val isAccessibilityEnabled: Boolean
get() {
if (mAccessibilityManager == null || !mAccessibilityManager!!.isEnabled()) {
// Accessibility is unavailable or disabled.
return false
} else if (mAccessibilityManager!!.isTouchExplorationEnabled()) {
// TalkBack's touch exploration mode is enabled.
return true
}
// Check if "Switch Access" is enabled.
val enabledAccessibilityServices: List<AccessibilityServiceInfo> =
mAccessibilityManager!!.getEnabledAccessibilityServiceList(FEEDBACK_GENERIC)
return !enabledAccessibilityServices.isEmpty()
}
private fun hintSnooze() {
val alarmLeft: Int = mAlarmButton.getLeft() + mAlarmButton.getPaddingLeft()
val alarmRight: Int = mAlarmButton.getRight() - mAlarmButton.getPaddingRight()
val translationX = (Math.max(mSnoozeButton.getLeft() - alarmRight, 0) +
Math.min(mSnoozeButton.getRight() - alarmLeft, 0)).toFloat()
getAlarmBounceAnimator(translationX, if (translationX < 0.0f) {
R.string.description_direction_left
} else {
R.string.description_direction_right
}).start()
}
private fun hintDismiss() {
val alarmLeft: Int = mAlarmButton.getLeft() + mAlarmButton.getPaddingLeft()
val alarmRight: Int = mAlarmButton.getRight() - mAlarmButton.getPaddingRight()
val translationX = (Math.max(mDismissButton.getLeft() - alarmRight, 0) +
Math.min(mDismissButton.getRight() - alarmLeft, 0)).toFloat()
getAlarmBounceAnimator(translationX, if (translationX < 0.0f) {
R.string.description_direction_left
} else {
R.string.description_direction_right
}).start()
}
/**
* Set animators to initial values and restart pulse on alarm button.
*/
private fun resetAnimations() {
// Set the animators to their initial values.
setAnimatedFractions(0.0f /* snoozeFraction */, 0.0f /* dismissFraction */)
// Restart the pulse.
mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE)
if (!mPulseAnimator.isStarted()) {
mPulseAnimator.start()
}
}
/**
* Perform snooze animation and send snooze intent.
*/
private fun snooze() {
mAlarmHandled = true
LOGGER.v("Snoozed: %s", mAlarmInstance)
val colorAccent = ThemeUtils.resolveColor(this, R.attr.colorAccent)
setAnimatedFractions(1.0f /* snoozeFraction */, 0.0f /* dismissFraction */)
val snoozeMinutes = DataModel.dataModel.snoozeLength
val infoText: String = getResources().getQuantityString(
R.plurals.alarm_alert_snooze_duration, snoozeMinutes, snoozeMinutes)
val accessibilityText: String = getResources().getQuantityString(
R.plurals.alarm_alert_snooze_set, snoozeMinutes, snoozeMinutes)
getAlertAnimator(mSnoozeButton, R.string.alarm_alert_snoozed_text, infoText,
accessibilityText, colorAccent, colorAccent).start()
AlarmStateManager.setSnoozeState(this, mAlarmInstance!!, false /* showToast */)
Events.sendAlarmEvent(R.string.action_snooze, R.string.label_deskclock)
// Unbind here, otherwise alarm will keep ringing until activity finishes.
unbindAlarmService()
}
/**
* Perform dismiss animation and send dismiss intent.
*/
private fun dismiss() {
mAlarmHandled = true
LOGGER.v("Dismissed: %s", mAlarmInstance)
setAnimatedFractions(0.0f /* snoozeFraction */, 1.0f /* dismissFraction */)
getAlertAnimator(mDismissButton, R.string.alarm_alert_off_text, null /* infoText */,
getString(R.string.alarm_alert_off_text) /* accessibilityText */,
Color.WHITE, mCurrentHourColor).start()
AlarmStateManager.deleteInstanceAndUpdateParent(this, mAlarmInstance!!)
Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_deskclock)
// Unbind here, otherwise alarm will keep ringing until activity finishes.
unbindAlarmService()
}
/**
* Bind AlarmService if not yet bound.
*/
private fun bindAlarmService() {
if (!mServiceBound) {
val intent = Intent(this, AlarmService::class.java)
bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
mServiceBound = true
}
}
/**
* Unbind AlarmService if bound.
*/
private fun unbindAlarmService() {
if (mServiceBound) {
unbindService(mConnection)
mServiceBound = false
}
}
private fun setAnimatedFractions(snoozeFraction: Float, dismissFraction: Float) {
val alarmFraction = Math.max(snoozeFraction, dismissFraction)
AnimatorUtils.setAnimatedFraction(mAlarmAnimator, alarmFraction)
AnimatorUtils.setAnimatedFraction(mSnoozeAnimator, snoozeFraction)
AnimatorUtils.setAnimatedFraction(mDismissAnimator, dismissFraction)
}
private fun getFraction(x0: Float, x1: Float, x: Float): Float {
return Math.max(Math.min((x - x0) / (x1 - x0), 1.0f), 0.0f)
}
private fun getButtonAnimator(button: ImageView?, tintColor: Int): ValueAnimator {
return ObjectAnimator.ofPropertyValuesHolder(button,
PropertyValuesHolder.ofFloat(View.SCALE_X, BUTTON_SCALE_DEFAULT, 1.0f),
PropertyValuesHolder.ofFloat(View.SCALE_Y, BUTTON_SCALE_DEFAULT, 1.0f),
PropertyValuesHolder.ofInt(AnimatorUtils.BACKGROUND_ALPHA, 0, 255),
PropertyValuesHolder.ofInt(AnimatorUtils.DRAWABLE_ALPHA,
BUTTON_DRAWABLE_ALPHA_DEFAULT, 255),
PropertyValuesHolder.ofObject(AnimatorUtils.DRAWABLE_TINT,
AnimatorUtils.ARGB_EVALUATOR, Color.WHITE, tintColor))
}
private fun getAlarmBounceAnimator(translationX: Float, hintResId: Int): ValueAnimator {
val bounceAnimator: ValueAnimator = ObjectAnimator.ofFloat(mAlarmButton,
View.TRANSLATION_X, mAlarmButton.getTranslationX(), translationX, 0.0f)
bounceAnimator.setInterpolator(AnimatorUtils.DECELERATE_ACCELERATE_INTERPOLATOR)
bounceAnimator.setDuration(ALARM_BOUNCE_DURATION_MILLIS.toLong())
bounceAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animator: Animator?) {
mHintView.setText(hintResId)
if (mHintView.getVisibility() != View.VISIBLE) {
mHintView.setVisibility(View.VISIBLE)
ObjectAnimator.ofFloat(mHintView, View.ALPHA, 0.0f, 1.0f).start()
}
}
})
return bounceAnimator
}
private fun getAlertAnimator(
source: View,
titleResId: Int,
infoText: String?,
accessibilityText: String,
revealColor: Int,
backgroundColor: Int
): Animator {
val containerView: ViewGroup = findViewById(android.R.id.content) as ViewGroup
val sourceBounds = Rect(0, 0, source.getHeight(), source.getWidth())
containerView.offsetDescendantRectToMyCoords(source, sourceBounds)
val centerX: Int = sourceBounds.centerX()
val centerY: Int = sourceBounds.centerY()
val xMax = max(centerX, containerView.getWidth() - centerX)
val yMax = max(centerY, containerView.getHeight() - centerY)
val startRadius: Float = max(sourceBounds.width(), sourceBounds.height()) / 2.0f
val endRadius = sqrt(xMax * xMax + yMax * yMax.toDouble()).toFloat()
val revealView = CircleView(this)
.setCenterX(centerX.toFloat())
.setCenterY(centerY.toFloat())
.setFillColor(revealColor)
containerView.addView(revealView)
// TODO: Fade out source icon over the reveal (like LOLLIPOP version).
val revealAnimator: Animator = ObjectAnimator.ofFloat(
revealView, CircleView.RADIUS, startRadius, endRadius)
revealAnimator.setDuration(ALERT_REVEAL_DURATION_MILLIS.toLong())
revealAnimator.setInterpolator(REVEAL_INTERPOLATOR)
revealAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animator: Animator?) {
mAlertView.setVisibility(View.VISIBLE)
mAlertTitleView.setText(titleResId)
if (infoText != null) {
mAlertInfoView.setText(infoText)
mAlertInfoView.setVisibility(View.VISIBLE)
}
mContentView.setVisibility(View.GONE)
getWindow().setBackgroundDrawable(ColorDrawable(backgroundColor))
}
})
val fadeAnimator: ValueAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f)
fadeAnimator.setDuration(ALERT_FADE_DURATION_MILLIS.toLong())
fadeAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
containerView.removeView(revealView)
}
})
val alertAnimator = AnimatorSet()
alertAnimator.play(revealAnimator).before(fadeAnimator)
alertAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animator: Animator?) {
mAlertView.announceForAccessibility(accessibilityText)
mHandler.postDelayed(Runnable { finish() }, ALERT_DISMISS_DELAY_MILLIS.toLong())
}
})
return alertAnimator
}
companion object {
private val LOGGER = LogUtils.Logger("AlarmActivity")
private val PULSE_INTERPOLATOR: TimeInterpolator =
PathInterpolatorCompat.create(0.4f, 0.0f, 0.2f, 1.0f)
private val REVEAL_INTERPOLATOR: TimeInterpolator =
PathInterpolatorCompat.create(0.0f, 0.0f, 0.2f, 1.0f)
private const val PULSE_DURATION_MILLIS = 1000
private const val ALARM_BOUNCE_DURATION_MILLIS = 500
private const val ALERT_REVEAL_DURATION_MILLIS = 500
private const val ALERT_FADE_DURATION_MILLIS = 500
private const val ALERT_DISMISS_DELAY_MILLIS = 2000
private const val BUTTON_SCALE_DEFAULT = 0.7f
private const val BUTTON_DRAWABLE_ALPHA_DEFAULT = 165
}
}