Home | History | Annotate | Download | only in deskclock
      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 
     17 package com.android.deskclock;
     18 
     19 import android.app.ActionBar;
     20 import android.app.AlertDialog;
     21 import android.app.TimePickerDialog;
     22 import android.content.Context;
     23 import android.content.DialogInterface;
     24 import android.content.DialogInterface.OnCancelListener;
     25 import android.content.Intent;
     26 import android.os.Bundle;
     27 import android.os.Handler;
     28 import android.os.Vibrator;
     29 import android.preference.CheckBoxPreference;
     30 import android.preference.Preference;
     31 import android.preference.PreferenceActivity;
     32 import android.preference.PreferenceScreen;
     33 import android.text.format.DateFormat;
     34 import android.view.LayoutInflater;
     35 import android.view.Menu;
     36 import android.view.MenuItem;
     37 import android.view.View;
     38 import android.view.View.OnClickListener;
     39 import android.widget.Button;
     40 import android.widget.EditText;
     41 import android.widget.ListView;
     42 import android.widget.TimePicker;
     43 import android.widget.Toast;
     44 
     45 /**
     46  * Manages each alarm
     47  */
     48 public 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         // Attach actions to each button.
    137         Button b = (Button) findViewById(R.id.alarm_save);
    138         if (b != null) {
    139             b.setOnClickListener(new View.OnClickListener() {
    140                     public void onClick(View v) {
    141                         long time = saveAlarm(null);
    142                         if(mEnabledPref.isChecked()) {
    143                             popAlarmSetToast(SetAlarm.this, time);
    144                         }
    145                         finish();
    146                     }
    147             });
    148         }
    149         b = (Button) findViewById(R.id.alarm_revert);
    150         if (b != null) {
    151             b.setOnClickListener(new View.OnClickListener() {
    152                 public void onClick(View v) {
    153                     revert();
    154                     finish();
    155                 }
    156             });
    157         }
    158         b = (Button) findViewById(R.id.alarm_delete);
    159         if (b != null) {
    160             if (mId == -1) {
    161                 b.setEnabled(false);
    162                 b.setVisibility(View.GONE);
    163             } else {
    164                 b.setVisibility(View.VISIBLE);
    165                 b.setOnClickListener(new View.OnClickListener() {
    166                     public void onClick(View v) {
    167                         deleteAlarm();
    168                     }
    169                 });
    170             }
    171         }
    172 }
    173 
    174     @Override
    175     public boolean onOptionsItemSelected(MenuItem item) {
    176         if (item.getItemId() == R.id.menu_delete) {
    177             deleteAlarm();
    178             return true;
    179         }
    180         return super.onOptionsItemSelected(item);
    181     }
    182 
    183     @Override
    184     public boolean onCreateOptionsMenu(Menu menu) {
    185         getMenuInflater().inflate(R.menu.set_alarm_context, menu);
    186         return true;
    187     }
    188 
    189     @Override
    190     protected void onSaveInstanceState(Bundle outState) {
    191         super.onSaveInstanceState(outState);
    192         outState.putParcelable(KEY_ORIGINAL_ALARM, mOriginalAlarm);
    193         outState.putParcelable(KEY_CURRENT_ALARM, buildAlarmFromUi());
    194         if (mTimePickerDialog != null) {
    195             if (mTimePickerDialog.isShowing()) {
    196                 outState.putParcelable(KEY_TIME_PICKER_BUNDLE, mTimePickerDialog
    197                         .onSaveInstanceState());
    198                 mTimePickerDialog.dismiss();
    199             }
    200             mTimePickerDialog = null;
    201         }
    202     }
    203 
    204     @Override
    205     protected void onRestoreInstanceState(Bundle state) {
    206         super.onRestoreInstanceState(state);
    207 
    208         Alarm alarmFromBundle = state.getParcelable(KEY_ORIGINAL_ALARM);
    209         if (alarmFromBundle != null) {
    210             mOriginalAlarm = alarmFromBundle;
    211         }
    212 
    213         alarmFromBundle = state.getParcelable(KEY_CURRENT_ALARM);
    214         if (alarmFromBundle != null) {
    215             updatePrefs(alarmFromBundle);
    216         }
    217 
    218         Bundle b = state.getParcelable(KEY_TIME_PICKER_BUNDLE);
    219         if (b != null) {
    220             showTimePicker();
    221             mTimePickerDialog.onRestoreInstanceState(b);
    222         }
    223     }
    224 
    225     // Used to post runnables asynchronously.
    226     private static final Handler sHandler = new Handler();
    227 
    228     public boolean onPreferenceChange(final Preference p, Object newValue) {
    229         // Asynchronously save the alarm since this method is called _before_
    230         // the value of the preference has changed.
    231         sHandler.post(new Runnable() {
    232             public void run() {
    233                 // Editing any preference (except enable) enables the alarm.
    234                 if (p != mEnabledPref) {
    235                     mEnabledPref.setChecked(true);
    236                 }
    237                 saveAlarm(null);
    238             }
    239         });
    240         return true;
    241     }
    242 
    243     private void updatePrefs(Alarm alarm) {
    244         mId = alarm.id;
    245         mEnabledPref.setChecked(alarm.enabled);
    246         mLabel.setText(alarm.label);
    247         mHour = alarm.hour;
    248         mMinute = alarm.minutes;
    249         mRepeatPref.setDaysOfWeek(alarm.daysOfWeek);
    250         mVibratePref.setChecked(alarm.vibrate);
    251         // Give the alert uri to the preference.
    252         mAlarmPref.setAlert(alarm.alert);
    253         updateTime();
    254     }
    255 
    256     @Override
    257     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
    258             Preference preference) {
    259         if (preference == mTimePref) {
    260             showTimePicker();
    261         }
    262 
    263         return super.onPreferenceTreeClick(preferenceScreen, preference);
    264     }
    265 
    266     @Override
    267     public void onBackPressed() {
    268         saveAndExit();
    269     }
    270 
    271     private void showTimePicker() {
    272         if (mTimePickerDialog != null) {
    273             if (mTimePickerDialog.isShowing()) {
    274                 Log.e("mTimePickerDialog is already showing.");
    275                 mTimePickerDialog.dismiss();
    276             } else {
    277                 Log.e("mTimePickerDialog is not null");
    278             }
    279             mTimePickerDialog = null;
    280         }
    281 
    282         mTimePickerDialog = new TimePickerDialog(this, this, mHour, mMinute,
    283                 DateFormat.is24HourFormat(this));
    284         mTimePickerDialog.setOnCancelListener(this);
    285         mTimePickerDialog.show();
    286     }
    287 
    288     public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
    289         // onTimeSet is called when the user clicks "Set"
    290         mTimePickerDialog = null;
    291         mHour = hourOfDay;
    292         mMinute = minute;
    293         updateTime();
    294         // If the time has been changed, enable the alarm.
    295         mEnabledPref.setChecked(true);
    296         saveAlarm(null);
    297     }
    298 
    299     @Override
    300     public void onCancel(DialogInterface dialog) {
    301         mTimePickerDialog = null;
    302     }
    303 
    304     private void updateTime() {
    305         mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinute,
    306                 mRepeatPref.getDaysOfWeek()));
    307     }
    308 
    309     private long saveAlarm(Alarm alarm) {
    310         if (alarm == null) {
    311             alarm = buildAlarmFromUi();
    312         }
    313 
    314         long time;
    315         if (alarm.id == -1) {
    316             time = Alarms.addAlarm(this, alarm);
    317             // addAlarm populates the alarm with the new id. Update mId so that
    318             // changes to other preferences update the new alarm.
    319             mId = alarm.id;
    320         } else {
    321             time = Alarms.setAlarm(this, alarm);
    322         }
    323         return time;
    324     }
    325 
    326     private Alarm buildAlarmFromUi() {
    327         Alarm alarm = new Alarm();
    328         alarm.id = mId;
    329         alarm.enabled = mEnabledPref.isChecked();
    330         alarm.hour = mHour;
    331         alarm.minutes = mMinute;
    332         alarm.daysOfWeek = mRepeatPref.getDaysOfWeek();
    333         alarm.vibrate = mVibratePref.isChecked();
    334         alarm.label = mLabel.getText().toString();
    335         alarm.alert = mAlarmPref.getAlert();
    336         return alarm;
    337     }
    338 
    339     private void deleteAlarm() {
    340         if (mId == -1) {
    341             // Unedited, newly created alarms don't require confirmation
    342             finish();
    343         } else {
    344             new AlertDialog.Builder(this)
    345                     .setTitle(getString(R.string.delete_alarm))
    346                     .setMessage(getString(R.string.delete_alarm_confirm))
    347                     .setPositiveButton(android.R.string.ok,
    348                             new DialogInterface.OnClickListener() {
    349                                 public void onClick(DialogInterface d, int w) {
    350                                     Alarms.deleteAlarm(SetAlarm.this, mId);
    351                                     finish();
    352                                 }
    353                             })
    354                     .setNegativeButton(android.R.string.cancel, null)
    355                     .show();
    356         }
    357     }
    358 
    359     private void revert() {
    360         int newId = mId;
    361         // "Revert" on a newly created alarm should delete it.
    362         if (mOriginalAlarm.id == -1) {
    363             Alarms.deleteAlarm(SetAlarm.this, newId);
    364         } else {
    365             saveAlarm(mOriginalAlarm);
    366         }
    367     }
    368 
    369     /**
    370      * Store any changes to the alarm and exit the activity.
    371      * Show a toast if the alarm is enabled with the time remaining until alarm
    372      */
    373     private void saveAndExit() {
    374         long time = saveAlarm(null);
    375         if(mEnabledPref.isChecked()) {
    376             popAlarmSetToast(SetAlarm.this, time);
    377         }
    378         finish();
    379     }
    380 
    381     /**
    382      * Display a toast that tells the user how long until the alarm
    383      * goes off.  This helps prevent "am/pm" mistakes.
    384      */
    385     static void popAlarmSetToast(Context context, int hour, int minute,
    386                                  Alarm.DaysOfWeek daysOfWeek) {
    387         popAlarmSetToast(context,
    388                 Alarms.calculateAlarm(hour, minute, daysOfWeek)
    389                 .getTimeInMillis());
    390     }
    391 
    392     static void popAlarmSetToast(Context context, long timeInMillis) {
    393         String toastText = formatToast(context, timeInMillis);
    394         Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG);
    395         ToastMaster.setToast(toast);
    396         toast.show();
    397     }
    398 
    399     /**
    400      * format "Alarm set for 2 days 7 hours and 53 minutes from
    401      * now"
    402      */
    403     static String formatToast(Context context, long timeInMillis) {
    404         long delta = timeInMillis - System.currentTimeMillis();
    405         long hours = delta / (1000 * 60 * 60);
    406         long minutes = delta / (1000 * 60) % 60;
    407         long days = hours / 24;
    408         hours = hours % 24;
    409 
    410         String daySeq = (days == 0) ? "" :
    411                 (days == 1) ? context.getString(R.string.day) :
    412                 context.getString(R.string.days, Long.toString(days));
    413 
    414         String minSeq = (minutes == 0) ? "" :
    415                 (minutes == 1) ? context.getString(R.string.minute) :
    416                 context.getString(R.string.minutes, Long.toString(minutes));
    417 
    418         String hourSeq = (hours == 0) ? "" :
    419                 (hours == 1) ? context.getString(R.string.hour) :
    420                 context.getString(R.string.hours, Long.toString(hours));
    421 
    422         boolean dispDays = days > 0;
    423         boolean dispHour = hours > 0;
    424         boolean dispMinute = minutes > 0;
    425 
    426         int index = (dispDays ? 1 : 0) |
    427                     (dispHour ? 2 : 0) |
    428                     (dispMinute ? 4 : 0);
    429 
    430         String[] formats = context.getResources().getStringArray(R.array.alarm_set);
    431         return String.format(formats[index], daySeq, hourSeq, minSeq);
    432     }
    433 }
    434