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 onCreate(Bundle icicle)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 onOptionsItemSelected(MenuItem item)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 onCreateOptionsMenu(Menu menu)184 public boolean onCreateOptionsMenu(Menu menu) { 185 getMenuInflater().inflate(R.menu.set_alarm_context, menu); 186 return true; 187 } 188 189 @Override onSaveInstanceState(Bundle outState)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 onRestoreInstanceState(Bundle state)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 onPreferenceChange(final Preference p, Object newValue)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 updatePrefs(Alarm alarm)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 onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)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 onBackPressed()267 public void onBackPressed() { 268 saveAndExit(); 269 } 270 showTimePicker()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 onTimeSet(TimePicker view, int hourOfDay, int minute)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 onCancel(DialogInterface dialog)300 public void onCancel(DialogInterface dialog) { 301 mTimePickerDialog = null; 302 } 303 updateTime()304 private void updateTime() { 305 mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinute, 306 mRepeatPref.getDaysOfWeek())); 307 } 308 saveAlarm(Alarm alarm)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 buildAlarmFromUi()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 deleteAlarm()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 revert()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 */ saveAndExit()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 */ popAlarmSetToast(Context context, int hour, int minute, Alarm.DaysOfWeek daysOfWeek)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 popAlarmSetToast(Context context, long timeInMillis)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 */ formatToast(Context context, long timeInMillis)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