SetAlarm.java revision 748e9ada696912c262a097d4211fde528f1cc0b8
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.deskclock;
18
19import android.app.ActionBar;
20import android.app.AlertDialog;
21import android.app.TimePickerDialog;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.content.DialogInterface.OnCancelListener;
25import android.content.Intent;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.Vibrator;
29import android.preference.CheckBoxPreference;
30import android.preference.Preference;
31import android.preference.PreferenceActivity;
32import android.preference.PreferenceScreen;
33import android.text.format.DateFormat;
34import android.view.LayoutInflater;
35import android.view.Menu;
36import android.view.MenuItem;
37import android.view.View;
38import android.view.View.OnClickListener;
39import android.widget.Button;
40import android.widget.EditText;
41import android.widget.ListView;
42import android.widget.TimePicker;
43import android.widget.Toast;
44
45/**
46 * Manages each alarm
47 */
48public class SetAlarm extends PreferenceActivity implements Preference.OnPreferenceChangeListener,
49        TimePickerDialog.OnTimeSetListener, OnCancelListener {
50
51    private static final String KEY_CURRENT_ALARM = "currentAlarm";
52    private static final String KEY_ORIGINAL_ALARM = "originalAlarm";
53    private static final String KEY_TIME_PICKER_BUNDLE = "timePickerBundle";
54
55    private EditText mLabel;
56    private CheckBoxPreference mEnabledPref;
57    private Preference mTimePref;
58    private AlarmPreference mAlarmPref;
59    private CheckBoxPreference mVibratePref;
60    private RepeatPreference mRepeatPref;
61
62    private int     mId;
63    private int     mHour;
64    private int     mMinute;
65    private TimePickerDialog mTimePickerDialog;
66    private Alarm   mOriginalAlarm;
67
68    @Override
69    protected void onCreate(Bundle icicle) {
70        super.onCreate(icicle);
71
72        // Override the default content view.
73        setContentView(R.layout.set_alarm);
74
75        EditText label = (EditText) getLayoutInflater().inflate(R.layout.alarm_label, null);
76        ListView list = (ListView) findViewById(android.R.id.list);
77        list.addFooterView(label);
78
79        // TODO Stop using preferences for this view. Save on done, not after
80        // each change.
81        addPreferencesFromResource(R.xml.alarm_prefs);
82
83        // Get each preference so we can retrieve the value later.
84        mLabel = label;
85        mEnabledPref = (CheckBoxPreference) findPreference("enabled");
86        mEnabledPref.setOnPreferenceChangeListener(this);
87        mTimePref = findPreference("time");
88        mAlarmPref = (AlarmPreference) findPreference("alarm");
89        mAlarmPref.setOnPreferenceChangeListener(this);
90        mVibratePref = (CheckBoxPreference) findPreference("vibrate");
91        mVibratePref.setOnPreferenceChangeListener(this);
92        Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
93        if (!v.hasVibrator()) {
94            getPreferenceScreen().removePreference(mVibratePref);
95        }
96        mRepeatPref = (RepeatPreference) findPreference("setRepeat");
97        mRepeatPref.setOnPreferenceChangeListener(this);
98
99        Intent i = getIntent();
100        Alarm alarm = i.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA);
101
102        if (alarm == null) {
103            // No alarm means create a new alarm.
104            alarm = new Alarm();
105        }
106        mOriginalAlarm = alarm;
107
108        // Populate the prefs with the original alarm data.  updatePrefs also
109        // sets mId so it must be called before checking mId below.
110        updatePrefs(mOriginalAlarm);
111
112        // We have to do this to get the save/cancel buttons to highlight on
113        // their own.
114        getListView().setItemsCanFocus(true);
115
116        ActionBar actionBar = getActionBar();
117        if (actionBar != null) {
118            actionBar.setDisplayOptions(
119                    0, ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
120            LayoutInflater inflater = (LayoutInflater) getSystemService
121                    (Context.LAYOUT_INFLATER_SERVICE);
122            View customActionBarView = inflater.inflate(R.layout.set_alarm_action_bar, null);
123            actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
124                    ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME |
125                    ActionBar.DISPLAY_SHOW_TITLE);
126            actionBar.setCustomView(customActionBarView);
127            View saveMenuItem = customActionBarView.findViewById(R.id.save_menu_item);
128            saveMenuItem.setOnClickListener(new OnClickListener() {
129                @Override
130                public void onClick(View v) {
131                    saveAndExit();
132                }
133            });
134        }
135    }
136
137    @Override
138    public boolean onOptionsItemSelected(MenuItem item) {
139        if (item.getItemId() == R.id.menu_delete) {
140            deleteAlarm();
141            return true;
142        }
143        return super.onOptionsItemSelected(item);
144    }
145
146    @Override
147    public boolean onCreateOptionsMenu(Menu menu) {
148        getMenuInflater().inflate(R.menu.set_alarm_context, menu);
149        return true;
150    }
151
152    @Override
153    protected void onSaveInstanceState(Bundle outState) {
154        super.onSaveInstanceState(outState);
155        outState.putParcelable(KEY_ORIGINAL_ALARM, mOriginalAlarm);
156        outState.putParcelable(KEY_CURRENT_ALARM, buildAlarmFromUi());
157        if (mTimePickerDialog != null) {
158            if (mTimePickerDialog.isShowing()) {
159                outState.putParcelable(KEY_TIME_PICKER_BUNDLE, mTimePickerDialog
160                        .onSaveInstanceState());
161                mTimePickerDialog.dismiss();
162            }
163            mTimePickerDialog = null;
164        }
165    }
166
167    @Override
168    protected void onRestoreInstanceState(Bundle state) {
169        super.onRestoreInstanceState(state);
170
171        Alarm alarmFromBundle = state.getParcelable(KEY_ORIGINAL_ALARM);
172        if (alarmFromBundle != null) {
173            mOriginalAlarm = alarmFromBundle;
174        }
175
176        alarmFromBundle = state.getParcelable(KEY_CURRENT_ALARM);
177        if (alarmFromBundle != null) {
178            updatePrefs(alarmFromBundle);
179        }
180
181        Bundle b = state.getParcelable(KEY_TIME_PICKER_BUNDLE);
182        if (b != null) {
183            showTimePicker();
184            mTimePickerDialog.onRestoreInstanceState(b);
185        }
186    }
187
188    // Used to post runnables asynchronously.
189    private static final Handler sHandler = new Handler();
190
191    public boolean onPreferenceChange(final Preference p, Object newValue) {
192        // Asynchronously save the alarm since this method is called _before_
193        // the value of the preference has changed.
194        sHandler.post(new Runnable() {
195            public void run() {
196                // Editing any preference (except enable) enables the alarm.
197                if (p != mEnabledPref) {
198                    mEnabledPref.setChecked(true);
199                }
200                saveAlarm(null);
201            }
202        });
203        return true;
204    }
205
206    private void updatePrefs(Alarm alarm) {
207        mId = alarm.id;
208        mEnabledPref.setChecked(alarm.enabled);
209        mLabel.setText(alarm.label);
210        mHour = alarm.hour;
211        mMinute = alarm.minutes;
212        mRepeatPref.setDaysOfWeek(alarm.daysOfWeek);
213        mVibratePref.setChecked(alarm.vibrate);
214        // Give the alert uri to the preference.
215        mAlarmPref.setAlert(alarm.alert);
216        updateTime();
217    }
218
219    @Override
220    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
221            Preference preference) {
222        if (preference == mTimePref) {
223            showTimePicker();
224        }
225
226        return super.onPreferenceTreeClick(preferenceScreen, preference);
227    }
228
229    @Override
230    public void onBackPressed() {
231        saveAndExit();
232    }
233
234    private void showTimePicker() {
235        if (mTimePickerDialog != null) {
236            if (mTimePickerDialog.isShowing()) {
237                Log.e("mTimePickerDialog is already showing.");
238                mTimePickerDialog.dismiss();
239            } else {
240                Log.e("mTimePickerDialog is not null");
241            }
242            mTimePickerDialog = null;
243        }
244
245        mTimePickerDialog = new TimePickerDialog(this, this, mHour, mMinute,
246                DateFormat.is24HourFormat(this));
247        mTimePickerDialog.setOnCancelListener(this);
248        mTimePickerDialog.show();
249    }
250
251    public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
252        // onTimeSet is called when the user clicks "Set"
253        mTimePickerDialog = null;
254        mHour = hourOfDay;
255        mMinute = minute;
256        updateTime();
257        // If the time has been changed, enable the alarm.
258        mEnabledPref.setChecked(true);
259        saveAlarm(null);
260    }
261
262    @Override
263    public void onCancel(DialogInterface dialog) {
264        mTimePickerDialog = null;
265    }
266
267    private void updateTime() {
268        mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinute,
269                mRepeatPref.getDaysOfWeek()));
270    }
271
272    private long saveAlarm(Alarm alarm) {
273        if (alarm == null) {
274            alarm = buildAlarmFromUi();
275        }
276
277        long time;
278        if (alarm.id == -1) {
279            time = Alarms.addAlarm(this, alarm);
280            // addAlarm populates the alarm with the new id. Update mId so that
281            // changes to other preferences update the new alarm.
282            mId = alarm.id;
283        } else {
284            time = Alarms.setAlarm(this, alarm);
285        }
286        return time;
287    }
288
289    private Alarm buildAlarmFromUi() {
290        Alarm alarm = new Alarm();
291        alarm.id = mId;
292        alarm.enabled = mEnabledPref.isChecked();
293        alarm.hour = mHour;
294        alarm.minutes = mMinute;
295        alarm.daysOfWeek = mRepeatPref.getDaysOfWeek();
296        alarm.vibrate = mVibratePref.isChecked();
297        alarm.label = mLabel.getText().toString();
298        alarm.alert = mAlarmPref.getAlert();
299        return alarm;
300    }
301
302    private void deleteAlarm() {
303        if (mId == -1) {
304            // Unedited, newly created alarms don't require confirmation
305            finish();
306        } else {
307            new AlertDialog.Builder(this)
308                    .setTitle(getString(R.string.delete_alarm))
309                    .setMessage(getString(R.string.delete_alarm_confirm))
310                    .setPositiveButton(android.R.string.ok,
311                            new DialogInterface.OnClickListener() {
312                                public void onClick(DialogInterface d, int w) {
313                                    Alarms.deleteAlarm(SetAlarm.this, mId);
314                                    finish();
315                                }
316                            })
317                    .setNegativeButton(android.R.string.cancel, null)
318                    .show();
319        }
320    }
321
322    /**
323     * Store any changes to the alarm and exit the activity.
324     * Show a toast if the alarm is enabled with the time remaining until alarm
325     */
326    private void saveAndExit() {
327        long time = saveAlarm(null);
328        if(mEnabledPref.isChecked()) {
329            popAlarmSetToast(SetAlarm.this, time);
330        }
331        finish();
332    }
333
334    /**
335     * Display a toast that tells the user how long until the alarm
336     * goes off.  This helps prevent "am/pm" mistakes.
337     */
338    static void popAlarmSetToast(Context context, int hour, int minute,
339                                 Alarm.DaysOfWeek daysOfWeek) {
340        popAlarmSetToast(context,
341                Alarms.calculateAlarm(hour, minute, daysOfWeek)
342                .getTimeInMillis());
343    }
344
345    static void popAlarmSetToast(Context context, long timeInMillis) {
346        String toastText = formatToast(context, timeInMillis);
347        Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG);
348        ToastMaster.setToast(toast);
349        toast.show();
350    }
351
352    /**
353     * format "Alarm set for 2 days 7 hours and 53 minutes from
354     * now"
355     */
356    static String formatToast(Context context, long timeInMillis) {
357        long delta = timeInMillis - System.currentTimeMillis();
358        long hours = delta / (1000 * 60 * 60);
359        long minutes = delta / (1000 * 60) % 60;
360        long days = hours / 24;
361        hours = hours % 24;
362
363        String daySeq = (days == 0) ? "" :
364                (days == 1) ? context.getString(R.string.day) :
365                context.getString(R.string.days, Long.toString(days));
366
367        String minSeq = (minutes == 0) ? "" :
368                (minutes == 1) ? context.getString(R.string.minute) :
369                context.getString(R.string.minutes, Long.toString(minutes));
370
371        String hourSeq = (hours == 0) ? "" :
372                (hours == 1) ? context.getString(R.string.hour) :
373                context.getString(R.string.hours, Long.toString(hours));
374
375        boolean dispDays = days > 0;
376        boolean dispHour = hours > 0;
377        boolean dispMinute = minutes > 0;
378
379        int index = (dispDays ? 1 : 0) |
380                    (dispHour ? 2 : 0) |
381                    (dispMinute ? 4 : 0);
382
383        String[] formats = context.getResources().getStringArray(R.array.alarm_set);
384        return String.format(formats[index], daySeq, hourSeq, minSeq);
385    }
386}
387