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