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().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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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
    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
    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
    287         public void timerAdded(Timer timer) {
    288             if (timer.isExpired()) {
    289                 addTimer(timer);
    290             }
    291         }
    292 
    293         @Override
    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
    303         public void timerRemoved(Timer timer) {
    304             if (timer.isExpired()) {
    305                 removeTimer(timer);
    306             }
    307         }
    308     }
    309 }
    310