/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.android.deskclock.timer; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Bundle; import android.os.SystemClock; import android.support.annotation.NonNull; import android.text.TextUtils; import android.transition.AutoTransition; import android.transition.TransitionManager; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.TextView; import com.android.deskclock.BaseActivity; import com.android.deskclock.LogUtils; import com.android.deskclock.R; import com.android.deskclock.data.DataModel; import com.android.deskclock.data.Timer; import com.android.deskclock.data.TimerListener; import java.util.List; /** * This activity is designed to be shown over the lock screen. As such, it displays the expired * timers and a single button to reset them all. Each expired timer can also be reset to one minute * with a button in the user interface. All other timer operations are disabled in this activity. */ public class ExpiredTimersActivity extends BaseActivity { /** Scheduled to update the timers while at least one is expired. */ private final Runnable mTimeUpdateRunnable = new TimeUpdateRunnable(); /** Updates the timers displayed in this activity as the backing data changes. */ private final TimerListener mTimerChangeWatcher = new TimerChangeWatcher(); /** The scene root for transitions when expired timers are added/removed from this container. */ private ViewGroup mExpiredTimersScrollView; /** Displays the expired timers. */ private ViewGroup mExpiredTimersView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final List expiredTimers = getExpiredTimers(); // If no expired timers, finish if (expiredTimers.size() == 0) { LogUtils.i("No expired timers, skipping display."); finish(); return; } setContentView(R.layout.expired_timers_activity); mExpiredTimersView = (ViewGroup) findViewById(R.id.expired_timers_list); mExpiredTimersScrollView = (ViewGroup) findViewById(R.id.expired_timers_scroll); findViewById(R.id.fab).setOnClickListener(new FabClickListener()); final View view = findViewById(R.id.expired_timers_activity); view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON); // Close dialogs and window shade, so this is fully visible sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); // Honor rotation on tablets; fix the orientation on phones. if (!getResources().getBoolean(R.bool.rotateAlarmAlert)) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); } // Create views for each of the expired timers. for (Timer timer : expiredTimers) { addTimer(timer); } // Update views in response to timer data changes. DataModel.getDataModel().addTimerListener(mTimerChangeWatcher); } @Override protected void onResume() { super.onResume(); startUpdatingTime(); } @Override protected void onPause() { super.onPause(); stopUpdatingTime(); } @Override public void onDestroy() { super.onDestroy(); DataModel.getDataModel().removeTimerListener(mTimerChangeWatcher); } @Override public boolean dispatchKeyEvent(@NonNull KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_UP) { switch (event.getKeyCode()) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: case KeyEvent.KEYCODE_CAMERA: case KeyEvent.KEYCODE_FOCUS: DataModel.getDataModel().resetExpiredTimers(R.string.label_hardware_button); return true; } } return super.dispatchKeyEvent(event); } /** * Post the first runnable to update times within the UI. It will reschedule itself as needed. */ private void startUpdatingTime() { // Ensure only one copy of the runnable is ever scheduled by first stopping updates. stopUpdatingTime(); mExpiredTimersView.post(mTimeUpdateRunnable); } /** * Remove the runnable that updates times within the UI. */ private void stopUpdatingTime() { mExpiredTimersView.removeCallbacks(mTimeUpdateRunnable); } /** * Create and add a new view that corresponds with the given {@code timer}. */ private void addTimer(Timer timer) { TransitionManager.beginDelayedTransition(mExpiredTimersScrollView, new AutoTransition()); final int timerId = timer.getId(); final TimerItem timerItem = (TimerItem) getLayoutInflater().inflate(R.layout.timer_item, mExpiredTimersView, false); // Store the timer id as a tag on the view so it can be located on delete. timerItem.setId(timerId); mExpiredTimersView.addView(timerItem); // Hide the label hint for expired timers. final TextView labelView = (TextView) timerItem.findViewById(R.id.timer_label); labelView.setHint(null); labelView.setVisibility(TextUtils.isEmpty(timer.getLabel()) ? View.GONE : View.VISIBLE); // Add logic to the "Add 1 Minute" button. final View addMinuteButton = timerItem.findViewById(R.id.reset_add); addMinuteButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final Timer timer = DataModel.getDataModel().getTimer(timerId); DataModel.getDataModel().addTimerMinute(timer); } }); // If the first timer was just added, center it. final List expiredTimers = getExpiredTimers(); if (expiredTimers.size() == 1) { centerFirstTimer(); } else if (expiredTimers.size() == 2) { uncenterFirstTimer(); } } /** * Remove an existing view that corresponds with the given {@code timer}. */ private void removeTimer(Timer timer) { TransitionManager.beginDelayedTransition(mExpiredTimersScrollView, new AutoTransition()); final int timerId = timer.getId(); final int count = mExpiredTimersView.getChildCount(); for (int i = 0; i < count; ++i) { final View timerView = mExpiredTimersView.getChildAt(i); if (timerView.getId() == timerId) { mExpiredTimersView.removeView(timerView); break; } } // If the second last timer was just removed, center the last timer. final List expiredTimers = getExpiredTimers(); if (expiredTimers.isEmpty()) { finish(); } else if (expiredTimers.size() == 1) { centerFirstTimer(); } } /** * Center the single timer. */ private void centerFirstTimer() { final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mExpiredTimersView.getLayoutParams(); lp.gravity = Gravity.CENTER; mExpiredTimersView.requestLayout(); } /** * Display the multiple timers as a scrollable list. */ private void uncenterFirstTimer() { final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mExpiredTimersView.getLayoutParams(); lp.gravity = Gravity.NO_GRAVITY; mExpiredTimersView.requestLayout(); } private List getExpiredTimers() { return DataModel.getDataModel().getExpiredTimers(); } /** * Periodically refreshes the state of each timer. */ private class TimeUpdateRunnable implements Runnable { @Override public void run() { final long startTime = SystemClock.elapsedRealtime(); final int count = mExpiredTimersView.getChildCount(); for (int i = 0; i < count; ++i) { final TimerItem timerItem = (TimerItem) mExpiredTimersView.getChildAt(i); final Timer timer = DataModel.getDataModel().getTimer(timerItem.getId()); if (timer != null) { timerItem.update(timer); } } final long endTime = SystemClock.elapsedRealtime(); // Try to maintain a consistent period of time between redraws. final long delay = Math.max(0L, startTime + 20L - endTime); mExpiredTimersView.postDelayed(this, delay); } } /** * Clicking the fab resets all expired timers. */ private class FabClickListener implements View.OnClickListener { @Override public void onClick(View v) { stopUpdatingTime(); DataModel.getDataModel().removeTimerListener(mTimerChangeWatcher); DataModel.getDataModel().resetExpiredTimers(R.string.label_deskclock); finish(); } } /** * Adds and removes expired timers from this activity based on their state changes. */ private class TimerChangeWatcher implements TimerListener { @Override public void timerAdded(Timer timer) { if (timer.isExpired()) { addTimer(timer); } } @Override public void timerUpdated(Timer before, Timer after) { if (!before.isExpired() && after.isExpired()) { addTimer(after); } else if (before.isExpired() && !after.isExpired()) { removeTimer(before); } } @Override public void timerRemoved(Timer timer) { if (timer.isExpired()) { removeTimer(timer); } } } }