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.animation.Animator 20 import android.animation.AnimatorListenerAdapter 21 import android.animation.AnimatorSet 22 import android.view.View 23 import android.view.animation.AccelerateInterpolator 24 import android.view.animation.DecelerateInterpolator 25 import android.view.animation.Interpolator 26 27 import com.android.deskclock.uidata.UiDataModel 28 29 import kotlin.math.min 30 31 /** 32 * This runnable chooses a random initial position for [.mSaverView] within 33 * [.mContentView] if [.mSaverView] is transparent. It also schedules itself to run 34 * each minute, at which time [.mSaverView] is faded out, set to a new random location, and 35 * faded in. 36 */ 37 class MoveScreensaverRunnable( 38 /** The container that houses [.mSaverView]. */ 39 private val mContentView: View, 40 /** The display within the [.mContentView] that is randomly positioned. */ 41 private val mSaverView: View 42 ) : Runnable { 43 /** Accelerate the hide animation. */ 44 private val mAcceleration: Interpolator = AccelerateInterpolator() 45 46 /** Decelerate the show animation. */ 47 private val mDeceleration: Interpolator = DecelerateInterpolator() 48 49 /** Tracks the currently executing animation if any; used to gracefully stop the animation. */ 50 private var mActiveAnimator: Animator? = null 51 52 /** Start or restart the random movement of the saver view within the content view. */ startnull53 fun start() { 54 // Stop any existing animations or callbacks. 55 stop() 56 57 // Reset the alpha to 0 so saver view will be randomly positioned within the new bounds. 58 mSaverView.alpha = 0f 59 60 // Execute the position updater runnable to choose the first random position of saver view. 61 run() 62 63 // Schedule callbacks every minute to adjust the position of mSaverView. 64 UiDataModel.uiDataModel.addMinuteCallback(this, -FADE_TIME) 65 } 66 67 /** Stop the random movement of the saver view within the content view. */ stopnull68 fun stop() { 69 UiDataModel.uiDataModel.removePeriodicCallback(this) 70 71 // End any animation currently running. 72 if (mActiveAnimator != null) { 73 mActiveAnimator?.end() 74 mActiveAnimator = null 75 } 76 } 77 runnull78 override fun run() { 79 Utils.enforceMainLooper() 80 81 val selectInitialPosition = mSaverView.alpha == 0f 82 if (selectInitialPosition) { 83 // When selecting an initial position for the saver view the width and height of 84 // mContentView are untrustworthy if this was caused by a configuration change. To 85 // combat this, we position the mSaverView randomly within the smallest box that is 86 // guaranteed to work. 87 val smallestDim = min(mContentView.width, mContentView.height) 88 val newX = getRandomPoint(smallestDim - mSaverView.width.toFloat()) 89 val newY = getRandomPoint(smallestDim - mSaverView.height.toFloat()) 90 91 mSaverView.x = newX 92 mSaverView.y = newY 93 mActiveAnimator = AnimatorUtils.getAlphaAnimator(mSaverView, 0f, 1f) 94 mActiveAnimator?.duration = FADE_TIME 95 mActiveAnimator?.interpolator = mDeceleration 96 mActiveAnimator?.start() 97 } else { 98 // Select a new random position anywhere in mContentView that will fit mSaverView. 99 val newX = getRandomPoint(mContentView.width - mSaverView.width.toFloat()) 100 val newY = getRandomPoint(mContentView.height - mSaverView.height.toFloat()) 101 102 // Fade out and shrink the saver view. 103 val hide = AnimatorSet() 104 hide.duration = FADE_TIME 105 hide.interpolator = mAcceleration 106 hide.play(AnimatorUtils.getAlphaAnimator(mSaverView, 1f, 0f)) 107 .with(AnimatorUtils.getScaleAnimator(mSaverView, 1f, 0.85f)) 108 109 // Fade in and grow the saver view after altering its position. 110 val show = AnimatorSet() 111 show.duration = FADE_TIME 112 show.interpolator = mDeceleration 113 show.play(AnimatorUtils.getAlphaAnimator(mSaverView, 0f, 1f)) 114 .with(AnimatorUtils.getScaleAnimator(mSaverView, 0.85f, 1f)) 115 show.addListener(object : AnimatorListenerAdapter() { 116 override fun onAnimationStart(animation: Animator) { 117 mSaverView.x = newX 118 mSaverView.y = newY 119 } 120 }) 121 122 // Execute hide followed by show. 123 val all = AnimatorSet() 124 all.play(show).after(hide) 125 mActiveAnimator = all 126 mActiveAnimator?.start() 127 } 128 } 129 130 companion object { 131 /** The duration over which the fade in/out animations occur. */ 132 private const val FADE_TIME = 3000L 133 134 /** 135 * @return a random integer between 0 and the `maximum` exclusive. 136 */ getRandomPointnull137 private fun getRandomPoint(maximum: Float): Float { 138 return (Math.random() * maximum).toFloat() 139 } 140 } 141 }