AlarmClockFragment.java revision e10e1df9d70879d9a44e86da390f5a8f26a36bae
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.Activity; 21import android.app.Fragment; 22import android.app.FragmentTransaction; 23import android.app.LoaderManager; 24import android.content.Context; 25import android.content.Intent; 26import android.content.Loader; 27import android.database.Cursor; 28import android.graphics.Typeface; 29import android.media.Ringtone; 30import android.media.RingtoneManager; 31import android.net.Uri; 32import android.os.AsyncTask; 33import android.os.Bundle; 34import android.os.Vibrator; 35import android.view.LayoutInflater; 36import android.view.Menu; 37import android.view.MenuItem; 38import android.view.MotionEvent; 39import android.view.View; 40import android.view.ViewGroup; 41import android.widget.Button; 42import android.widget.CheckBox; 43import android.widget.CursorAdapter; 44import android.widget.ImageButton; 45import android.widget.LinearLayout; 46import android.widget.TextView; 47import android.widget.ToggleButton; 48 49import com.android.deskclock.widget.ActionableToastBar; 50import com.android.deskclock.widget.swipeablelistview.SwipeableListView; 51 52import java.text.DateFormatSymbols; 53import java.util.Calendar; 54import java.util.HashSet; 55 56/** 57 * AlarmClock application. 58 */ 59public class AlarmClock extends Activity implements LoaderManager.LoaderCallbacks<Cursor>, 60 AlarmTimePickerDialogFragment.AlarmTimePickerDialogHandler, 61 AlarmLabelDialogFragment.AlarmLabelDialogHandler { 62 63 private static final String KEY_EXPANDED_IDS = "expandedIds"; 64 private static final String KEY_REPEAT_CHECKED_IDS = "repeatCheckedIds"; 65 private static final String KEY_RINGTONE_TITLE_CACHE = "ringtoneTitleCache"; 66 private static final String KEY_DELETED_ALARM = "deletedAlarm"; 67 private static final String KEY_UNDO_SHOWING = "undoShowing"; 68 private static final String KEY_PREVIOUS_DAY_MAP = "previousDayMap"; 69 70 private static final int REQUEST_CODE_RINGTONE = 1; 71 72 private SwipeableListView mAlarmsList; 73 private AlarmItemAdapter mAdapter; 74 private Bundle mRingtoneTitleCache; // Key: ringtone uri, value: ringtone title 75 private ActionableToastBar mUndoBar; 76 77 private Alarm mSelectedAlarm; 78 79 // This flag relies on the activity having a "standard" launchMode and a new instance of this 80 // activity being created when launched. 81 private boolean mFirstLoad = true; 82 83 // Saved states for undo 84 private Alarm mDeletedAlarm; 85 private boolean mUndoShowing = false; 86 87 @Override 88 protected void onCreate(Bundle savedState) { 89 super.onCreate(savedState); 90 initialize(savedState); 91 updateLayout(); 92 getLoaderManager().initLoader(0, null, this); 93 } 94 95 private void initialize(Bundle savedState) { 96 setContentView(R.layout.alarm_clock); 97 int[] expandedIds = null; 98 int[] repeatCheckedIds = null; 99 Bundle previousDayMap = null; 100 if (savedState != null) { 101 expandedIds = savedState.getIntArray(KEY_EXPANDED_IDS); 102 repeatCheckedIds = savedState.getIntArray(KEY_REPEAT_CHECKED_IDS); 103 mRingtoneTitleCache = savedState.getBundle(KEY_RINGTONE_TITLE_CACHE); 104 mDeletedAlarm = savedState.getParcelable(KEY_DELETED_ALARM); 105 mUndoShowing = savedState.getBoolean(KEY_UNDO_SHOWING); 106 previousDayMap = savedState.getBundle(KEY_PREVIOUS_DAY_MAP); 107 } 108 109 mAdapter = new AlarmItemAdapter(this, expandedIds, repeatCheckedIds, previousDayMap); 110 111 if (mRingtoneTitleCache == null) { 112 mRingtoneTitleCache = new Bundle(); 113 } 114 115 mAlarmsList = (SwipeableListView) findViewById(R.id.alarms_list); 116 mAlarmsList.setAdapter(mAdapter); 117 mAlarmsList.setVerticalScrollBarEnabled(true); 118 mAlarmsList.enableSwipe(true); 119 mAlarmsList.setOnCreateContextMenuListener(this); 120 mAlarmsList.setOnItemSwipeListener(new SwipeableListView.OnItemSwipeListener() { 121 @Override 122 public void onSwipe(View view) { 123 final AlarmItemAdapter.ItemHolder itemHolder = 124 (AlarmItemAdapter.ItemHolder) view.getTag(); 125 asyncDeleteAlarm(itemHolder.alarm); 126 } 127 }); 128 mAlarmsList.setOnTouchListener(new View.OnTouchListener() { 129 @Override 130 public boolean onTouch(View view, MotionEvent event) { 131 hideUndoBar(true, event); 132 return false; 133 } 134 }); 135 136 mUndoBar = (ActionableToastBar) findViewById(R.id.undo_bar); 137 138 if (mUndoShowing) { 139 mUndoBar.show(new ActionableToastBar.ActionClickedListener() { 140 @Override 141 public void onActionClicked() { 142 asyncAddAlarm(mDeletedAlarm); 143 mDeletedAlarm = null; 144 mUndoShowing = false; 145 } 146 }, 0, getResources().getString(R.string.alarm_deleted), true, R.string.alarm_undo, 147 true); 148 } 149 } 150 151 private void hideUndoBar(boolean animate, MotionEvent event) { 152 if (mUndoBar != null) { 153 if (event != null && mUndoBar.isEventInToastBar(event)) { 154 // Avoid touches inside the undo bar. 155 return; 156 } 157 mUndoBar.hide(animate); 158 } 159 mDeletedAlarm = null; 160 mUndoShowing = false; 161 } 162 163 @Override 164 protected void onSaveInstanceState(Bundle outState) { 165 super.onSaveInstanceState(outState); 166 outState.putIntArray(KEY_EXPANDED_IDS, mAdapter.getExpandedArray()); 167 outState.putIntArray(KEY_REPEAT_CHECKED_IDS, mAdapter.getRepeatArray()); 168 outState.putBundle(KEY_RINGTONE_TITLE_CACHE, mRingtoneTitleCache); 169 outState.putParcelable(KEY_DELETED_ALARM, mDeletedAlarm); 170 outState.putBoolean(KEY_UNDO_SHOWING, mUndoShowing); 171 outState.putBundle(KEY_PREVIOUS_DAY_MAP, mAdapter.getPreviousDaysOfWeekMap()); 172 } 173 174 private void updateLayout() { 175 final ActionBar actionBar = getActionBar(); 176 if (actionBar != null) { 177 actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP); 178 } 179 } 180 181 private void addNewAlarm() { 182 // TODO: change to async 183 mAdapter.setNewAlarmCreated(true); 184 asyncAddAlarm(); 185 } 186 187 @Override 188 protected void onDestroy() { 189 super.onDestroy(); 190 ToastMaster.cancelToast(); 191 } 192 193 @Override 194 public boolean onOptionsItemSelected(MenuItem item) { 195 hideUndoBar(true, null); 196 switch (item.getItemId()) { 197 case R.id.menu_item_settings: 198 startActivity(new Intent(this, SettingsActivity.class)); 199 return true; 200 case R.id.menu_item_add_alarm: 201 addNewAlarm(); 202 return true; 203 case android.R.id.home: 204 Intent intent = new Intent(this, DeskClock.class); 205 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 206 startActivity(intent); 207 return true; 208 default: 209 break; 210 } 211 return super.onOptionsItemSelected(item); 212 } 213 214 @Override 215 public boolean onCreateOptionsMenu(Menu menu) { 216 getMenuInflater().inflate(R.menu.alarm_list_menu, menu); 217 MenuItem help = menu.findItem(R.id.menu_item_help); 218 if (help != null) { 219 Utils.prepareHelpMenuItem(this, help); 220 } 221 return super.onCreateOptionsMenu(menu); 222 } 223 224 @Override 225 protected void onRestart() { 226 super.onRestart(); 227 // When the user places the app in the background by pressing "home", 228 // dismiss the toast bar. However, since there is no way to determine if 229 // home was pressed, just dismiss any existing toast bar when restarting 230 // the app. 231 if (mUndoBar != null) { 232 hideUndoBar(false, null); 233 } 234 } 235 236 // Callback used by AlarmTimePickerDialogFragment 237 @Override 238 public void onDialogTimeSet(Alarm alarm, int hourOfDay, int minute) { 239 alarm.hour = hourOfDay; 240 alarm.minutes = minute; 241 alarm.enabled = true; 242 asyncUpdateAlarm(alarm, true); 243 244 } 245 246 private void showLabelDialog(final Alarm alarm) { 247 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 248 final Fragment prev = getFragmentManager().findFragmentByTag("label_dialog"); 249 if (prev != null) { 250 ft.remove(prev); 251 } 252 ft.addToBackStack(null); 253 254 // Create and show the dialog. 255 final AlarmLabelDialogFragment newFragment = AlarmLabelDialogFragment.newInstance(alarm, 256 alarm.label); 257 newFragment.show(ft, "label_dialog"); 258 } 259 260 // Callback used by AlarmLabelDialogFragment. 261 @Override 262 public void onDialogLabelSet(Alarm alarm, String label) { 263 alarm.label = label; 264 asyncUpdateAlarm(alarm, false); 265 } 266 267 @Override 268 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 269 return Alarms.getAlarmsCursorLoader(this); 270 } 271 272 @Override 273 public void onLoadFinished(Loader<Cursor> cursorLoader, final Cursor data) { 274 mAdapter.swapCursor(data); 275 gotoAlarmIfSpecified(); 276 } 277 278 /** 279 * Checks if an alarm was passed in. If so, go to that particular alarm in the list. 280 */ 281 private void gotoAlarmIfSpecified() { 282 final Intent intent = getIntent(); 283 if (mFirstLoad && intent != null) { 284 final Alarm alarm = (Alarm) intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); 285 if (alarm != null) { 286 for (int i = 0; i < mAdapter.getCount(); i++) { 287 long id = mAdapter.getItemId(i); 288 if (id == alarm.id) { 289 mAdapter.setNewAlarm(alarm.id); 290 mAlarmsList.setSelection(i); 291 292 final int firstPositionId = mAlarmsList.getFirstVisiblePosition(); 293 final int childId = i - firstPositionId; 294 295 final View view = mAlarmsList.getChildAt(childId); 296 mAdapter.getView(i, view, mAlarmsList); 297 break; 298 } 299 } 300 } 301 } 302 mFirstLoad = false; 303 } 304 305 @Override 306 public void onLoaderReset(Loader<Cursor> cursorLoader) { 307 mAdapter.swapCursor(null); 308 } 309 310 private void launchRingTonePicker(Alarm alarm) { 311 mSelectedAlarm = alarm; 312 final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); 313 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarm.alert); 314 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM); 315 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false); 316 startActivityForResult(intent, REQUEST_CODE_RINGTONE); 317 } 318 319 private void saveRingtoneUri(Intent intent) { 320 final Uri uri = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); 321 mSelectedAlarm.alert = uri; 322 asyncUpdateAlarm(mSelectedAlarm, false); 323 } 324 325 @Override 326 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 327 if (resultCode == RESULT_OK) { 328 switch (requestCode) { 329 case REQUEST_CODE_RINGTONE: 330 saveRingtoneUri(data); 331 break; 332 default: 333 Log.w("Unhandled request code in onActivityResult: " + requestCode); 334 } 335 } 336 } 337 338 public class AlarmItemAdapter extends CursorAdapter { 339 340 private final Context mContext; 341 private final LayoutInflater mFactory; 342 private final String[] mShortWeekDayStrings; 343 private final int mColorLit; 344 private final int mColorDim; 345 private final int mColorRed; 346 private final Typeface mRobotoNormal; 347 private final Typeface mRobotoBold; 348 349 private final HashSet<Integer> mExpanded = new HashSet<Integer>(); 350 private final HashSet<Integer> mRepeatChecked = new HashSet<Integer>(); 351 private Bundle mPreviousDaysOfWeekMap = new Bundle(); 352 private boolean mNewAlarmCreated = false; 353 354 private final boolean mHasVibrator; 355 356 // This determines the order in which it is shown and processed in the UI. 357 private final int[] DAY_ORDER = new int[] { 358 Calendar.SUNDAY, 359 Calendar.MONDAY, 360 Calendar.TUESDAY, 361 Calendar.WEDNESDAY, 362 Calendar.THURSDAY, 363 Calendar.FRIDAY, 364 Calendar.SATURDAY, 365 }; 366 367 private class ItemHolder { 368 369 // views for optimization 370 DigitalClock clock; 371 ToggleButton onoff; 372 TextView daysOfWeek; 373 TextView label; 374 ImageButton expand; 375 View expandArea; 376 View infoArea; 377 TextView clickableLabel; 378 CheckBox repeat; 379 LinearLayout repeatDays; 380 ToggleButton[] daysButtons = new ToggleButton[7]; 381 CheckBox vibrate; 382 ImageButton collapse; 383 Button ringtone; 384 385 // Other states 386 Alarm alarm; 387 } 388 389 public AlarmItemAdapter(Context context, int[] expandedIds, int[] repeatCheckedIds, 390 Bundle previousDaysOfWeekMap) { 391 super(context, null, 0); 392 mContext = context; 393 mFactory = LayoutInflater.from(context); 394 395 DateFormatSymbols dfs = new DateFormatSymbols(); 396 mShortWeekDayStrings = dfs.getShortWeekdays(); 397 398 mColorLit = mContext.getResources().getColor(R.color.clock_white); 399 mColorDim = mContext.getResources().getColor(R.color.clock_gray); 400 mColorRed = mContext.getResources().getColor(R.color.clock_red); 401 402 mRobotoBold = Typeface.create("sans-serif-condensed", Typeface.BOLD); 403 mRobotoNormal = Typeface.create("sans-serif-condensed", Typeface.NORMAL); 404 405 if (expandedIds != null) { 406 buildHashSetFromArray(expandedIds, mExpanded); 407 } 408 if (repeatCheckedIds != null) { 409 buildHashSetFromArray(repeatCheckedIds, mRepeatChecked); 410 } 411 if (previousDaysOfWeekMap != null) { 412 mPreviousDaysOfWeekMap = previousDaysOfWeekMap; 413 } 414 415 mHasVibrator = ((Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE)) 416 .hasVibrator(); 417 } 418 419 @Override 420 public View getView(int position, View convertView, ViewGroup parent) { 421 if (!getCursor().moveToPosition(position)) { 422 throw new IllegalStateException("couldn't move cursor to position " + position); 423 } 424 View v; 425 if (convertView == null) { 426 v = newView(mContext, getCursor(), parent); 427 } else { 428 // Do a translation check to test for animation. Change this to something more 429 // reliable and robust in the future. 430 if (convertView.getTranslationX() != 0 || convertView.getTranslationY() != 0) { 431 // view was animated, reset 432 v = newView(mContext, getCursor(), parent); 433 } else { 434 v = convertView; 435 } 436 } 437 bindView(v, mContext, getCursor()); 438 return v; 439 } 440 441 @Override 442 public View newView(Context context, Cursor cursor, ViewGroup parent) { 443 final View view = mFactory.inflate(R.layout.alarm_time, parent, false); 444 445 // standard view holder optimization 446 final ItemHolder holder = new ItemHolder(); 447 holder.clock = (DigitalClock) view.findViewById(R.id.digital_clock); 448 holder.clock.setLive(false); 449 holder.onoff = (ToggleButton) view.findViewById(R.id.onoff); 450 holder.daysOfWeek = (TextView) view.findViewById(R.id.daysOfWeek); 451 holder.label = (TextView) view.findViewById(R.id.label); 452 holder.expand = (ImageButton) view.findViewById(R.id.expand); 453 holder.expandArea = view.findViewById(R.id.expand_area); 454 holder.infoArea = view.findViewById(R.id.info_area); 455 holder.repeat = (CheckBox) view.findViewById(R.id.repeat_onoff); 456 holder.clickableLabel = (TextView) view.findViewById(R.id.edit_label); 457 holder.repeatDays = (LinearLayout) view.findViewById(R.id.repeat_days); 458 459 // Build button for each day. 460 for (int i = 0; i < 7; i++) { 461 final ToggleButton button = (ToggleButton) mFactory.inflate(R.layout.day_button, 462 holder.repeatDays, false); 463 final int dayToShowIndex = DAY_ORDER[i]; 464 button.setText(mShortWeekDayStrings[dayToShowIndex]); 465 button.setTextOn(mShortWeekDayStrings[dayToShowIndex]); 466 button.setTextOff(mShortWeekDayStrings[dayToShowIndex]); 467 holder.repeatDays.addView(button); 468 holder.daysButtons[i] = button; 469 } 470 holder.vibrate = (CheckBox) view.findViewById(R.id.vibrate_onoff); 471 holder.collapse = (ImageButton) view.findViewById(R.id.collapse); 472 holder.ringtone = (Button) view.findViewById(R.id.choose_ringtone); 473 474 view.setTag(holder); 475 return view; 476 } 477 478 @Override 479 public void bindView(View view, Context context, final Cursor cursor) { 480 final Alarm alarm = new Alarm(cursor); 481 final ItemHolder itemHolder = (ItemHolder) view.getTag(); 482 itemHolder.alarm = alarm; 483 484 boolean forceExpand = false; 485 if (cursor.getPosition() == 0) { 486 487 if (mNewAlarmCreated) { 488 mNewAlarmCreated = false; 489 forceExpand = true; 490 } 491 } 492 493 itemHolder.onoff.setChecked(alarm.enabled); 494 if (itemHolder.onoff.isChecked()) { 495 itemHolder.onoff.setTextColor(mColorRed); 496 itemHolder.onoff.setTypeface(null, Typeface.BOLD); 497 } else { 498 itemHolder.onoff.setTextColor(mColorDim); 499 itemHolder.onoff.setTypeface(null, Typeface.NORMAL); 500 } 501 final View.OnClickListener onOffListener = new View.OnClickListener() { 502 @Override 503 public void onClick(View v) { 504 //boolean checked = ((ToggleButton) v).isChecked(); 505 if (itemHolder.onoff.isChecked()) { 506 itemHolder.onoff.setTextColor(mColorRed); 507 itemHolder.onoff.setTypeface(null, Typeface.BOLD); 508 } else { 509 itemHolder.onoff.setTextColor(mColorDim); 510 itemHolder.onoff.setTypeface(null, Typeface.NORMAL); 511 } 512 alarm.enabled = !alarm.enabled; 513 asyncUpdateAlarm(alarm, alarm.enabled); 514 } 515 }; 516 itemHolder.onoff.setOnClickListener(onOffListener); 517 518 itemHolder.clock.updateTime(alarm.hour, alarm.minutes); 519 itemHolder.clock.setClickable(true); 520 itemHolder.clock.setOnClickListener(new View.OnClickListener() { 521 @Override 522 public void onClick(View view) { 523 AlarmUtils.showTimeEditDialog(AlarmClock.this.getFragmentManager(), alarm); 524 expandAlarm(itemHolder); 525 } 526 }); 527 528 itemHolder.expandArea.setVisibility(isAlarmExpanded(alarm) ? View.VISIBLE : View.GONE); 529 itemHolder.infoArea.setVisibility(!isAlarmExpanded(alarm) ? View.VISIBLE : View.GONE); 530 531 String colons = ""; 532 // Set the repeat text or leave it blank if it does not repeat. 533 final String daysOfWeekStr = alarm.daysOfWeek.toString(AlarmClock.this, false); 534 if (daysOfWeekStr != null && daysOfWeekStr.length() != 0) { 535 itemHolder.daysOfWeek.setText(daysOfWeekStr); 536 itemHolder.daysOfWeek.setVisibility(View.VISIBLE); 537 colons = ": "; 538 } else { 539 itemHolder.daysOfWeek.setVisibility(View.GONE); 540 } 541 542 if (alarm.label != null && alarm.label.length() != 0) { 543 itemHolder.label.setText(alarm.label + colons); 544 itemHolder.label.setVisibility(View.VISIBLE); 545 } else { 546 itemHolder.label.setVisibility(View.GONE); 547 } 548 549 itemHolder.expand.setOnClickListener(new View.OnClickListener() { 550 @Override 551 public void onClick(View v) { 552 expandAlarm(itemHolder); 553 } 554 }); 555 556 if (isAlarmExpanded(alarm) || forceExpand) { 557 expandAlarm(itemHolder); 558 } 559 } 560 561 private void bindExpandArea(final ItemHolder itemHolder, final Alarm alarm) { 562 // Views in here are not bound until the item is expanded. 563 564 if (alarm.label != null && alarm.label.length() > 0) { 565 itemHolder.clickableLabel.setText(alarm.label); 566 itemHolder.clickableLabel.setTextColor(mColorLit); 567 } else { 568 itemHolder.clickableLabel.setText(R.string.label); 569 itemHolder.clickableLabel.setTextColor(mColorDim); 570 } 571 itemHolder.clickableLabel.setOnClickListener(new View.OnClickListener() { 572 @Override 573 public void onClick(View view) { 574 showLabelDialog(alarm); 575 } 576 }); 577 578 if (mRepeatChecked.contains(alarm.id) || itemHolder.alarm.daysOfWeek.isRepeatSet()) { 579 itemHolder.repeat.setChecked(true); 580 itemHolder.repeat.setTextColor(mColorLit); 581 itemHolder.repeatDays.setVisibility(View.VISIBLE); 582 } else { 583 itemHolder.repeat.setChecked(false); 584 itemHolder.repeat.setTextColor(mColorDim); 585 itemHolder.repeatDays.setVisibility(View.GONE); 586 } 587 itemHolder.repeat.setOnClickListener(new View.OnClickListener() { 588 @Override 589 public void onClick(View view) { 590 final boolean checked = ((CheckBox) view).isChecked(); 591 if (checked) { 592 // Show days 593 itemHolder.repeatDays.setVisibility(View.VISIBLE); 594 itemHolder.repeat.setTextColor(mColorLit); 595 mRepeatChecked.add(alarm.id); 596 597 // Set all previously set days 598 // or 599 // Set all days if no previous. 600 final int daysOfWeekCoded = mPreviousDaysOfWeekMap.getInt("" + alarm.id); 601 if (daysOfWeekCoded == 0) { 602 for (int day : DAY_ORDER) { 603 alarm.daysOfWeek.setDayOfWeek(day, true); 604 } 605 } else { 606 alarm.daysOfWeek.set(new Alarm.DaysOfWeek(daysOfWeekCoded)); 607 } 608 updateDaysOfWeekButtons(itemHolder, alarm.daysOfWeek); 609 } else { 610 itemHolder.repeatDays.setVisibility(View.GONE); 611 itemHolder.repeat.setTextColor(mColorDim); 612 mRepeatChecked.remove(alarm.id); 613 614 // Remember the set days in case the user wants it back. 615 final int daysOfWeekCoded = alarm.daysOfWeek.getCoded(); 616 mPreviousDaysOfWeekMap.putInt("" + alarm.id, daysOfWeekCoded); 617 618 // Remove all repeat days 619 alarm.daysOfWeek.set(new Alarm.DaysOfWeek(0)); 620 } 621 asyncUpdateAlarm(alarm, false); 622 } 623 }); 624 625 updateDaysOfWeekButtons(itemHolder, alarm.daysOfWeek); 626 for (int i = 0; i < 7; i++) { 627 final int buttonIndex = i; 628 itemHolder.daysButtons[i].setOnClickListener(new View.OnClickListener() { 629 @Override 630 public void onClick(View view) { 631 final boolean checked = ((ToggleButton) view).isChecked(); 632 int day = DAY_ORDER[buttonIndex]; 633 alarm.daysOfWeek.setDayOfWeek(day, checked); 634 if (checked) { 635 turnOnDayOfWeek(itemHolder, buttonIndex); 636 } else { 637 turnOffDayOfWeek(itemHolder, buttonIndex); 638 639 // See if this was the last day, if so, un-check the repeat box. 640 if (alarm.daysOfWeek.getCoded() == 0) { 641 itemHolder.repeatDays.setVisibility(View.GONE); 642 itemHolder.repeat.setTextColor(mColorDim); 643 mRepeatChecked.remove(alarm.id); 644 645 // Remember the set days in case the user wants it back. 646 mPreviousDaysOfWeekMap.putInt("" + alarm.id, 0); 647 } 648 } 649 asyncUpdateAlarm(alarm, false); 650 } 651 }); 652 } 653 654 655 if (!mHasVibrator) { 656 itemHolder.vibrate.setVisibility(View.INVISIBLE); 657 } else { 658 itemHolder.vibrate.setVisibility(View.VISIBLE); 659 if (!alarm.vibrate) { 660 itemHolder.vibrate.setChecked(false); 661 itemHolder.vibrate.setTextColor(mColorDim); 662 } else { 663 itemHolder.vibrate.setChecked(true); 664 itemHolder.vibrate.setTextColor(mColorLit); 665 } 666 } 667 668 itemHolder.vibrate.setOnClickListener(new View.OnClickListener() { 669 @Override 670 public void onClick(View v) { 671 final boolean checked = ((CheckBox) v).isChecked(); 672 if (checked) { 673 itemHolder.vibrate.setTextColor(mColorLit); 674 } else { 675 itemHolder.vibrate.setTextColor(mColorDim); 676 } 677 alarm.vibrate = checked; 678 asyncUpdateAlarm(alarm, false); 679 } 680 }); 681 682 itemHolder.collapse.setOnClickListener(new View.OnClickListener() { 683 @Override 684 public void onClick(View v) { 685 itemHolder.expandArea.setVisibility(LinearLayout.GONE); 686 itemHolder.infoArea.setVisibility(View.VISIBLE); 687 collapseAlarm(alarm); 688 } 689 }); 690 691 final String ringtone; 692 if (alarm.alert == null) { 693 ringtone = mContext.getResources().getString(R.string.silent_alarm_summary); 694 } else { 695 ringtone = getRingToneTitle(alarm.alert); 696 } 697 itemHolder.ringtone.setText(ringtone); 698 itemHolder.ringtone.setOnClickListener(new View.OnClickListener() { 699 @Override 700 public void onClick(View view) { 701 launchRingTonePicker(alarm); 702 } 703 }); 704 } 705 706 private void updateDaysOfWeekButtons(ItemHolder holder, Alarm.DaysOfWeek daysOfWeek) { 707 HashSet<Integer> setDays = daysOfWeek.getSetDays(); 708 for (int i = 0; i < 7; i++) { 709 if (setDays.contains(DAY_ORDER[i])) { 710 turnOnDayOfWeek(holder, i); 711 } else { 712 turnOffDayOfWeek(holder, i); 713 } 714 } 715 } 716 717 private void turnOffDayOfWeek(ItemHolder holder, int dayIndex) { 718 holder.daysButtons[dayIndex].setChecked(false); 719 holder.daysButtons[dayIndex].setTextColor(mColorDim); 720 holder.daysButtons[dayIndex].setTypeface(mRobotoNormal); 721 } 722 723 private void turnOnDayOfWeek(ItemHolder holder, int dayIndex) { 724 holder.daysButtons[dayIndex].setChecked(true); 725 holder.daysButtons[dayIndex].setTextColor(mColorLit); 726 holder.daysButtons[dayIndex].setTypeface(mRobotoBold); 727 } 728 729 730 /** 731 * Does a read-through cache for ringtone titles. 732 * 733 * @param uri The uri of the ringtone. 734 * @return The ringtone title. {@literal null} if no matching ringtone found. 735 */ 736 private String getRingToneTitle(Uri uri) { 737 // Try the cache first 738 String title = mRingtoneTitleCache.getString(uri.toString()); 739 if (title == null) { 740 // This is slow because a media player is created during Ringtone object creation. 741 Ringtone ringTone = RingtoneManager.getRingtone(mContext, uri); 742 title = ringTone.getTitle(mContext); 743 if (title != null) { 744 mRingtoneTitleCache.putString(uri.toString(), title); 745 } 746 } 747 return title; 748 } 749 750 public void setNewAlarm(int alarmId) { 751 mExpanded.add(alarmId); 752 } 753 754 /** 755 * Expands the alarm for editing. 756 * 757 * @param itemHolder The item holder instance. 758 */ 759 private void expandAlarm(ItemHolder itemHolder) { 760 itemHolder.expandArea.setVisibility(View.VISIBLE); 761 itemHolder.infoArea.setVisibility(View.GONE); 762 763 mExpanded.add(itemHolder.alarm.id); 764 bindExpandArea(itemHolder, itemHolder.alarm); 765 } 766 767 private boolean isAlarmExpanded(Alarm alarm) { 768 return mExpanded.contains(alarm.id); 769 } 770 771 private void collapseAlarm(Alarm alarm) { 772 mExpanded.remove(alarm.id); 773 } 774 775 @Override 776 public int getViewTypeCount() { 777 return 1; 778 } 779 780 public void setNewAlarmCreated(boolean newAlarmCreated) { 781 mNewAlarmCreated = newAlarmCreated; 782 } 783 784 public int[] getExpandedArray() { 785 final int[] ids = new int[mExpanded.size()]; 786 int index = 0; 787 for (int id : mExpanded) { 788 ids[index] = id; 789 index++; 790 } 791 return ids; 792 } 793 794 public int[] getRepeatArray() { 795 final int[] ids = new int[mRepeatChecked.size()]; 796 int index = 0; 797 for (int id : mRepeatChecked) { 798 ids[index] = id; 799 index++; 800 } 801 return ids; 802 } 803 804 public Bundle getPreviousDaysOfWeekMap() { 805 return mPreviousDaysOfWeekMap; 806 } 807 808 private void buildHashSetFromArray(int[] ids, HashSet<Integer> set) { 809 for (int id : ids) { 810 set.add(id); 811 } 812 } 813 } 814 815 private void asyncAddAlarm() { 816 asyncAddAlarm(new Alarm()); 817 } 818 819 private void asyncDeleteAlarm(final Alarm alarm) { 820 final AsyncTask<Alarm, Void, Void> deleteTask = new AsyncTask<Alarm, Void, Void>() { 821 822 @Override 823 protected Void doInBackground(Alarm... alarms) { 824 for (final Alarm alarm : alarms) { 825 Alarms.deleteAlarm(AlarmClock.this, alarm.id); 826 } 827 return null; 828 } 829 }; 830 mDeletedAlarm = alarm; 831 mUndoShowing = true; 832 deleteTask.execute(alarm); 833 mUndoBar.show(new ActionableToastBar.ActionClickedListener() { 834 @Override 835 public void onActionClicked() { 836 asyncAddAlarm(alarm); 837 mDeletedAlarm = null; 838 mUndoShowing = false; 839 } 840 }, 0, getResources().getString(R.string.alarm_deleted), true, R.string.alarm_undo, true); 841 } 842 843 private void asyncAddAlarm(final Alarm alarm) { 844 final AsyncTask<Void, Void, Void> updateTask = new AsyncTask<Void, Void, Void>() { 845 @Override 846 protected Void doInBackground(Void... aVoid) { 847 Alarms.addAlarm(AlarmClock.this, alarm); 848 return null; 849 } 850 851 @Override 852 protected void onPostExecute(Void aVoid) { 853 if (alarm.enabled) { 854 popToast(alarm); 855 } 856 } 857 }; 858 updateTask.execute(); 859 } 860 861 private void asyncUpdateAlarm(final Alarm alarm, final boolean popToast) { 862 final AsyncTask<Alarm, Void, Void> updateTask = new AsyncTask<Alarm, Void, Void>() { 863 @Override 864 protected Void doInBackground(Alarm... alarms) { 865 for (final Alarm alarm : alarms) { 866 Alarms.setAlarm(AlarmClock.this, alarm); 867 } 868 return null; 869 } 870 871 @Override 872 protected void onPostExecute(Void aVoid) { 873 if (popToast) { 874 popToast(alarm); 875 } 876 } 877 }; 878 updateTask.execute(alarm); 879 } 880 881 private void popToast(Alarm alarm) { 882 AlarmUtils.popAlarmSetToast(this, alarm.hour, alarm.minutes, alarm.daysOfWeek); 883 } 884} 885