• 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().resetExpiredTimers(R.string.label_hardware_button);
135                     return true;
136             }
137         }
138         return super.dispatchKeyEvent(event);
139     }
140 
141     /**
142      * Post the first runnable to update times within the UI. It will reschedule itself as needed.
143      */
startUpdatingTime()144     private void startUpdatingTime() {
145         // Ensure only one copy of the runnable is ever scheduled by first stopping updates.
146         stopUpdatingTime();
147         mExpiredTimersView.post(mTimeUpdateRunnable);
148     }
149 
150     /**
151      * Remove the runnable that updates times within the UI.
152      */
stopUpdatingTime()153     private void stopUpdatingTime() {
154         mExpiredTimersView.removeCallbacks(mTimeUpdateRunnable);
155     }
156 
157     /**
158      * Create and add a new view that corresponds with the given {@code timer}.
159      */
addTimer(Timer timer)160     private void addTimer(Timer timer) {
161         TransitionManager.beginDelayedTransition(mExpiredTimersScrollView, new AutoTransition());
162 
163         final int timerId = timer.getId();
164         final TimerItem timerItem = (TimerItem)
165                 getLayoutInflater().inflate(R.layout.timer_item, mExpiredTimersView, false);
166         // Store the timer id as a tag on the view so it can be located on delete.
167         timerItem.setId(timerId);
168         mExpiredTimersView.addView(timerItem);
169 
170         // Hide the label hint for expired timers.
171         final TextView labelView = (TextView) timerItem.findViewById(R.id.timer_label);
172         labelView.setHint(null);
173         labelView.setVisibility(TextUtils.isEmpty(timer.getLabel()) ? View.GONE : View.VISIBLE);
174 
175         // Add logic to the "Add 1 Minute" button.
176         final View addMinuteButton = timerItem.findViewById(R.id.reset_add);
177         addMinuteButton.setOnClickListener(new View.OnClickListener() {
178             @Override
179             public void onClick(View v) {
180                 final Timer timer = DataModel.getDataModel().getTimer(timerId);
181                 DataModel.getDataModel().addTimerMinute(timer);
182             }
183         });
184 
185         // If the first timer was just added, center it.
186         final List<Timer> expiredTimers = getExpiredTimers();
187         if (expiredTimers.size() == 1) {
188             centerFirstTimer();
189         } else if (expiredTimers.size() == 2) {
190             uncenterFirstTimer();
191         }
192     }
193 
194     /**
195      * Remove an existing view that corresponds with the given {@code timer}.
196      */
removeTimer(Timer timer)197     private void removeTimer(Timer timer) {
198         TransitionManager.beginDelayedTransition(mExpiredTimersScrollView, new AutoTransition());
199 
200         final int timerId = timer.getId();
201         final int count = mExpiredTimersView.getChildCount();
202         for (int i = 0; i < count; ++i) {
203             final View timerView = mExpiredTimersView.getChildAt(i);
204             if (timerView.getId() == timerId) {
205                 mExpiredTimersView.removeView(timerView);
206                 break;
207             }
208         }
209 
210         // If the second last timer was just removed, center the last timer.
211         final List<Timer> expiredTimers = getExpiredTimers();
212         if (expiredTimers.isEmpty()) {
213             finish();
214         } else if (expiredTimers.size() == 1) {
215             centerFirstTimer();
216         }
217     }
218 
219     /**
220      * Center the single timer.
221      */
centerFirstTimer()222     private void centerFirstTimer() {
223         final FrameLayout.LayoutParams lp =
224                 (FrameLayout.LayoutParams) mExpiredTimersView.getLayoutParams();
225         lp.gravity = Gravity.CENTER;
226         mExpiredTimersView.requestLayout();
227     }
228 
229     /**
230      * Display the multiple timers as a scrollable list.
231      */
uncenterFirstTimer()232     private void uncenterFirstTimer() {
233         final FrameLayout.LayoutParams lp =
234                 (FrameLayout.LayoutParams) mExpiredTimersView.getLayoutParams();
235         lp.gravity = Gravity.NO_GRAVITY;
236         mExpiredTimersView.requestLayout();
237     }
238 
getExpiredTimers()239     private List<Timer> getExpiredTimers() {
240         return DataModel.getDataModel().getExpiredTimers();
241     }
242 
243     /**
244      * Periodically refreshes the state of each timer.
245      */
246     private class TimeUpdateRunnable implements Runnable {
247         @Override
run()248         public void run() {
249             final long startTime = SystemClock.elapsedRealtime();
250 
251             final int count = mExpiredTimersView.getChildCount();
252             for (int i = 0; i < count; ++i) {
253                 final TimerItem timerItem = (TimerItem) mExpiredTimersView.getChildAt(i);
254                 final Timer timer = DataModel.getDataModel().getTimer(timerItem.getId());
255                 if (timer != null) {
256                     timerItem.update(timer);
257                 }
258             }
259 
260             final long endTime = SystemClock.elapsedRealtime();
261 
262             // Try to maintain a consistent period of time between redraws.
263             final long delay = Math.max(0L, startTime + 20L - endTime);
264             mExpiredTimersView.postDelayed(this, delay);
265         }
266     }
267 
268     /**
269      * Clicking the fab resets all expired timers.
270      */
271     private class FabClickListener implements View.OnClickListener {
272         @Override
onClick(View v)273         public void onClick(View v) {
274             stopUpdatingTime();
275             DataModel.getDataModel().removeTimerListener(mTimerChangeWatcher);
276             DataModel.getDataModel().resetExpiredTimers(R.string.label_deskclock);
277             finish();
278         }
279     }
280 
281     /**
282      * Adds and removes expired timers from this activity based on their state changes.
283      */
284     private class TimerChangeWatcher implements TimerListener {
285         @Override
timerAdded(Timer timer)286         public void timerAdded(Timer timer) {
287             if (timer.isExpired()) {
288                 addTimer(timer);
289             }
290         }
291 
292         @Override
timerUpdated(Timer before, Timer after)293         public void timerUpdated(Timer before, Timer after) {
294             if (!before.isExpired() && after.isExpired()) {
295                 addTimer(after);
296             } else if (before.isExpired() && !after.isExpired()) {
297                 removeTimer(before);
298             }
299         }
300 
301         @Override
timerRemoved(Timer timer)302         public void timerRemoved(Timer timer) {
303             if (timer.isExpired()) {
304                 removeTimer(timer);
305             }
306         }
307     }
308 }
309