• 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.content.Intent;
18 import android.content.pm.ActivityInfo;
19 import android.os.Bundle;
20 import android.os.SystemClock;
21 import android.support.annotation.NonNull;
22 import android.text.TextUtils;
23 import android.transition.AutoTransition;
24 import android.transition.TransitionManager;
25 import android.view.Gravity;
26 import android.view.KeyEvent;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.view.WindowManager;
30 import android.widget.FrameLayout;
31 import android.widget.TextView;
32 
33 import com.android.deskclock.BaseActivity;
34 import com.android.deskclock.LogUtils;
35 import com.android.deskclock.R;
36 import com.android.deskclock.data.DataModel;
37 import com.android.deskclock.data.Timer;
38 import com.android.deskclock.data.TimerListener;
39 
40 import java.util.List;
41 
42 /**
43  * This activity is designed to be shown over the lock screen. As such, it displays the expired
44  * timers and a single button to reset them all. Each expired timer can also be reset to one minute
45  * with a button in the user interface. All other timer operations are disabled in this activity.
46  */
47 public class ExpiredTimersActivity extends BaseActivity {
48 
49     /** Scheduled to update the timers while at least one is expired. */
50     private final Runnable mTimeUpdateRunnable = new TimeUpdateRunnable();
51 
52     /** Updates the timers displayed in this activity as the backing data changes. */
53     private final TimerListener mTimerChangeWatcher = new TimerChangeWatcher();
54 
55     /** The scene root for transitions when expired timers are added/removed from this container. */
56     private ViewGroup mExpiredTimersScrollView;
57 
58     /** Displays the expired timers. */
59     private ViewGroup mExpiredTimersView;
60 
61     @Override
onCreate(Bundle savedInstanceState)62     protected void onCreate(Bundle savedInstanceState) {
63         super.onCreate(savedInstanceState);
64 
65         final List<Timer> expiredTimers = getExpiredTimers();
66 
67         // If no expired timers, finish
68         if (expiredTimers.size() == 0) {
69             LogUtils.i("No expired timers, skipping display.");
70             finish();
71             return;
72         }
73 
74         setContentView(R.layout.expired_timers_activity);
75 
76         mExpiredTimersView = (ViewGroup) findViewById(R.id.expired_timers_list);
77         mExpiredTimersScrollView = (ViewGroup) findViewById(R.id.expired_timers_scroll);
78 
79         findViewById(R.id.fab).setOnClickListener(new FabClickListener());
80 
81         final View view = findViewById(R.id.expired_timers_activity);
82         view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
83 
84         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
85                 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
86                 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
87                 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
88                 | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON);
89 
90         // Close dialogs and window shade, so this is fully visible
91         sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
92 
93         // Honor rotation on tablets; fix the orientation on phones.
94         if (!getResources().getBoolean(R.bool.rotateAlarmAlert)) {
95             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
96         }
97 
98         // Create views for each of the expired timers.
99         for (Timer timer : expiredTimers) {
100             addTimer(timer);
101         }
102 
103         // Update views in response to timer data changes.
104         DataModel.getDataModel().addTimerListener(mTimerChangeWatcher);
105     }
106 
107     @Override
onResume()108     protected void onResume() {
109         super.onResume();
110         startUpdatingTime();
111     }
112 
113     @Override
onPause()114     protected void onPause() {
115         super.onPause();
116         stopUpdatingTime();
117     }
118 
119     @Override
onDestroy()120     public void onDestroy() {
121         super.onDestroy();
122         DataModel.getDataModel().removeTimerListener(mTimerChangeWatcher);
123     }
124 
125     @Override
dispatchKeyEvent(@onNull KeyEvent event)126     public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
127         if (event.getAction() == KeyEvent.ACTION_UP) {
128             switch (event.getKeyCode()) {
129                 case KeyEvent.KEYCODE_VOLUME_UP:
130                 case KeyEvent.KEYCODE_VOLUME_DOWN:
131                 case KeyEvent.KEYCODE_VOLUME_MUTE:
132                 case KeyEvent.KEYCODE_CAMERA:
133                 case KeyEvent.KEYCODE_FOCUS:
134                     DataModel.getDataModel().resetOrDeleteExpiredTimers(
135                             R.string.label_hardware_button);
136                     return true;
137             }
138         }
139         return super.dispatchKeyEvent(event);
140     }
141 
142     /**
143      * Post the first runnable to update times within the UI. It will reschedule itself as needed.
144      */
startUpdatingTime()145     private void startUpdatingTime() {
146         // Ensure only one copy of the runnable is ever scheduled by first stopping updates.
147         stopUpdatingTime();
148         mExpiredTimersView.post(mTimeUpdateRunnable);
149     }
150 
151     /**
152      * Remove the runnable that updates times within the UI.
153      */
stopUpdatingTime()154     private void stopUpdatingTime() {
155         mExpiredTimersView.removeCallbacks(mTimeUpdateRunnable);
156     }
157 
158     /**
159      * Create and add a new view that corresponds with the given {@code timer}.
160      */
addTimer(Timer timer)161     private void addTimer(Timer timer) {
162         TransitionManager.beginDelayedTransition(mExpiredTimersScrollView, new AutoTransition());
163 
164         final int timerId = timer.getId();
165         final TimerItem timerItem = (TimerItem)
166                 getLayoutInflater().inflate(R.layout.timer_item, mExpiredTimersView, false);
167         // Store the timer id as a tag on the view so it can be located on delete.
168         timerItem.setId(timerId);
169         mExpiredTimersView.addView(timerItem);
170 
171         // Hide the label hint for expired timers.
172         final TextView labelView = (TextView) timerItem.findViewById(R.id.timer_label);
173         labelView.setHint(null);
174         labelView.setVisibility(TextUtils.isEmpty(timer.getLabel()) ? View.GONE : View.VISIBLE);
175 
176         // Add logic to the "Add 1 Minute" button.
177         final View addMinuteButton = timerItem.findViewById(R.id.reset_add);
178         addMinuteButton.setOnClickListener(new View.OnClickListener() {
179             @Override
180             public void onClick(View v) {
181                 final Timer timer = DataModel.getDataModel().getTimer(timerId);
182                 DataModel.getDataModel().addTimerMinute(timer);
183             }
184         });
185 
186         // If the first timer was just added, center it.
187         final List<Timer> expiredTimers = getExpiredTimers();
188         if (expiredTimers.size() == 1) {
189             centerFirstTimer();
190         } else if (expiredTimers.size() == 2) {
191             uncenterFirstTimer();
192         }
193     }
194 
195     /**
196      * Remove an existing view that corresponds with the given {@code timer}.
197      */
removeTimer(Timer timer)198     private void removeTimer(Timer timer) {
199         TransitionManager.beginDelayedTransition(mExpiredTimersScrollView, new AutoTransition());
200 
201         final int timerId = timer.getId();
202         final int count = mExpiredTimersView.getChildCount();
203         for (int i = 0; i < count; ++i) {
204             final View timerView = mExpiredTimersView.getChildAt(i);
205             if (timerView.getId() == timerId) {
206                 mExpiredTimersView.removeView(timerView);
207                 break;
208             }
209         }
210 
211         // If the second last timer was just removed, center the last timer.
212         final List<Timer> expiredTimers = getExpiredTimers();
213         if (expiredTimers.isEmpty()) {
214             finish();
215         } else if (expiredTimers.size() == 1) {
216             centerFirstTimer();
217         }
218     }
219 
220     /**
221      * Center the single timer.
222      */
centerFirstTimer()223     private void centerFirstTimer() {
224         final FrameLayout.LayoutParams lp =
225                 (FrameLayout.LayoutParams) mExpiredTimersView.getLayoutParams();
226         lp.gravity = Gravity.CENTER;
227         mExpiredTimersView.requestLayout();
228     }
229 
230     /**
231      * Display the multiple timers as a scrollable list.
232      */
uncenterFirstTimer()233     private void uncenterFirstTimer() {
234         final FrameLayout.LayoutParams lp =
235                 (FrameLayout.LayoutParams) mExpiredTimersView.getLayoutParams();
236         lp.gravity = Gravity.NO_GRAVITY;
237         mExpiredTimersView.requestLayout();
238     }
239 
getExpiredTimers()240     private List<Timer> getExpiredTimers() {
241         return DataModel.getDataModel().getExpiredTimers();
242     }
243 
244     /**
245      * Periodically refreshes the state of each timer.
246      */
247     private class TimeUpdateRunnable implements Runnable {
248         @Override
run()249         public void run() {
250             final long startTime = SystemClock.elapsedRealtime();
251 
252             final int count = mExpiredTimersView.getChildCount();
253             for (int i = 0; i < count; ++i) {
254                 final TimerItem timerItem = (TimerItem) mExpiredTimersView.getChildAt(i);
255                 final Timer timer = DataModel.getDataModel().getTimer(timerItem.getId());
256                 if (timer != null) {
257                     timerItem.update(timer);
258                 }
259             }
260 
261             final long endTime = SystemClock.elapsedRealtime();
262 
263             // Try to maintain a consistent period of time between redraws.
264             final long delay = Math.max(0L, startTime + 20L - endTime);
265             mExpiredTimersView.postDelayed(this, delay);
266         }
267     }
268 
269     /**
270      * Clicking the fab resets all expired timers.
271      */
272     private class FabClickListener implements View.OnClickListener {
273         @Override
onClick(View v)274         public void onClick(View v) {
275             stopUpdatingTime();
276             DataModel.getDataModel().removeTimerListener(mTimerChangeWatcher);
277             DataModel.getDataModel().resetOrDeleteExpiredTimers(R.string.label_deskclock);
278             finish();
279         }
280     }
281 
282     /**
283      * Adds and removes expired timers from this activity based on their state changes.
284      */
285     private class TimerChangeWatcher implements TimerListener {
286         @Override
timerAdded(Timer timer)287         public void timerAdded(Timer timer) {
288             if (timer.isExpired()) {
289                 addTimer(timer);
290             }
291         }
292 
293         @Override
timerUpdated(Timer before, Timer after)294         public void timerUpdated(Timer before, Timer after) {
295             if (!before.isExpired() && after.isExpired()) {
296                 addTimer(after);
297             } else if (before.isExpired() && !after.isExpired()) {
298                 removeTimer(before);
299             }
300         }
301 
302         @Override
timerRemoved(Timer timer)303         public void timerRemoved(Timer timer) {
304             if (timer.isExpired()) {
305                 removeTimer(timer);
306             }
307         }
308     }
309 }
310