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.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
     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
    108     protected void onResume() {
    109         super.onResume();
    110         startUpdatingTime();
    111     }
    112 
    113     @Override
    114     protected void onPause() {
    115         super.onPause();
    116         stopUpdatingTime();
    117     }
    118 
    119     @Override
    120     public void onDestroy() {
    121         super.onDestroy();
    122         DataModel.getDataModel().removeTimerListener(mTimerChangeWatcher);
    123     }
    124 
    125     @Override
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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
    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
    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
    286         public void timerAdded(Timer timer) {
    287             if (timer.isExpired()) {
    288                 addTimer(timer);
    289             }
    290         }
    291 
    292         @Override
    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
    302         public void timerRemoved(Timer timer) {
    303             if (timer.isExpired()) {
    304                 removeTimer(timer);
    305             }
    306         }
    307     }
    308 }
    309