1 /*
2  * Copyright (C) 2016 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 static com.android.deskclock.AnimatorUtils.getAlphaAnimator;
30 import static com.android.deskclock.AnimatorUtils.getScaleAnimator;
31 
32 /**
33  * This runnable chooses a random initial position for {@link #mSaverView} within
34  * {@link #mContentView} if {@link #mSaverView} is transparent. It also schedules itself to run
35  * each minute, at which time {@link #mSaverView} is faded out, set to a new random location, and
36  * faded in.
37  */
38 public final class MoveScreensaverRunnable implements Runnable {
39 
40     /** The duration over which the fade in/out animations occur. */
41     private static final long FADE_TIME = 3000L;
42 
43     /** Accelerate the hide animation. */
44     private final Interpolator mAcceleration = new AccelerateInterpolator();
45 
46     /** Decelerate the show animation. */
47     private final Interpolator mDeceleration = new DecelerateInterpolator();
48 
49     /** The container that houses {@link #mSaverView}. */
50     private final View mContentView;
51 
52     /** The display within the {@link #mContentView} that is randomly positioned. */
53     private final View mSaverView;
54 
55     /** Tracks the currently executing animation if any; used to gracefully stop the animation. */
56     private Animator mActiveAnimator;
57 
58     /**
59      * @param contentView contains the {@code saverView}
60      * @param saverView a child view of {@code contentView} that periodically moves around
61      */
MoveScreensaverRunnable(View contentView, View saverView)62     public MoveScreensaverRunnable(View contentView, View saverView) {
63         mContentView = contentView;
64         mSaverView = saverView;
65     }
66 
67     /**
68      * Start or restart the random movement of the saver view within the content view.
69      */
start()70     public void start() {
71         // Stop any existing animations or callbacks.
72         stop();
73 
74         // Reset the alpha to 0 so saver view will be randomly positioned within the new bounds.
75         mSaverView.setAlpha(0);
76 
77         // Execute the position updater runnable to choose the first random position of saver view.
78         run();
79 
80         // Schedule callbacks every minute to adjust the position of mSaverView.
81         UiDataModel.getUiDataModel().addMinuteCallback(this, -FADE_TIME);
82     }
83 
84     /**
85      * Stop the random movement of the saver view within the content view.
86      */
stop()87     public void stop() {
88         UiDataModel.getUiDataModel().removePeriodicCallback(this);
89 
90         // End any animation currently running.
91         if (mActiveAnimator != null) {
92             mActiveAnimator.end();
93             mActiveAnimator = null;
94         }
95     }
96 
97     @Override
run()98     public void run() {
99         Utils.enforceMainLooper();
100 
101         final boolean selectInitialPosition = mSaverView.getAlpha() == 0f;
102         if (selectInitialPosition) {
103             // When selecting an initial position for the saver view the width and height of
104             // mContentView are untrustworthy if this was caused by a configuration change. To
105             // combat this, we position the mSaverView randomly within the smallest box that is
106             // guaranteed to work.
107             final int smallestDim = Math.min(mContentView.getWidth(), mContentView.getHeight());
108             final float newX = getRandomPoint(smallestDim - mSaverView.getWidth());
109             final float newY = getRandomPoint(smallestDim - mSaverView.getHeight());
110 
111             mSaverView.setX(newX);
112             mSaverView.setY(newY);
113             mActiveAnimator = getAlphaAnimator(mSaverView, 0f, 1f);
114             mActiveAnimator.setDuration(FADE_TIME);
115             mActiveAnimator.setInterpolator(mDeceleration);
116             mActiveAnimator.start();
117         } else {
118             // Select a new random position anywhere in mContentView that will fit mSaverView.
119             final float newX = getRandomPoint(mContentView.getWidth() - mSaverView.getWidth());
120             final float newY = getRandomPoint(mContentView.getHeight() - mSaverView.getHeight());
121 
122             // Fade out and shrink the saver view.
123             final AnimatorSet hide = new AnimatorSet();
124             hide.setDuration(FADE_TIME);
125             hide.setInterpolator(mAcceleration);
126             hide.play(getAlphaAnimator(mSaverView, 1f, 0f))
127                     .with(getScaleAnimator(mSaverView, 1f, 0.85f));
128 
129             // Fade in and grow the saver view after altering its position.
130             final AnimatorSet show = new AnimatorSet();
131             show.setDuration(FADE_TIME);
132             show.setInterpolator(mDeceleration);
133             show.play(getAlphaAnimator(mSaverView, 0f, 1f))
134                     .with(getScaleAnimator(mSaverView, 0.85f, 1f));
135             show.addListener(new AnimatorListenerAdapter() {
136                 @Override
137                 public void onAnimationStart(Animator animation) {
138                     mSaverView.setX(newX);
139                     mSaverView.setY(newY);
140                 }
141             });
142 
143             // Execute hide followed by show.
144             final AnimatorSet all = new AnimatorSet();
145             all.play(show).after(hide);
146             mActiveAnimator = all;
147             mActiveAnimator.start();
148         }
149     }
150 
151     /**
152      * @return a random integer between 0 and the {@code maximum} exclusive.
153      */
getRandomPoint(float maximum)154     private static float getRandomPoint(float maximum) {
155         return (int) (Math.random() * maximum);
156     }
157 }