• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.android.deskclock.timer;
16 
17 import android.os.Bundle;
18 import android.os.SystemClock;
19 import android.support.annotation.NonNull;
20 import android.transition.AutoTransition;
21 import android.transition.TransitionManager;
22 import android.view.Gravity;
23 import android.view.KeyEvent;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.view.WindowManager;
27 import android.widget.FrameLayout;
28 import android.widget.TextView;
29 
30 import com.android.deskclock.BaseActivity;
31 import com.android.deskclock.R;
32 import com.android.deskclock.data.DataModel;
33 import com.android.deskclock.data.Timer;
34 import com.android.deskclock.data.TimerListener;
35 
36 import java.util.List;
37 
38 /**
39  * This activity is designed to be shown over the lock screen. As such, it displays the expired
40  * timers and a single button to reset them all. Each expired timer can also be reset to one minute
41  * with a button in the user interface. All other timer operations are disabled in this activity.
42  */
43 public class ExpiredTimersActivity extends BaseActivity {
44 
45     /** Scheduled to update the timers while at least one is expired. */
46     private final Runnable mTimeUpdateRunnable = new TimeUpdateRunnable();
47 
48     /** Updates the timers displayed in this activity as the backing data changes. */
49     private final TimerListener mTimerChangeWatcher = new TimerChangeWatcher();
50 
51     /** The scene root for transitions when expired timers are added/removed from this container. */
52     private ViewGroup mExpiredTimersScrollView;
53 
54     /** Displays the expired timers. */
55     private ViewGroup mExpiredTimersView;
56 
57     @Override
onCreate(Bundle savedInstanceState)58     protected void onCreate(Bundle savedInstanceState) {
59         super.onCreate(savedInstanceState);
60 
61         setContentView(R.layout.expired_timers_activity);
62 
63         mExpiredTimersView = (ViewGroup) findViewById(R.id.expired_timers_list);
64         mExpiredTimersScrollView = (ViewGroup) findViewById(R.id.expired_timers_scroll);
65 
66         findViewById(R.id.fab).setOnClickListener(new FabClickListener());
67 
68         final View view = findViewById(R.id.expired_timers_activity);
69         view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
70 
71         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
72                 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
73                 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
74                 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
75                 | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON);
76 
77         // Create views for each of the expired timers.
78         for (Timer timer : getExpiredTimers()) {
79             addTimer(timer);
80         }
81 
82         // Update views in response to timer data changes.
83         DataModel.getDataModel().addTimerListener(mTimerChangeWatcher);
84     }
85 
86     @Override
onResume()87     protected void onResume() {
88         super.onResume();
89         startUpdatingTime();
90     }
91 
92     @Override
onPause()93     protected void onPause() {
94         super.onPause();
95         stopUpdatingTime();
96     }
97 
98     @Override
onDestroy()99     public void onDestroy() {
100         super.onDestroy();
101         DataModel.getDataModel().removeTimerListener(mTimerChangeWatcher);
102     }
103 
104     @Override
dispatchKeyEvent(@onNull KeyEvent event)105     public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
106         if (event.getAction() == KeyEvent.ACTION_UP) {
107             switch (event.getKeyCode()) {
108                 case KeyEvent.KEYCODE_VOLUME_UP:
109                 case KeyEvent.KEYCODE_VOLUME_DOWN:
110                 case KeyEvent.KEYCODE_VOLUME_MUTE:
111                 case KeyEvent.KEYCODE_CAMERA:
112                 case KeyEvent.KEYCODE_FOCUS:
113                     DataModel.getDataModel().resetExpiredTimers(R.string.label_hardware_button);
114                     return true;
115             }
116         }
117         return super.dispatchKeyEvent(event);
118     }
119 
120     /**
121      * Post the first runnable to update times within the UI. It will reschedule itself as needed.
122      */
startUpdatingTime()123     private void startUpdatingTime() {
124         // Ensure only one copy of the runnable is ever scheduled by first stopping updates.
125         stopUpdatingTime();
126         mExpiredTimersView.post(mTimeUpdateRunnable);
127     }
128 
129     /**
130      * Remove the runnable that updates times within the UI.
131      */
stopUpdatingTime()132     private void stopUpdatingTime() {
133         mExpiredTimersView.removeCallbacks(mTimeUpdateRunnable);
134     }
135 
136     /**
137      * Create and add a new view that corresponds with the given {@code timer}.
138      */
addTimer(Timer timer)139     private void addTimer(Timer timer) {
140         TransitionManager.beginDelayedTransition(mExpiredTimersScrollView, new AutoTransition());
141 
142         final TimerItem timerItem = (TimerItem)
143                 getLayoutInflater().inflate(R.layout.timer_item, mExpiredTimersView, false);
144         // Store the timer id as a tag on the view so it can be located on delete.
145         timerItem.setTag(timer.getId());
146         mExpiredTimersView.addView(timerItem);
147 
148         // Hide the label hint for expired timers.
149         final TextView labelView = (TextView) timerItem.findViewById(R.id.timer_label);
150         labelView.setHint(null);
151 
152         // Add logic to the "Add 1 Minute" button.
153         final View addMinuteButton = timerItem.findViewById(R.id.reset_add);
154         addMinuteButton.setOnClickListener(new View.OnClickListener() {
155             @Override
156             public void onClick(View v) {
157                 final int index = mExpiredTimersView.indexOfChild(timerItem);
158                 final Timer timer = getExpiredTimers().get(index);
159                 DataModel.getDataModel().addTimerMinute(timer);
160             }
161         });
162 
163         // If the first timer was just added, center it.
164         final List<Timer> expiredTimers = getExpiredTimers();
165         if (expiredTimers.size() == 1) {
166             centerFirstTimer();
167         } else if (expiredTimers.size() == 2) {
168             uncenterFirstTimer();
169         }
170     }
171 
172     /**
173      * Remove an existing view that corresponds with the given {@code timer}.
174      */
removeTimer(Timer timer)175     private void removeTimer(Timer timer) {
176         TransitionManager.beginDelayedTransition(mExpiredTimersScrollView, new AutoTransition());
177 
178         final View timerView = mExpiredTimersView.findViewWithTag(timer.getId());
179         mExpiredTimersView.removeView(timerView);
180 
181         // If the second last timer was just removed, center the last timer.
182         final List<Timer> expiredTimers = getExpiredTimers();
183         if (expiredTimers.isEmpty()) {
184             finish();
185         } else if (expiredTimers.size() == 1) {
186             centerFirstTimer();
187         }
188     }
189 
190     /**
191      * Center the single timer.
192      */
centerFirstTimer()193     private void centerFirstTimer() {
194         final FrameLayout.LayoutParams lp =
195                 (FrameLayout.LayoutParams) mExpiredTimersView.getLayoutParams();
196         lp.gravity = Gravity.CENTER;
197         mExpiredTimersView.requestLayout();
198     }
199 
200     /**
201      * Display the multiple timers as a scrollable list.
202      */
uncenterFirstTimer()203     private void uncenterFirstTimer() {
204         final FrameLayout.LayoutParams lp =
205                 (FrameLayout.LayoutParams) mExpiredTimersView.getLayoutParams();
206         lp.gravity = Gravity.NO_GRAVITY;
207         mExpiredTimersView.requestLayout();
208     }
209 
getExpiredTimers()210     private List<Timer> getExpiredTimers() {
211         return DataModel.getDataModel().getExpiredTimers();
212     }
213 
214     /**
215      * Periodically refreshes the state of each timer.
216      */
217     private class TimeUpdateRunnable implements Runnable {
218         @Override
run()219         public void run() {
220             final long startTime = SystemClock.elapsedRealtime();
221 
222             for (int i = 0; i < mExpiredTimersView.getChildCount(); i++) {
223                 final TimerItem timerItem = (TimerItem) mExpiredTimersView.getChildAt(i);
224                 final Timer timer = getExpiredTimers().get(i);
225                 timerItem.update(timer);
226             }
227 
228             final long endTime = SystemClock.elapsedRealtime();
229 
230             // Try to maintain a consistent period of time between redraws.
231             final long delay = Math.max(0, startTime + 20 - endTime);
232             mExpiredTimersView.postDelayed(this, delay);
233         }
234     }
235 
236     /**
237      * Clicking the fab resets all expired timers.
238      */
239     private class FabClickListener implements View.OnClickListener {
240         @Override
onClick(View v)241         public void onClick(View v) {
242             stopUpdatingTime();
243             DataModel.getDataModel().removeTimerListener(mTimerChangeWatcher);
244             DataModel.getDataModel().resetExpiredTimers(R.string.label_deskclock);
245             finish();
246         }
247     }
248 
249     /**
250      * Adds and removes expired timers from this activity based on their state changes.
251      */
252     private class TimerChangeWatcher implements TimerListener {
253         @Override
timerAdded(Timer timer)254         public void timerAdded(Timer timer) {
255             if (timer.isExpired()) {
256                 addTimer(timer);
257             }
258         }
259 
260         @Override
timerUpdated(Timer before, Timer after)261         public void timerUpdated(Timer before, Timer after) {
262             if (!before.isExpired() && after.isExpired()) {
263                 addTimer(after);
264             } else if (before.isExpired() && !after.isExpired()) {
265                 removeTimer(before);
266             }
267         }
268 
269         @Override
timerRemoved(Timer timer)270         public void timerRemoved(Timer timer) {
271             if (timer.isExpired()) {
272                 removeTimer(timer);
273             }
274         }
275     }
276 }