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.AlertDialog; 20 import android.app.TimePickerDialog; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.DialogInterface.OnCancelListener; 24 import android.content.Intent; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.Vibrator; 28 import android.preference.CheckBoxPreference; 29 import android.preference.Preference; 30 import android.preference.PreferenceActivity; 31 import android.preference.PreferenceScreen; 32 import android.text.format.DateFormat; 33 import android.view.View; 34 import android.widget.Button; 35 import android.widget.EditText; 36 import android.widget.ListView; 37 import android.widget.TimePicker; 38 import android.widget.Toast; 39 40 /** 41 * Manages each alarm 42 */ 43 public class SetAlarm extends PreferenceActivity implements Preference.OnPreferenceChangeListener, 44 TimePickerDialog.OnTimeSetListener, OnCancelListener { 45 private static final String KEY_CURRENT_ALARM = "currentAlarm"; 46 private static final String KEY_ORIGINAL_ALARM = "originalAlarm"; 47 private static final String KEY_TIME_PICKER_BUNDLE = "timePickerBundle"; 48 49 private EditText mLabel; 50 private CheckBoxPreference mEnabledPref; 51 private Preference mTimePref; 52 private AlarmPreference mAlarmPref; 53 private CheckBoxPreference mVibratePref; 54 private RepeatPreference mRepeatPref; 55 56 private int mId; 57 private int mHour; 58 private int mMinute; 59 private TimePickerDialog mTimePickerDialog; 60 private Alarm mOriginalAlarm; 61 62 @Override onCreate(Bundle icicle)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 // Attach actions to each button. 111 Button b = (Button) findViewById(R.id.alarm_save); 112 b.setOnClickListener(new View.OnClickListener() { 113 public void onClick(View v) { 114 long time = saveAlarm(null); 115 if(mEnabledPref.isChecked()) { 116 popAlarmSetToast(SetAlarm.this, time); 117 } 118 finish(); 119 } 120 }); 121 Button revert = (Button) findViewById(R.id.alarm_revert); 122 revert.setOnClickListener(new View.OnClickListener() { 123 public void onClick(View v) { 124 revert(); 125 finish(); 126 } 127 }); 128 b = (Button) findViewById(R.id.alarm_delete); 129 if (mId == -1) { 130 b.setEnabled(false); 131 b.setVisibility(View.GONE); 132 } else { 133 b.setVisibility(View.VISIBLE); 134 b.setOnClickListener(new View.OnClickListener() { 135 public void onClick(View v) { 136 deleteAlarm(); 137 } 138 }); 139 } 140 } 141 142 @Override onSaveInstanceState(Bundle outState)143 protected void onSaveInstanceState(Bundle outState) { 144 super.onSaveInstanceState(outState); 145 outState.putParcelable(KEY_ORIGINAL_ALARM, mOriginalAlarm); 146 outState.putParcelable(KEY_CURRENT_ALARM, buildAlarmFromUi()); 147 if (mTimePickerDialog != null) { 148 if (mTimePickerDialog.isShowing()) { 149 outState.putParcelable(KEY_TIME_PICKER_BUNDLE, mTimePickerDialog 150 .onSaveInstanceState()); 151 mTimePickerDialog.dismiss(); 152 } 153 mTimePickerDialog = null; 154 } 155 } 156 157 @Override onRestoreInstanceState(Bundle state)158 protected void onRestoreInstanceState(Bundle state) { 159 super.onRestoreInstanceState(state); 160 161 Alarm alarmFromBundle = state.getParcelable(KEY_ORIGINAL_ALARM); 162 if (alarmFromBundle != null) { 163 mOriginalAlarm = alarmFromBundle; 164 } 165 166 alarmFromBundle = state.getParcelable(KEY_CURRENT_ALARM); 167 if (alarmFromBundle != null) { 168 updatePrefs(alarmFromBundle); 169 } 170 171 Bundle b = state.getParcelable(KEY_TIME_PICKER_BUNDLE); 172 if (b != null) { 173 showTimePicker(); 174 mTimePickerDialog.onRestoreInstanceState(b); 175 } 176 } 177 178 // Used to post runnables asynchronously. 179 private static final Handler sHandler = new Handler(); 180 onPreferenceChange(final Preference p, Object newValue)181 public boolean onPreferenceChange(final Preference p, Object newValue) { 182 // Asynchronously save the alarm since this method is called _before_ 183 // the value of the preference has changed. 184 sHandler.post(new Runnable() { 185 public void run() { 186 // Editing any preference (except enable) enables the alarm. 187 if (p != mEnabledPref) { 188 mEnabledPref.setChecked(true); 189 } 190 saveAlarm(null); 191 } 192 }); 193 return true; 194 } 195 updatePrefs(Alarm alarm)196 private void updatePrefs(Alarm alarm) { 197 mId = alarm.id; 198 mEnabledPref.setChecked(alarm.enabled); 199 mLabel.setText(alarm.label); 200 mHour = alarm.hour; 201 mMinute = alarm.minutes; 202 mRepeatPref.setDaysOfWeek(alarm.daysOfWeek); 203 mVibratePref.setChecked(alarm.vibrate); 204 // Give the alert uri to the preference. 205 mAlarmPref.setAlert(alarm.alert); 206 updateTime(); 207 } 208 209 @Override onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)210 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, 211 Preference preference) { 212 if (preference == mTimePref) { 213 showTimePicker(); 214 } 215 216 return super.onPreferenceTreeClick(preferenceScreen, preference); 217 } 218 219 @Override onBackPressed()220 public void onBackPressed() { 221 revert(); 222 finish(); 223 } 224 showTimePicker()225 private void showTimePicker() { 226 if (mTimePickerDialog != null) { 227 if (mTimePickerDialog.isShowing()) { 228 Log.e("mTimePickerDialog is already showing."); 229 mTimePickerDialog.dismiss(); 230 } else { 231 Log.e("mTimePickerDialog is not null"); 232 } 233 mTimePickerDialog.dismiss(); 234 } 235 236 mTimePickerDialog = new TimePickerDialog(this, this, mHour, mMinute, 237 DateFormat.is24HourFormat(this)); 238 mTimePickerDialog.setOnCancelListener(this); 239 mTimePickerDialog.show(); 240 } 241 onTimeSet(TimePicker view, int hourOfDay, int minute)242 public void onTimeSet(TimePicker view, int hourOfDay, int minute) { 243 // onTimeSet is called when the user clicks "Set" 244 mTimePickerDialog = null; 245 mHour = hourOfDay; 246 mMinute = minute; 247 updateTime(); 248 // If the time has been changed, enable the alarm. 249 mEnabledPref.setChecked(true); 250 } 251 252 @Override onCancel(DialogInterface dialog)253 public void onCancel(DialogInterface dialog) { 254 mTimePickerDialog = null; 255 } 256 updateTime()257 private void updateTime() { 258 mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinute, 259 mRepeatPref.getDaysOfWeek())); 260 } 261 saveAlarm(Alarm alarm)262 private long saveAlarm(Alarm alarm) { 263 if (alarm == null) { 264 alarm = buildAlarmFromUi(); 265 } 266 267 long time; 268 if (alarm.id == -1) { 269 time = Alarms.addAlarm(this, alarm); 270 // addAlarm populates the alarm with the new id. Update mId so that 271 // changes to other preferences update the new alarm. 272 mId = alarm.id; 273 } else { 274 time = Alarms.setAlarm(this, alarm); 275 } 276 return time; 277 } 278 buildAlarmFromUi()279 private Alarm buildAlarmFromUi() { 280 Alarm alarm = new Alarm(); 281 alarm.id = mId; 282 alarm.enabled = mEnabledPref.isChecked(); 283 alarm.hour = mHour; 284 alarm.minutes = mMinute; 285 alarm.daysOfWeek = mRepeatPref.getDaysOfWeek(); 286 alarm.vibrate = mVibratePref.isChecked(); 287 alarm.label = mLabel.getText().toString(); 288 alarm.alert = mAlarmPref.getAlert(); 289 return alarm; 290 } 291 deleteAlarm()292 private void deleteAlarm() { 293 new AlertDialog.Builder(this) 294 .setTitle(getString(R.string.delete_alarm)) 295 .setMessage(getString(R.string.delete_alarm_confirm)) 296 .setPositiveButton(android.R.string.ok, 297 new DialogInterface.OnClickListener() { 298 public void onClick(DialogInterface d, int w) { 299 Alarms.deleteAlarm(SetAlarm.this, mId); 300 finish(); 301 } 302 }) 303 .setNegativeButton(android.R.string.cancel, null) 304 .show(); 305 } 306 revert()307 private void revert() { 308 int newId = mId; 309 // "Revert" on a newly created alarm should delete it. 310 if (mOriginalAlarm.id == -1) { 311 Alarms.deleteAlarm(SetAlarm.this, newId); 312 } else { 313 saveAlarm(mOriginalAlarm); 314 } 315 } 316 317 /** 318 * Display a toast that tells the user how long until the alarm 319 * goes off. This helps prevent "am/pm" mistakes. 320 */ popAlarmSetToast(Context context, int hour, int minute, Alarm.DaysOfWeek daysOfWeek)321 static void popAlarmSetToast(Context context, int hour, int minute, 322 Alarm.DaysOfWeek daysOfWeek) { 323 popAlarmSetToast(context, 324 Alarms.calculateAlarm(hour, minute, daysOfWeek) 325 .getTimeInMillis()); 326 } 327 popAlarmSetToast(Context context, long timeInMillis)328 static void popAlarmSetToast(Context context, long timeInMillis) { 329 String toastText = formatToast(context, timeInMillis); 330 Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG); 331 ToastMaster.setToast(toast); 332 toast.show(); 333 } 334 335 /** 336 * format "Alarm set for 2 days 7 hours and 53 minutes from 337 * now" 338 */ formatToast(Context context, long timeInMillis)339 static String formatToast(Context context, long timeInMillis) { 340 long delta = timeInMillis - System.currentTimeMillis(); 341 long hours = delta / (1000 * 60 * 60); 342 long minutes = delta / (1000 * 60) % 60; 343 long days = hours / 24; 344 hours = hours % 24; 345 346 String daySeq = (days == 0) ? "" : 347 (days == 1) ? context.getString(R.string.day) : 348 context.getString(R.string.days, Long.toString(days)); 349 350 String minSeq = (minutes == 0) ? "" : 351 (minutes == 1) ? context.getString(R.string.minute) : 352 context.getString(R.string.minutes, Long.toString(minutes)); 353 354 String hourSeq = (hours == 0) ? "" : 355 (hours == 1) ? context.getString(R.string.hour) : 356 context.getString(R.string.hours, Long.toString(hours)); 357 358 boolean dispDays = days > 0; 359 boolean dispHour = hours > 0; 360 boolean dispMinute = minutes > 0; 361 362 int index = (dispDays ? 1 : 0) | 363 (dispHour ? 2 : 0) | 364 (dispMinute ? 4 : 0); 365 366 String[] formats = context.getResources().getStringArray(R.array.alarm_set); 367 return String.format(formats[index], daySeq, hourSeq, minSeq); 368 } 369 } 370