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.Intent; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.preference.CheckBoxPreference; 27 import android.preference.EditTextPreference; 28 import android.preference.Preference; 29 import android.preference.PreferenceActivity; 30 import android.preference.PreferenceScreen; 31 import android.text.format.DateFormat; 32 import android.view.LayoutInflater; 33 import android.view.Menu; 34 import android.view.MenuItem; 35 import android.view.View; 36 import android.view.ViewGroup.LayoutParams; 37 import android.widget.Button; 38 import android.widget.FrameLayout; 39 import android.widget.LinearLayout; 40 import android.widget.ListView; 41 import android.widget.TimePicker; 42 import android.widget.Toast; 43 44 /** 45 * Manages each alarm 46 */ 47 public class SetAlarm extends PreferenceActivity 48 implements TimePickerDialog.OnTimeSetListener, 49 Preference.OnPreferenceChangeListener { 50 51 private EditTextPreference mLabel; 52 private CheckBoxPreference mEnabledPref; 53 private Preference mTimePref; 54 private AlarmPreference mAlarmPref; 55 private CheckBoxPreference mVibratePref; 56 private RepeatPreference mRepeatPref; 57 private MenuItem mTestAlarmItem; 58 59 private int mId; 60 private int mHour; 61 private int mMinutes; 62 private boolean mTimePickerCancelled; 63 private Alarm mOriginalAlarm; 64 65 /** 66 * Set an alarm. Requires an Alarms.ALARM_ID to be passed in as an 67 * extra. FIXME: Pass an Alarm object like every other Activity. 68 */ 69 @Override 70 protected void onCreate(Bundle icicle) { 71 super.onCreate(icicle); 72 73 // Override the default content view. 74 setContentView(R.layout.set_alarm); 75 76 addPreferencesFromResource(R.xml.alarm_prefs); 77 78 // Get each preference so we can retrieve the value later. 79 mLabel = (EditTextPreference) findPreference("label"); 80 mLabel.setOnPreferenceChangeListener( 81 new Preference.OnPreferenceChangeListener() { 82 public boolean onPreferenceChange(Preference p, 83 Object newValue) { 84 String val = (String) newValue; 85 // Set the summary based on the new label. 86 p.setSummary(val); 87 if (val != null && !val.equals(mLabel.getText())) { 88 // Call through to the generic listener. 89 return SetAlarm.this.onPreferenceChange(p, 90 newValue); 91 } 92 return true; 93 } 94 }); 95 mEnabledPref = (CheckBoxPreference) findPreference("enabled"); 96 mEnabledPref.setOnPreferenceChangeListener( 97 new Preference.OnPreferenceChangeListener() { 98 public boolean onPreferenceChange(Preference p, 99 Object newValue) { 100 // Pop a toast when enabling alarms. 101 if (!mEnabledPref.isChecked()) { 102 popAlarmSetToast(SetAlarm.this, mHour, mMinutes, 103 mRepeatPref.getDaysOfWeek()); 104 } 105 return SetAlarm.this.onPreferenceChange(p, newValue); 106 } 107 }); 108 mTimePref = findPreference("time"); 109 mAlarmPref = (AlarmPreference) findPreference("alarm"); 110 mAlarmPref.setOnPreferenceChangeListener(this); 111 mVibratePref = (CheckBoxPreference) findPreference("vibrate"); 112 mVibratePref.setOnPreferenceChangeListener(this); 113 mRepeatPref = (RepeatPreference) findPreference("setRepeat"); 114 mRepeatPref.setOnPreferenceChangeListener(this); 115 116 Intent i = getIntent(); 117 mId = i.getIntExtra(Alarms.ALARM_ID, -1); 118 if (Log.LOGV) { 119 Log.v("In SetAlarm, alarm id = " + mId); 120 } 121 122 Alarm alarm = null; 123 if (mId == -1) { 124 // No alarm id means create a new alarm. 125 alarm = new Alarm(); 126 } else { 127 /* load alarm details from database */ 128 alarm = Alarms.getAlarm(getContentResolver(), mId); 129 // Bad alarm, bail to avoid a NPE. 130 if (alarm == null) { 131 finish(); 132 return; 133 } 134 } 135 mOriginalAlarm = alarm; 136 137 updatePrefs(mOriginalAlarm); 138 139 // We have to do this to get the save/cancel buttons to highlight on 140 // their own. 141 getListView().setItemsCanFocus(true); 142 143 // Attach actions to each button. 144 Button b = (Button) findViewById(R.id.alarm_save); 145 b.setOnClickListener(new View.OnClickListener() { 146 public void onClick(View v) { 147 saveAlarm(); 148 finish(); 149 } 150 }); 151 final Button revert = (Button) findViewById(R.id.alarm_revert); 152 revert.setEnabled(false); 153 revert.setOnClickListener(new View.OnClickListener() { 154 public void onClick(View v) { 155 int newId = mId; 156 updatePrefs(mOriginalAlarm); 157 // "Revert" on a newly created alarm should delete it. 158 if (mOriginalAlarm.id == -1) { 159 Alarms.deleteAlarm(SetAlarm.this, newId); 160 } else { 161 saveAlarm(); 162 } 163 revert.setEnabled(false); 164 } 165 }); 166 b = (Button) findViewById(R.id.alarm_delete); 167 if (mId == -1) { 168 b.setEnabled(false); 169 } else { 170 b.setOnClickListener(new View.OnClickListener() { 171 public void onClick(View v) { 172 deleteAlarm(); 173 } 174 }); 175 } 176 177 // The last thing we do is pop the time picker if this is a new alarm. 178 if (mId == -1) { 179 // Assume the user hit cancel 180 mTimePickerCancelled = true; 181 showTimePicker(); 182 } 183 } 184 185 // Used to post runnables asynchronously. 186 private static final Handler sHandler = new Handler(); 187 188 public boolean onPreferenceChange(final Preference p, Object newValue) { 189 // Asynchronously save the alarm since this method is called _before_ 190 // the value of the preference has changed. 191 sHandler.post(new Runnable() { 192 public void run() { 193 // Editing any preference (except enable) enables the alarm. 194 if (p != mEnabledPref) { 195 mEnabledPref.setChecked(true); 196 } 197 saveAlarmAndEnableRevert(); 198 } 199 }); 200 return true; 201 } 202 203 private void updatePrefs(Alarm alarm) { 204 mId = alarm.id; 205 mEnabledPref.setChecked(alarm.enabled); 206 mLabel.setText(alarm.label); 207 mLabel.setSummary(alarm.label); 208 mHour = alarm.hour; 209 mMinutes = alarm.minutes; 210 mRepeatPref.setDaysOfWeek(alarm.daysOfWeek); 211 mVibratePref.setChecked(alarm.vibrate); 212 // Give the alert uri to the preference. 213 mAlarmPref.setAlert(alarm.alert); 214 updateTime(); 215 } 216 217 @Override 218 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, 219 Preference preference) { 220 if (preference == mTimePref) { 221 showTimePicker(); 222 } 223 224 return super.onPreferenceTreeClick(preferenceScreen, preference); 225 } 226 227 @Override 228 public void onBackPressed() { 229 // In the usual case of viewing an alarm, mTimePickerCancelled is 230 // initialized to false. When creating a new alarm, this value is 231 // assumed true until the user changes the time. 232 if (!mTimePickerCancelled) { 233 saveAlarm(); 234 } 235 finish(); 236 } 237 238 private void showTimePicker() { 239 new TimePickerDialog(this, this, mHour, mMinutes, 240 DateFormat.is24HourFormat(this)).show(); 241 } 242 243 public void onTimeSet(TimePicker view, int hourOfDay, int minute) { 244 // onTimeSet is called when the user clicks "Set" 245 mTimePickerCancelled = false; 246 mHour = hourOfDay; 247 mMinutes = minute; 248 updateTime(); 249 // If the time has been changed, enable the alarm. 250 mEnabledPref.setChecked(true); 251 // Save the alarm and pop a toast. 252 popAlarmSetToast(this, saveAlarmAndEnableRevert()); 253 } 254 255 private void updateTime() { 256 if (Log.LOGV) { 257 Log.v("updateTime " + mId); 258 } 259 mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinutes, 260 mRepeatPref.getDaysOfWeek())); 261 } 262 263 private long saveAlarmAndEnableRevert() { 264 // Enable "Revert" to go back to the original Alarm. 265 final Button revert = (Button) findViewById(R.id.alarm_revert); 266 revert.setEnabled(true); 267 return saveAlarm(); 268 } 269 270 private long saveAlarm() { 271 Alarm alarm = new Alarm(); 272 alarm.id = mId; 273 alarm.enabled = mEnabledPref.isChecked(); 274 alarm.hour = mHour; 275 alarm.minutes = mMinutes; 276 alarm.daysOfWeek = mRepeatPref.getDaysOfWeek(); 277 alarm.vibrate = mVibratePref.isChecked(); 278 alarm.label = mLabel.getText(); 279 alarm.alert = mAlarmPref.getAlert(); 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 void deleteAlarm() { 294 new AlertDialog.Builder(this) 295 .setTitle(getString(R.string.delete_alarm)) 296 .setMessage(getString(R.string.delete_alarm_confirm)) 297 .setPositiveButton(android.R.string.ok, 298 new DialogInterface.OnClickListener() { 299 public void onClick(DialogInterface d, int w) { 300 Alarms.deleteAlarm(SetAlarm.this, mId); 301 finish(); 302 } 303 }) 304 .setNegativeButton(android.R.string.cancel, null) 305 .show(); 306 } 307 308 /** 309 * Display a toast that tells the user how long until the alarm 310 * goes off. This helps prevent "am/pm" mistakes. 311 */ 312 static void popAlarmSetToast(Context context, int hour, int minute, 313 Alarm.DaysOfWeek daysOfWeek) { 314 popAlarmSetToast(context, 315 Alarms.calculateAlarm(hour, minute, daysOfWeek) 316 .getTimeInMillis()); 317 } 318 319 static void popAlarmSetToast(Context context, long timeInMillis) { 320 String toastText = formatToast(context, timeInMillis); 321 Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG); 322 ToastMaster.setToast(toast); 323 toast.show(); 324 } 325 326 /** 327 * format "Alarm set for 2 days 7 hours and 53 minutes from 328 * now" 329 */ 330 static String formatToast(Context context, long timeInMillis) { 331 long delta = timeInMillis - System.currentTimeMillis(); 332 long hours = delta / (1000 * 60 * 60); 333 long minutes = delta / (1000 * 60) % 60; 334 long days = hours / 24; 335 hours = hours % 24; 336 337 String daySeq = (days == 0) ? "" : 338 (days == 1) ? context.getString(R.string.day) : 339 context.getString(R.string.days, Long.toString(days)); 340 341 String minSeq = (minutes == 0) ? "" : 342 (minutes == 1) ? context.getString(R.string.minute) : 343 context.getString(R.string.minutes, Long.toString(minutes)); 344 345 String hourSeq = (hours == 0) ? "" : 346 (hours == 1) ? context.getString(R.string.hour) : 347 context.getString(R.string.hours, Long.toString(hours)); 348 349 boolean dispDays = days > 0; 350 boolean dispHour = hours > 0; 351 boolean dispMinute = minutes > 0; 352 353 int index = (dispDays ? 1 : 0) | 354 (dispHour ? 2 : 0) | 355 (dispMinute ? 4 : 0); 356 357 String[] formats = context.getResources().getStringArray(R.array.alarm_set); 358 return String.format(formats[index], daySeq, hourSeq, minSeq); 359 } 360 } 361