Home | History | Annotate | Download | only in timer
      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
     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
     87     protected void onResume() {
     88         super.onResume();
     89         startUpdatingTime();
     90     }
     91 
     92     @Override
     93     protected void onPause() {
     94         super.onPause();
     95         stopUpdatingTime();
     96     }
     97 
     98     @Override
     99     public void onDestroy() {
    100         super.onDestroy();
    101         DataModel.getDataModel().removeTimerListener(mTimerChangeWatcher);
    102     }
    103 
    104     @Override
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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
    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
    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
    254         public void timerAdded(Timer timer) {
    255             if (timer.isExpired()) {
    256                 addTimer(timer);
    257             }
    258         }
    259 
    260         @Override
    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
    270         public void timerRemoved(Timer timer) {
    271             if (timer.isExpired()) {
    272                 removeTimer(timer);
    273             }
    274         }
    275     }
    276 }