blob: 2f9c260e6ef9e0c72dc688a7a6febc534b43ac04 [file] [log] [blame]
/*
* 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<Timer> 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().resetOrDeleteExpiredTimers(
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<Timer> 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<Timer> 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<Timer> 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().resetOrDeleteExpiredTimers(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);
}
}
}
}