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