16d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux/*
26d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Copyright (C) 2015 The Android Open Source Project
36d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux *
46d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
56d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * in compliance with the License. You may obtain a copy of the License at
66d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux *
76d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * http://www.apache.org/licenses/LICENSE-2.0
86d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux *
96d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Unless required by applicable law or agreed to in writing, software distributed under the License
106d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
116d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * or implied. See the License for the specific language governing permissions and limitations under
126d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * the License.
136d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */
146d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuxpackage com.android.deskclock.timer;
166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
174c74e17b4d318c695cc12a0f79d26e2a4ae2af95James Lemieuximport android.content.Intent;
184c74e17b4d318c695cc12a0f79d26e2a4ae2af95James Lemieuximport android.content.pm.ActivityInfo;
196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.os.Bundle;
206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.os.SystemClock;
216d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.support.annotation.NonNull;
22df41926ee8cf9bf97866cc6c44a875eb82246fb6Christine Franksimport android.text.TextUtils;
236d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.transition.AutoTransition;
246d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.transition.TransitionManager;
256d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.view.Gravity;
266d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.view.KeyEvent;
276d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.view.View;
286d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.view.ViewGroup;
296d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.view.WindowManager;
306d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.widget.FrameLayout;
316d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.widget.TextView;
326d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
336d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport com.android.deskclock.BaseActivity;
342548f5593d52e5db4321afd61c4b40f8b4c4072cSean Stoutimport com.android.deskclock.LogUtils;
356d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport com.android.deskclock.R;
366d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport com.android.deskclock.data.DataModel;
376d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport com.android.deskclock.data.Timer;
386d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport com.android.deskclock.data.TimerListener;
396d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
406d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport java.util.List;
416d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
426d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux/**
436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * This activity is designed to be shown over the lock screen. As such, it displays the expired
446d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * timers and a single button to reset them all. Each expired timer can also be reset to one minute
456d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * with a button in the user interface. All other timer operations are disabled in this activity.
466d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */
476d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuxpublic class ExpiredTimersActivity extends BaseActivity {
486d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
496d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /** Scheduled to update the timers while at least one is expired. */
506d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private final Runnable mTimeUpdateRunnable = new TimeUpdateRunnable();
516d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
526d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /** Updates the timers displayed in this activity as the backing data changes. */
536d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private final TimerListener mTimerChangeWatcher = new TimerChangeWatcher();
546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
556d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /** The scene root for transitions when expired timers are added/removed from this container. */
566d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private ViewGroup mExpiredTimersScrollView;
576d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
586d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /** Displays the expired timers. */
596d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private ViewGroup mExpiredTimersView;
606d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
616d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    @Override
626d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    protected void onCreate(Bundle savedInstanceState) {
636d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        super.onCreate(savedInstanceState);
646d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
652548f5593d52e5db4321afd61c4b40f8b4c4072cSean Stout        final List<Timer> expiredTimers = getExpiredTimers();
662548f5593d52e5db4321afd61c4b40f8b4c4072cSean Stout
672548f5593d52e5db4321afd61c4b40f8b4c4072cSean Stout        // If no expired timers, finish
682548f5593d52e5db4321afd61c4b40f8b4c4072cSean Stout        if (expiredTimers.size() == 0) {
692548f5593d52e5db4321afd61c4b40f8b4c4072cSean Stout            LogUtils.i("No expired timers, skipping display.");
702548f5593d52e5db4321afd61c4b40f8b4c4072cSean Stout            finish();
712548f5593d52e5db4321afd61c4b40f8b4c4072cSean Stout            return;
722548f5593d52e5db4321afd61c4b40f8b4c4072cSean Stout        }
732548f5593d52e5db4321afd61c4b40f8b4c4072cSean Stout
746d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        setContentView(R.layout.expired_timers_activity);
756d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
766d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mExpiredTimersView = (ViewGroup) findViewById(R.id.expired_timers_list);
776d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mExpiredTimersScrollView = (ViewGroup) findViewById(R.id.expired_timers_scroll);
786d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
796d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        findViewById(R.id.fab).setOnClickListener(new FabClickListener());
806d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
816d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final View view = findViewById(R.id.expired_timers_activity);
826d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
836d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
846d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
856d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
866d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
876d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
886d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON);
896d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
904c74e17b4d318c695cc12a0f79d26e2a4ae2af95James Lemieux        // Close dialogs and window shade, so this is fully visible
914c74e17b4d318c695cc12a0f79d26e2a4ae2af95James Lemieux        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
924c74e17b4d318c695cc12a0f79d26e2a4ae2af95James Lemieux
934c74e17b4d318c695cc12a0f79d26e2a4ae2af95James Lemieux        // Honor rotation on tablets; fix the orientation on phones.
94feaedf7c77195021484a9bcd7e3d7c40cbeba78eJustin Klaassen        if (!getResources().getBoolean(R.bool.rotateAlarmAlert)) {
954c74e17b4d318c695cc12a0f79d26e2a4ae2af95James Lemieux            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
964c74e17b4d318c695cc12a0f79d26e2a4ae2af95James Lemieux        }
974c74e17b4d318c695cc12a0f79d26e2a4ae2af95James Lemieux
986d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        // Create views for each of the expired timers.
992548f5593d52e5db4321afd61c4b40f8b4c4072cSean Stout        for (Timer timer : expiredTimers) {
1006d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            addTimer(timer);
1016d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
1026d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1036d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        // Update views in response to timer data changes.
1046d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        DataModel.getDataModel().addTimerListener(mTimerChangeWatcher);
1056d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
1066d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1076d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    @Override
1086d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    protected void onResume() {
1096d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        super.onResume();
1106d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        startUpdatingTime();
1116d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
1126d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1136d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    @Override
1146d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    protected void onPause() {
1156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        super.onPause();
1166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        stopUpdatingTime();
1176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
1186d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    @Override
1206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public void onDestroy() {
1216d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        super.onDestroy();
1226d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        DataModel.getDataModel().removeTimerListener(mTimerChangeWatcher);
1236d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
1246d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1256d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    @Override
1266d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
1276d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        if (event.getAction() == KeyEvent.ACTION_UP) {
1286d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            switch (event.getKeyCode()) {
1296d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                case KeyEvent.KEYCODE_VOLUME_UP:
1306d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                case KeyEvent.KEYCODE_VOLUME_DOWN:
1316d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                case KeyEvent.KEYCODE_VOLUME_MUTE:
1326d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                case KeyEvent.KEYCODE_CAMERA:
1336d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                case KeyEvent.KEYCODE_FOCUS:
1346d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                    DataModel.getDataModel().resetExpiredTimers(R.string.label_hardware_button);
1356d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                    return true;
1366d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            }
1376d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
1386d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        return super.dispatchKeyEvent(event);
1396d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
1406d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1416d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
1426d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * Post the first runnable to update times within the UI. It will reschedule itself as needed.
1436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
1446d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private void startUpdatingTime() {
1456d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        // Ensure only one copy of the runnable is ever scheduled by first stopping updates.
1466d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        stopUpdatingTime();
1476d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mExpiredTimersView.post(mTimeUpdateRunnable);
1486d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
1496d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1506d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
1516d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * Remove the runnable that updates times within the UI.
1526d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
1536d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private void stopUpdatingTime() {
1546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mExpiredTimersView.removeCallbacks(mTimeUpdateRunnable);
1556d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
1566d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1576d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
1586d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * Create and add a new view that corresponds with the given {@code timer}.
1596d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
1606d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private void addTimer(Timer timer) {
1616d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        TransitionManager.beginDelayedTransition(mExpiredTimersScrollView, new AutoTransition());
1626d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
163b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen        final int timerId = timer.getId();
1646d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final TimerItem timerItem = (TimerItem)
1656d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                getLayoutInflater().inflate(R.layout.timer_item, mExpiredTimersView, false);
1666d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        // Store the timer id as a tag on the view so it can be located on delete.
167b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen        timerItem.setId(timerId);
1686d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mExpiredTimersView.addView(timerItem);
1696d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1706d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        // Hide the label hint for expired timers.
1716d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final TextView labelView = (TextView) timerItem.findViewById(R.id.timer_label);
1726d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        labelView.setHint(null);
173df41926ee8cf9bf97866cc6c44a875eb82246fb6Christine Franks        labelView.setVisibility(TextUtils.isEmpty(timer.getLabel()) ? View.GONE : View.VISIBLE);
1746d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1756d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        // Add logic to the "Add 1 Minute" button.
1766d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final View addMinuteButton = timerItem.findViewById(R.id.reset_add);
1776d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        addMinuteButton.setOnClickListener(new View.OnClickListener() {
1786d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            @Override
1796d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            public void onClick(View v) {
180b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen                final Timer timer = DataModel.getDataModel().getTimer(timerId);
1816d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                DataModel.getDataModel().addTimerMinute(timer);
1826d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            }
1836d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        });
1846d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1856d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        // If the first timer was just added, center it.
1866d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final List<Timer> expiredTimers = getExpiredTimers();
1876d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        if (expiredTimers.size() == 1) {
1886d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            centerFirstTimer();
1896d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        } else if (expiredTimers.size() == 2) {
1906d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            uncenterFirstTimer();
1916d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
1926d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
1936d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
1946d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
1956d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * Remove an existing view that corresponds with the given {@code timer}.
1966d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
1976d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private void removeTimer(Timer timer) {
1986d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        TransitionManager.beginDelayedTransition(mExpiredTimersScrollView, new AutoTransition());
1996d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
200b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen        final int timerId = timer.getId();
201b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen        final int count = mExpiredTimersView.getChildCount();
202b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen        for (int i = 0; i < count; ++i) {
203b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen            final View timerView = mExpiredTimersView.getChildAt(i);
204b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen            if (timerView.getId() == timerId) {
205b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen                mExpiredTimersView.removeView(timerView);
206b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen                break;
207b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen            }
208b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen        }
2096d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
2106d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        // If the second last timer was just removed, center the last timer.
2116d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final List<Timer> expiredTimers = getExpiredTimers();
2126d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        if (expiredTimers.isEmpty()) {
2136d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            finish();
2146d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        } else if (expiredTimers.size() == 1) {
2156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            centerFirstTimer();
2166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
2176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
2186d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
2196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
2206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * Center the single timer.
2216d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
2226d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private void centerFirstTimer() {
2236d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final FrameLayout.LayoutParams lp =
2246d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                (FrameLayout.LayoutParams) mExpiredTimersView.getLayoutParams();
2256d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        lp.gravity = Gravity.CENTER;
2266d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mExpiredTimersView.requestLayout();
2276d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
2286d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
2296d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
2306d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * Display the multiple timers as a scrollable list.
2316d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
2326d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private void uncenterFirstTimer() {
2336d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        final FrameLayout.LayoutParams lp =
2346d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                (FrameLayout.LayoutParams) mExpiredTimersView.getLayoutParams();
2356d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        lp.gravity = Gravity.NO_GRAVITY;
2366d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        mExpiredTimersView.requestLayout();
2376d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
2386d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
2396d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private List<Timer> getExpiredTimers() {
2406d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        return DataModel.getDataModel().getExpiredTimers();
2416d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
2426d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
2436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
2446d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * Periodically refreshes the state of each timer.
2456d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
2466d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private class TimeUpdateRunnable implements Runnable {
2476d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        @Override
2486d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        public void run() {
2496d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            final long startTime = SystemClock.elapsedRealtime();
2506d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
251b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen            final int count = mExpiredTimersView.getChildCount();
252b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen            for (int i = 0; i < count; ++i) {
2536d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                final TimerItem timerItem = (TimerItem) mExpiredTimersView.getChildAt(i);
254b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen                final Timer timer = DataModel.getDataModel().getTimer(timerItem.getId());
255b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen                if (timer != null) {
256b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen                    timerItem.update(timer);
257b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen                }
2586d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            }
2596d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
2606d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            final long endTime = SystemClock.elapsedRealtime();
2616d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
2626d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            // Try to maintain a consistent period of time between redraws.
263b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen            final long delay = Math.max(0L, startTime + 20L - endTime);
2646d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            mExpiredTimersView.postDelayed(this, delay);
2656d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
2666d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
2676d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
2686d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
2696d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * Clicking the fab resets all expired timers.
2706d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
2716d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private class FabClickListener implements View.OnClickListener {
2726d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        @Override
2736d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        public void onClick(View v) {
2746d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            stopUpdatingTime();
2756d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            DataModel.getDataModel().removeTimerListener(mTimerChangeWatcher);
2766d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            DataModel.getDataModel().resetExpiredTimers(R.string.label_deskclock);
2776d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            finish();
2786d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
2796d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
2806d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
2816d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    /**
2826d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     * Adds and removes expired timers from this activity based on their state changes.
2836d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux     */
2846d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    private class TimerChangeWatcher implements TimerListener {
2856d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        @Override
2866d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        public void timerAdded(Timer timer) {
2876d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            if (timer.isExpired()) {
2886d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                addTimer(timer);
2896d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            }
2906d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
2916d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
2926d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        @Override
2936d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        public void timerUpdated(Timer before, Timer after) {
2946d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            if (!before.isExpired() && after.isExpired()) {
2956d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                addTimer(after);
2966d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            } else if (before.isExpired() && !after.isExpired()) {
2976d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                removeTimer(before);
2986d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            }
2996d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
3006d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux
3016d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        @Override
3026d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        public void timerRemoved(Timer timer) {
3036d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            if (timer.isExpired()) {
3046d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux                removeTimer(timer);
3056d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux            }
3066d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux        }
3076d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux    }
308b132d921bb64fc69145d040f18f504be6a2f0029Justin Klaassen}
309