Home | History | Annotate | Download | only in deskclock
      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.animation.Animator;
     20 import android.animation.Animator.AnimatorListener;
     21 import android.animation.AnimatorInflater;
     22 import android.animation.ValueAnimator;
     23 import android.app.Activity;
     24 import android.app.Fragment;
     25 import android.app.FragmentTransaction;
     26 import android.app.LoaderManager;
     27 import android.content.ContentResolver;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.content.Loader;
     31 import android.content.res.Configuration;
     32 import android.content.res.Resources;
     33 import android.database.Cursor;
     34 import android.database.DataSetObserver;
     35 import android.graphics.Rect;
     36 import android.graphics.Typeface;
     37 import android.media.Ringtone;
     38 import android.media.RingtoneManager;
     39 import android.net.Uri;
     40 import android.os.AsyncTask;
     41 import android.os.Bundle;
     42 import android.os.Vibrator;
     43 import android.text.format.DateFormat;
     44 import android.view.Gravity;
     45 import android.view.LayoutInflater;
     46 import android.view.MotionEvent;
     47 import android.view.View;
     48 import android.view.View.OnClickListener;
     49 import android.view.ViewGroup;
     50 import android.view.ViewGroup.LayoutParams;
     51 import android.view.ViewTreeObserver;
     52 import android.view.animation.DecelerateInterpolator;
     53 import android.view.animation.Interpolator;
     54 import android.widget.CheckBox;
     55 import android.widget.CompoundButton;
     56 import android.widget.CursorAdapter;
     57 import android.widget.FrameLayout;
     58 import android.widget.ImageButton;
     59 import android.widget.ImageView;
     60 import android.widget.LinearLayout;
     61 import android.widget.ListView;
     62 import android.widget.Switch;
     63 import android.widget.TextView;
     64 import android.widget.Toast;
     65 import android.widget.ToggleButton;
     66 
     67 import com.android.datetimepicker.time.RadialPickerLayout;
     68 import com.android.datetimepicker.time.TimePickerDialog;
     69 import com.android.deskclock.alarms.AlarmStateManager;
     70 import com.android.deskclock.provider.Alarm;
     71 import com.android.deskclock.provider.AlarmInstance;
     72 import com.android.deskclock.provider.DaysOfWeek;
     73 import com.android.deskclock.widget.ActionableToastBar;
     74 import com.android.deskclock.widget.TextTime;
     75 
     76 import java.text.DateFormatSymbols;
     77 import java.util.Calendar;
     78 import java.util.HashSet;
     79 import java.util.concurrent.ConcurrentHashMap;
     80 
     81 /**
     82  * AlarmClock application.
     83  */
     84 public class AlarmClockFragment extends DeskClockFragment implements
     85         LoaderManager.LoaderCallbacks<Cursor>,
     86         TimePickerDialog.OnTimeSetListener,
     87         View.OnTouchListener
     88         {
     89     private static final float EXPAND_DECELERATION = 1f;
     90     private static final float COLLAPSE_DECELERATION = 0.7f;
     91     private static final int ANIMATION_DURATION = 300;
     92     private static final String KEY_EXPANDED_IDS = "expandedIds";
     93     private static final String KEY_REPEAT_CHECKED_IDS = "repeatCheckedIds";
     94     private static final String KEY_RINGTONE_TITLE_CACHE = "ringtoneTitleCache";
     95     private static final String KEY_SELECTED_ALARMS = "selectedAlarms";
     96     private static final String KEY_DELETED_ALARM = "deletedAlarm";
     97     private static final String KEY_UNDO_SHOWING = "undoShowing";
     98     private static final String KEY_PREVIOUS_DAY_MAP = "previousDayMap";
     99     private static final String KEY_SELECTED_ALARM = "selectedAlarm";
    100     private static final String KEY_DELETE_CONFIRMATION = "deleteConfirmation";
    101 
    102     private static final int REQUEST_CODE_RINGTONE = 1;
    103 
    104     // This extra is used when receiving an intent to create an alarm, but no alarm details
    105     // have been passed in, so the alarm page should start the process of creating a new alarm.
    106     public static final String ALARM_CREATE_NEW_INTENT_EXTRA = "deskclock.create.new";
    107 
    108     // This extra is used when receiving an intent to scroll to specific alarm. If alarm
    109     // can not be found, and toast message will pop up that the alarm has be deleted.
    110     public static final String SCROLL_TO_ALARM_INTENT_EXTRA = "deskclock.scroll.to.alarm";
    111 
    112     private ListView mAlarmsList;
    113     private AlarmItemAdapter mAdapter;
    114     private View mEmptyView;
    115     private ImageView mAddAlarmButton;
    116     private View mAlarmsView;
    117     private View mTimelineLayout;
    118     private AlarmTimelineView mTimelineView;
    119     private View mFooterView;
    120 
    121     private Bundle mRingtoneTitleCache; // Key: ringtone uri, value: ringtone title
    122     private ActionableToastBar mUndoBar;
    123     private View mUndoFrame;
    124 
    125     private Alarm mSelectedAlarm;
    126     private long mScrollToAlarmId = -1;
    127 
    128     private Loader mCursorLoader = null;
    129 
    130     // Saved states for undo
    131     private Alarm mDeletedAlarm;
    132     private Alarm mAddedAlarm;
    133     private boolean mUndoShowing = false;
    134 
    135     private Animator mFadeIn;
    136     private Animator mFadeOut;
    137 
    138     private Interpolator mExpandInterpolator;
    139     private Interpolator mCollapseInterpolator;
    140 
    141     private int mTimelineViewWidth;
    142     private int mUndoBarInitialMargin;
    143 
    144     // Cached layout positions of items in listview prior to add/removal of alarm item
    145     private ConcurrentHashMap<Long, Integer> mItemIdTopMap = new ConcurrentHashMap<Long, Integer>();
    146 
    147     public AlarmClockFragment() {
    148         // Basic provider required by Fragment.java
    149     }
    150 
    151     @Override
    152     public void onCreate(Bundle savedState) {
    153         super.onCreate(savedState);
    154         mCursorLoader = getLoaderManager().initLoader(0, null, this);
    155     }
    156 
    157     @Override
    158     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    159             Bundle savedState) {
    160         // Inflate the layout for this fragment
    161         final View v = inflater.inflate(R.layout.alarm_clock, container, false);
    162 
    163         long[] expandedIds = null;
    164         long[] repeatCheckedIds = null;
    165         long[] selectedAlarms = null;
    166         Bundle previousDayMap = null;
    167         if (savedState != null) {
    168             expandedIds = savedState.getLongArray(KEY_EXPANDED_IDS);
    169             repeatCheckedIds = savedState.getLongArray(KEY_REPEAT_CHECKED_IDS);
    170             mRingtoneTitleCache = savedState.getBundle(KEY_RINGTONE_TITLE_CACHE);
    171             mDeletedAlarm = savedState.getParcelable(KEY_DELETED_ALARM);
    172             mUndoShowing = savedState.getBoolean(KEY_UNDO_SHOWING);
    173             selectedAlarms = savedState.getLongArray(KEY_SELECTED_ALARMS);
    174             previousDayMap = savedState.getBundle(KEY_PREVIOUS_DAY_MAP);
    175             mSelectedAlarm = savedState.getParcelable(KEY_SELECTED_ALARM);
    176         }
    177 
    178         mExpandInterpolator = new DecelerateInterpolator(EXPAND_DECELERATION);
    179         mCollapseInterpolator = new DecelerateInterpolator(COLLAPSE_DECELERATION);
    180 
    181         mAddAlarmButton = (ImageButton) v.findViewById(R.id.alarm_add_alarm);
    182         mAddAlarmButton.setOnClickListener(new OnClickListener() {
    183             @Override
    184             public void onClick(View v) {
    185                 hideUndoBar(true, null);
    186                 startCreatingAlarm();
    187             }
    188         });
    189         // For landscape, put the add button on the right and the menu in the actionbar.
    190         FrameLayout.LayoutParams layoutParams =
    191                 (FrameLayout.LayoutParams) mAddAlarmButton.getLayoutParams();
    192         boolean isLandscape = getResources().getConfiguration().orientation
    193                 == Configuration.ORIENTATION_LANDSCAPE;
    194         if (isLandscape) {
    195             layoutParams.gravity = Gravity.END;
    196         } else {
    197             layoutParams.gravity = Gravity.CENTER;
    198         }
    199         mAddAlarmButton.setLayoutParams(layoutParams);
    200 
    201         View menuButton = v.findViewById(R.id.menu_button);
    202         if (menuButton != null) {
    203             if (isLandscape) {
    204                 menuButton.setVisibility(View.GONE);
    205             } else {
    206                 menuButton.setVisibility(View.VISIBLE);
    207                 setupFakeOverflowMenuButton(menuButton);
    208             }
    209         }
    210 
    211         mEmptyView = v.findViewById(R.id.alarms_empty_view);
    212         mEmptyView.setOnClickListener(new OnClickListener() {
    213             @Override
    214             public void onClick(View v) {
    215                 startCreatingAlarm();
    216             }
    217         });
    218         mAlarmsList = (ListView) v.findViewById(R.id.alarms_list);
    219 
    220         mFadeIn = AnimatorInflater.loadAnimator(getActivity(), R.anim.fade_in);
    221         mFadeIn.setDuration(ANIMATION_DURATION);
    222         mFadeIn.addListener(new AnimatorListener() {
    223 
    224             @Override
    225             public void onAnimationStart(Animator animation) {
    226                 mEmptyView.setVisibility(View.VISIBLE);
    227             }
    228 
    229             @Override
    230             public void onAnimationCancel(Animator animation) {
    231                 // Do nothing.
    232             }
    233 
    234             @Override
    235             public void onAnimationEnd(Animator animation) {
    236                 // Do nothing.
    237             }
    238 
    239             @Override
    240             public void onAnimationRepeat(Animator animation) {
    241                 // Do nothing.
    242             }
    243         });
    244         mFadeIn.setTarget(mEmptyView);
    245         mFadeOut = AnimatorInflater.loadAnimator(getActivity(), R.anim.fade_out);
    246         mFadeOut.setDuration(ANIMATION_DURATION);
    247         mFadeOut.addListener(new AnimatorListener() {
    248 
    249             @Override
    250             public void onAnimationStart(Animator arg0) {
    251                 mEmptyView.setVisibility(View.VISIBLE);
    252             }
    253 
    254             @Override
    255             public void onAnimationCancel(Animator arg0) {
    256                 // Do nothing.
    257             }
    258 
    259             @Override
    260             public void onAnimationEnd(Animator arg0) {
    261                 mEmptyView.setVisibility(View.GONE);
    262             }
    263 
    264             @Override
    265             public void onAnimationRepeat(Animator arg0) {
    266                 // Do nothing.
    267             }
    268         });
    269         mFadeOut.setTarget(mEmptyView);
    270         mAlarmsView = v.findViewById(R.id.alarm_layout);
    271         mTimelineLayout = v.findViewById(R.id.timeline_layout);
    272 
    273         mUndoBar = (ActionableToastBar) v.findViewById(R.id.undo_bar);
    274         mUndoBarInitialMargin = getActivity().getResources()
    275                 .getDimensionPixelOffset(R.dimen.alarm_undo_bar_horizontal_margin);
    276         mUndoFrame = v.findViewById(R.id.undo_frame);
    277         mUndoFrame.setOnTouchListener(this);
    278 
    279         mFooterView = v.findViewById(R.id.alarms_footer_view);
    280         mFooterView.setOnTouchListener(this);
    281 
    282         // Timeline layout only exists in tablet landscape mode for now.
    283         if (mTimelineLayout != null) {
    284             mTimelineView = (AlarmTimelineView) v.findViewById(R.id.alarm_timeline_view);
    285             mTimelineViewWidth = getActivity().getResources()
    286                     .getDimensionPixelOffset(R.dimen.alarm_timeline_layout_width);
    287         }
    288 
    289         mAdapter = new AlarmItemAdapter(getActivity(),
    290                 expandedIds, repeatCheckedIds, selectedAlarms, previousDayMap, mAlarmsList);
    291         mAdapter.registerDataSetObserver(new DataSetObserver() {
    292 
    293             private int prevAdapterCount = -1;
    294 
    295             @Override
    296             public void onChanged() {
    297 
    298                 final int count = mAdapter.getCount();
    299                 if (mDeletedAlarm != null && prevAdapterCount > count) {
    300                     showUndoBar();
    301                 }
    302 
    303                 // If there are no alarms in the adapter...
    304                 if (count == 0) {
    305                     mAddAlarmButton.setBackgroundResource(R.drawable.main_button_red);
    306 
    307                     // ...and if there exists a timeline view (currently only in tablet landscape)
    308                     if (mTimelineLayout != null && mAlarmsView != null) {
    309 
    310                         // ...and if the previous adapter had alarms (indicating a removal)...
    311                         if (prevAdapterCount > 0) {
    312 
    313                             // Then animate in the "no alarms" icon...
    314                             mFadeIn.start();
    315 
    316                             // and animate out the alarm timeline view, expanding the width of the
    317                             // alarms list / undo bar.
    318                             mTimelineLayout.setVisibility(View.VISIBLE);
    319                             ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f)
    320                                     .setDuration(ANIMATION_DURATION);
    321                             animator.setInterpolator(mCollapseInterpolator);
    322                             animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    323                                 @Override
    324                                 public void onAnimationUpdate(ValueAnimator animator) {
    325                                     Float value = (Float) animator.getAnimatedValue();
    326                                     int currentTimelineWidth = (int) (value * mTimelineViewWidth);
    327                                     float rightOffset = mTimelineViewWidth * (1 - value);
    328                                     mTimelineLayout.setTranslationX(rightOffset);
    329                                     mTimelineLayout.setAlpha(value);
    330                                     mTimelineLayout.requestLayout();
    331                                     setUndoBarRightMargin(currentTimelineWidth
    332                                             + mUndoBarInitialMargin);
    333                                 }
    334                             });
    335                             animator.addListener(new AnimatorListener() {
    336 
    337                                 @Override
    338                                 public void onAnimationCancel(Animator animation) {
    339                                     // Do nothing.
    340                                 }
    341 
    342                                 @Override
    343                                 public void onAnimationEnd(Animator animation) {
    344                                     mTimelineView.setIsAnimatingOut(false);
    345                                 }
    346 
    347                                 @Override
    348                                 public void onAnimationRepeat(Animator animation) {
    349                                     // Do nothing.
    350                                 }
    351 
    352                                 @Override
    353                                 public void onAnimationStart(Animator animation) {
    354                                     mTimelineView.setIsAnimatingOut(true);
    355                                 }
    356 
    357                             });
    358                             animator.start();
    359                         } else {
    360                             // If the previous adapter did not have alarms, no animation needed,
    361                             // just hide the timeline view and show the "no alarms" icon.
    362                             mTimelineLayout.setVisibility(View.GONE);
    363                             mEmptyView.setVisibility(View.VISIBLE);
    364                             setUndoBarRightMargin(mUndoBarInitialMargin);
    365                         }
    366                     } else {
    367 
    368                         // If there is no timeline view, just show the "no alarms" icon.
    369                         mEmptyView.setVisibility(View.VISIBLE);
    370                     }
    371                 } else {
    372 
    373                     // Otherwise, if the adapter DOES contain alarms...
    374                     mAddAlarmButton.setBackgroundResource(R.drawable.main_button_normal);
    375 
    376                     // ...and if there exists a timeline view (currently in tablet landscape mode)
    377                     if (mTimelineLayout != null && mAlarmsView != null) {
    378 
    379                         mTimelineLayout.setVisibility(View.VISIBLE);
    380                         // ...and if the previous adapter did not have alarms (indicating an add)
    381                         if (prevAdapterCount == 0) {
    382 
    383                             // Then, animate to hide the "no alarms" icon...
    384                             mFadeOut.start();
    385 
    386                             // and animate to show the timeline view, reducing the width of the
    387                             // alarms list / undo bar.
    388                             ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f)
    389                                     .setDuration(ANIMATION_DURATION);
    390                             animator.setInterpolator(mExpandInterpolator);
    391                             animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    392                                 @Override
    393                                 public void onAnimationUpdate(ValueAnimator animator) {
    394                                     Float value = (Float) animator.getAnimatedValue();
    395                                     int currentTimelineWidth = (int) (value * mTimelineViewWidth);
    396                                     float rightOffset = mTimelineViewWidth * (1 - value);
    397                                     mTimelineLayout.setTranslationX(rightOffset);
    398                                     mTimelineLayout.setAlpha(value);
    399                                     mTimelineLayout.requestLayout();
    400                                     ((FrameLayout.LayoutParams) mAlarmsView.getLayoutParams())
    401                                         .setMargins(0, 0, (int) -rightOffset, 0);
    402                                     mAlarmsView.requestLayout();
    403                                     setUndoBarRightMargin(currentTimelineWidth
    404                                             + mUndoBarInitialMargin);
    405                                 }
    406                             });
    407                             animator.start();
    408                         } else {
    409                             mTimelineLayout.setVisibility(View.VISIBLE);
    410                             mEmptyView.setVisibility(View.GONE);
    411                             setUndoBarRightMargin(mUndoBarInitialMargin + mTimelineViewWidth);
    412                         }
    413                     } else {
    414 
    415                         // If there is no timeline view, just hide the "no alarms" icon.
    416                         mEmptyView.setVisibility(View.GONE);
    417                     }
    418                 }
    419 
    420                 // Cache this adapter's count for when the adapter changes.
    421                 prevAdapterCount = count;
    422                 super.onChanged();
    423             }
    424         });
    425 
    426         if (mRingtoneTitleCache == null) {
    427             mRingtoneTitleCache = new Bundle();
    428         }
    429 
    430         mAlarmsList.setAdapter(mAdapter);
    431         mAlarmsList.setVerticalScrollBarEnabled(true);
    432         mAlarmsList.setOnCreateContextMenuListener(this);
    433 
    434         if (mUndoShowing) {
    435             showUndoBar();
    436         }
    437         return v;
    438     }
    439 
    440     private void setUndoBarRightMargin(int margin) {
    441         FrameLayout.LayoutParams params =
    442                 (FrameLayout.LayoutParams) mUndoBar.getLayoutParams();
    443         ((FrameLayout.LayoutParams) mUndoBar.getLayoutParams())
    444             .setMargins(params.leftMargin, params.topMargin, margin, params.bottomMargin);
    445         mUndoBar.requestLayout();
    446     }
    447 
    448     @Override
    449     public void onResume() {
    450         super.onResume();
    451         // Check if another app asked us to create a blank new alarm.
    452         final Intent intent = getActivity().getIntent();
    453         if (intent.hasExtra(ALARM_CREATE_NEW_INTENT_EXTRA)) {
    454             if (intent.getBooleanExtra(ALARM_CREATE_NEW_INTENT_EXTRA, false)) {
    455                 // An external app asked us to create a blank alarm.
    456                 startCreatingAlarm();
    457             }
    458 
    459             // Remove the CREATE_NEW extra now that we've processed it.
    460             intent.removeExtra(ALARM_CREATE_NEW_INTENT_EXTRA);
    461         } else if (intent.hasExtra(SCROLL_TO_ALARM_INTENT_EXTRA)) {
    462             long alarmId = intent.getLongExtra(SCROLL_TO_ALARM_INTENT_EXTRA, Alarm.INVALID_ID);
    463             if (alarmId != Alarm.INVALID_ID) {
    464                 mScrollToAlarmId = alarmId;
    465                 if (mCursorLoader != null && mCursorLoader.isStarted()) {
    466                     // We need to force a reload here to make sure we have the latest view
    467                     // of the data to scroll to.
    468                     mCursorLoader.forceLoad();
    469                 }
    470             }
    471 
    472             // Remove the SCROLL_TO_ALARM extra now that we've processed it.
    473             intent.removeExtra(SCROLL_TO_ALARM_INTENT_EXTRA);
    474         }
    475 
    476         // Make sure to use the child FragmentManager. We have to use that one for the
    477         // case where an intent comes in telling the activity to load the timepicker,
    478         // which means we have to use that one everywhere so that the fragment can get
    479         // correctly picked up here if it's open.
    480         TimePickerDialog tpd = (TimePickerDialog) getChildFragmentManager().
    481                 findFragmentByTag(AlarmUtils.FRAG_TAG_TIME_PICKER);
    482         if (tpd != null) {
    483             // The dialog is already open so we need to set the listener again.
    484             tpd.setOnTimeSetListener(this);
    485         }
    486     }
    487 
    488     private void hideUndoBar(boolean animate, MotionEvent event) {
    489         if (mUndoBar != null) {
    490             mUndoFrame.setVisibility(View.GONE);
    491             if (event != null && mUndoBar.isEventInToastBar(event)) {
    492                 // Avoid touches inside the undo bar.
    493                 return;
    494             }
    495             mUndoBar.hide(animate);
    496         }
    497         mDeletedAlarm = null;
    498         mUndoShowing = false;
    499     }
    500 
    501     private void showUndoBar() {
    502         mUndoFrame.setVisibility(View.VISIBLE);
    503         mUndoBar.show(new ActionableToastBar.ActionClickedListener() {
    504             @Override
    505             public void onActionClicked() {
    506                 asyncAddAlarm(mDeletedAlarm);
    507                 mDeletedAlarm = null;
    508                 mUndoShowing = false;
    509             }
    510         }, 0, getResources().getString(R.string.alarm_deleted), true, R.string.alarm_undo, true);
    511     }
    512 
    513     @Override
    514     public void onSaveInstanceState(Bundle outState) {
    515         super.onSaveInstanceState(outState);
    516         outState.putLongArray(KEY_EXPANDED_IDS, mAdapter.getExpandedArray());
    517         outState.putLongArray(KEY_REPEAT_CHECKED_IDS, mAdapter.getRepeatArray());
    518         outState.putLongArray(KEY_SELECTED_ALARMS, mAdapter.getSelectedAlarmsArray());
    519         outState.putBundle(KEY_RINGTONE_TITLE_CACHE, mRingtoneTitleCache);
    520         outState.putParcelable(KEY_DELETED_ALARM, mDeletedAlarm);
    521         outState.putBoolean(KEY_UNDO_SHOWING, mUndoShowing);
    522         outState.putBundle(KEY_PREVIOUS_DAY_MAP, mAdapter.getPreviousDaysOfWeekMap());
    523         outState.putParcelable(KEY_SELECTED_ALARM, mSelectedAlarm);
    524     }
    525 
    526     @Override
    527     public void onDestroy() {
    528         super.onDestroy();
    529         ToastMaster.cancelToast();
    530     }
    531 
    532     @Override
    533     public void onPause() {
    534         super.onPause();
    535         // When the user places the app in the background by pressing "home",
    536         // dismiss the toast bar. However, since there is no way to determine if
    537         // home was pressed, just dismiss any existing toast bar when restarting
    538         // the app.
    539         hideUndoBar(false, null);
    540     }
    541 
    542     // Callback used by TimePickerDialog
    543     @Override
    544     public void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute) {
    545         if (mSelectedAlarm == null) {
    546             // If mSelectedAlarm is null then we're creating a new alarm.
    547             Alarm a = new Alarm();
    548             a.alert = RingtoneManager.getActualDefaultRingtoneUri(getActivity(),
    549                     RingtoneManager.TYPE_ALARM);
    550             if (a.alert == null) {
    551                 a.alert = Uri.parse("content://settings/system/alarm_alert");
    552             }
    553             a.hour = hourOfDay;
    554             a.minutes = minute;
    555             a.enabled = true;
    556             asyncAddAlarm(a);
    557         } else {
    558             mSelectedAlarm.hour = hourOfDay;
    559             mSelectedAlarm.minutes = minute;
    560             mSelectedAlarm.enabled = true;
    561             mScrollToAlarmId = mSelectedAlarm.id;
    562             asyncUpdateAlarm(mSelectedAlarm, true);
    563             mSelectedAlarm = null;
    564         }
    565     }
    566 
    567     private void showLabelDialog(final Alarm alarm) {
    568         final FragmentTransaction ft = getFragmentManager().beginTransaction();
    569         final Fragment prev = getFragmentManager().findFragmentByTag("label_dialog");
    570         if (prev != null) {
    571             ft.remove(prev);
    572         }
    573         ft.addToBackStack(null);
    574 
    575         // Create and show the dialog.
    576         final LabelDialogFragment newFragment =
    577                 LabelDialogFragment.newInstance(alarm, alarm.label, getTag());
    578         newFragment.show(ft, "label_dialog");
    579     }
    580 
    581     public void setLabel(Alarm alarm, String label) {
    582         alarm.label = label;
    583         asyncUpdateAlarm(alarm, false);
    584     }
    585 
    586     @Override
    587     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    588         return Alarm.getAlarmsCursorLoader(getActivity());
    589     }
    590 
    591     @Override
    592     public void onLoadFinished(Loader<Cursor> cursorLoader, final Cursor data) {
    593         mAdapter.swapCursor(data);
    594         if (mScrollToAlarmId != -1) {
    595             scrollToAlarm(mScrollToAlarmId);
    596             mScrollToAlarmId = -1;
    597         }
    598     }
    599 
    600     /**
    601      * Scroll to alarm with given alarm id.
    602      *
    603      * @param alarmId The alarm id to scroll to.
    604      */
    605     private void scrollToAlarm(long alarmId) {
    606         int alarmPosition = -1;
    607         for (int i = 0; i < mAdapter.getCount(); i++) {
    608             long id = mAdapter.getItemId(i);
    609             if (id == alarmId) {
    610                 alarmPosition = i;
    611                 break;
    612             }
    613         }
    614 
    615         if (alarmPosition >= 0) {
    616             mAdapter.setNewAlarm(alarmId);
    617             mAlarmsList.smoothScrollToPositionFromTop(alarmPosition, 0);
    618         } else {
    619             // Trying to display a deleted alarm should only happen from a missed notification for
    620             // an alarm that has been marked deleted after use.
    621             Context context = getActivity().getApplicationContext();
    622             Toast toast = Toast.makeText(context, R.string.missed_alarm_has_been_deleted,
    623                     Toast.LENGTH_LONG);
    624             ToastMaster.setToast(toast);
    625             toast.show();
    626         }
    627     }
    628 
    629     @Override
    630     public void onLoaderReset(Loader<Cursor> cursorLoader) {
    631         mAdapter.swapCursor(null);
    632     }
    633 
    634     private void launchRingTonePicker(Alarm alarm) {
    635         mSelectedAlarm = alarm;
    636         Uri oldRingtone = Alarm.NO_RINGTONE_URI.equals(alarm.alert) ? null : alarm.alert;
    637         final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
    638         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, oldRingtone);
    639         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM);
    640         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
    641         startActivityForResult(intent, REQUEST_CODE_RINGTONE);
    642     }
    643 
    644     private void saveRingtoneUri(Intent intent) {
    645         Uri uri = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
    646         if (uri == null) {
    647             uri = Alarm.NO_RINGTONE_URI;
    648         }
    649         mSelectedAlarm.alert = uri;
    650 
    651         // Save the last selected ringtone as the default for new alarms
    652         if (!Alarm.NO_RINGTONE_URI.equals(uri)) {
    653             RingtoneManager.setActualDefaultRingtoneUri(
    654                     getActivity(), RingtoneManager.TYPE_ALARM, uri);
    655         }
    656         asyncUpdateAlarm(mSelectedAlarm, false);
    657     }
    658 
    659     @Override
    660     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    661         if (resultCode == Activity.RESULT_OK) {
    662             switch (requestCode) {
    663                 case REQUEST_CODE_RINGTONE:
    664                     saveRingtoneUri(data);
    665                     break;
    666                 default:
    667                     Log.w("Unhandled request code in onActivityResult: " + requestCode);
    668             }
    669         }
    670     }
    671 
    672     public class AlarmItemAdapter extends CursorAdapter {
    673         private static final int EXPAND_DURATION = 300;
    674         private static final int COLLAPSE_DURATION = 250;
    675 
    676         private final Context mContext;
    677         private final LayoutInflater mFactory;
    678         private final String[] mShortWeekDayStrings;
    679         private final String[] mLongWeekDayStrings;
    680         private final int mColorLit;
    681         private final int mColorDim;
    682         private final int mBackgroundColorExpanded;
    683         private final int mBackgroundColor;
    684         private final Typeface mRobotoNormal;
    685         private final Typeface mRobotoBold;
    686         private final ListView mList;
    687 
    688         private final HashSet<Long> mExpanded = new HashSet<Long>();
    689         private final HashSet<Long> mRepeatChecked = new HashSet<Long>();
    690         private final HashSet<Long> mSelectedAlarms = new HashSet<Long>();
    691         private Bundle mPreviousDaysOfWeekMap = new Bundle();
    692 
    693         private final boolean mHasVibrator;
    694         private final int mCollapseExpandHeight;
    695 
    696         // This determines the order in which it is shown and processed in the UI.
    697         private final int[] DAY_ORDER = new int[] {
    698                 Calendar.SUNDAY,
    699                 Calendar.MONDAY,
    700                 Calendar.TUESDAY,
    701                 Calendar.WEDNESDAY,
    702                 Calendar.THURSDAY,
    703                 Calendar.FRIDAY,
    704                 Calendar.SATURDAY,
    705         };
    706 
    707         public class ItemHolder {
    708 
    709             // views for optimization
    710             LinearLayout alarmItem;
    711             TextTime clock;
    712             Switch onoff;
    713             TextView daysOfWeek;
    714             TextView label;
    715             ImageView delete;
    716             View expandArea;
    717             View summary;
    718             TextView clickableLabel;
    719             CheckBox repeat;
    720             LinearLayout repeatDays;
    721             ViewGroup[] dayButtonParents = new ViewGroup[7];
    722             ToggleButton[] dayButtons = new ToggleButton[7];
    723             CheckBox vibrate;
    724             TextView ringtone;
    725             View hairLine;
    726             View arrow;
    727             View collapseExpandArea;
    728             View footerFiller;
    729 
    730             // Other states
    731             Alarm alarm;
    732         }
    733 
    734         // Used for scrolling an expanded item in the list to make sure it is fully visible.
    735         private long mScrollAlarmId = -1;
    736         private final Runnable mScrollRunnable = new Runnable() {
    737             @Override
    738             public void run() {
    739                 if (mScrollAlarmId != -1) {
    740                     View v = getViewById(mScrollAlarmId);
    741                     if (v != null) {
    742                         Rect rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
    743                         mList.requestChildRectangleOnScreen(v, rect, false);
    744                     }
    745                     mScrollAlarmId = -1;
    746                 }
    747             }
    748         };
    749 
    750         public AlarmItemAdapter(Context context, long[] expandedIds, long[] repeatCheckedIds,
    751                 long[] selectedAlarms, Bundle previousDaysOfWeekMap, ListView list) {
    752             super(context, null, 0);
    753             mContext = context;
    754             mFactory = LayoutInflater.from(context);
    755             mList = list;
    756 
    757             DateFormatSymbols dfs = new DateFormatSymbols();
    758             mShortWeekDayStrings = dfs.getShortWeekdays();
    759             mLongWeekDayStrings = dfs.getWeekdays();
    760 
    761             Resources res = mContext.getResources();
    762             mColorLit = res.getColor(R.color.clock_white);
    763             mColorDim = res.getColor(R.color.clock_gray);
    764             mBackgroundColorExpanded = res.getColor(R.color.alarm_whiteish);
    765             mBackgroundColor = R.drawable.alarm_background_normal;
    766 
    767             mRobotoBold = Typeface.create("sans-serif-condensed", Typeface.BOLD);
    768             mRobotoNormal = Typeface.create("sans-serif-condensed", Typeface.NORMAL);
    769 
    770             if (expandedIds != null) {
    771                 buildHashSetFromArray(expandedIds, mExpanded);
    772             }
    773             if (repeatCheckedIds != null) {
    774                 buildHashSetFromArray(repeatCheckedIds, mRepeatChecked);
    775             }
    776             if (previousDaysOfWeekMap != null) {
    777                 mPreviousDaysOfWeekMap = previousDaysOfWeekMap;
    778             }
    779             if (selectedAlarms != null) {
    780                 buildHashSetFromArray(selectedAlarms, mSelectedAlarms);
    781             }
    782 
    783             mHasVibrator = ((Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE))
    784                     .hasVibrator();
    785 
    786             mCollapseExpandHeight = (int) res.getDimension(R.dimen.collapse_expand_height);
    787         }
    788 
    789         public void removeSelectedId(int id) {
    790             mSelectedAlarms.remove(id);
    791         }
    792 
    793         @Override
    794         public View getView(int position, View convertView, ViewGroup parent) {
    795             if (!getCursor().moveToPosition(position)) {
    796                 // May happen if the last alarm was deleted and the cursor refreshed while the
    797                 // list is updated.
    798                 Log.v("couldn't move cursor to position " + position);
    799                 return null;
    800             }
    801             View v;
    802             if (convertView == null) {
    803                 v = newView(mContext, getCursor(), parent);
    804             } else {
    805                 // TODO temporary hack to prevent the convertView from not having stuff we need.
    806                 boolean badConvertView = convertView.findViewById(R.id.digital_clock) == null;
    807                 // Do a translation check to test for animation. Change this to something more
    808                 // reliable and robust in the future.
    809                 if (convertView.getTranslationX() != 0 || convertView.getTranslationY() != 0 ||
    810                         badConvertView) {
    811                     // view was animated, reset
    812                     v = newView(mContext, getCursor(), parent);
    813                 } else {
    814                     v = convertView;
    815                 }
    816             }
    817             bindView(v, mContext, getCursor());
    818             ItemHolder holder = (ItemHolder) v.getTag();
    819 
    820             // We need the footer for the last element of the array to allow the user to scroll
    821             // the item beyond the bottom button bar, which obscures the view.
    822             holder.footerFiller.setVisibility(position < getCount() - 1 ? View.GONE : View.VISIBLE);
    823             return v;
    824         }
    825 
    826         @Override
    827         public View newView(Context context, Cursor cursor, ViewGroup parent) {
    828             final View view = mFactory.inflate(R.layout.alarm_time, parent, false);
    829             setNewHolder(view);
    830             return view;
    831         }
    832 
    833         /**
    834          * In addition to changing the data set for the alarm list, swapCursor is now also
    835          * responsible for preparing the list view's pre-draw operation for any animations that
    836          * need to occur if an alarm was removed or added.
    837          */
    838         @Override
    839         public synchronized Cursor swapCursor(Cursor cursor) {
    840             Cursor c = super.swapCursor(cursor);
    841 
    842             if (mItemIdTopMap.isEmpty() && mAddedAlarm == null) {
    843                 return c;
    844             }
    845 
    846             final ListView list = mAlarmsList;
    847             final ViewTreeObserver observer = list.getViewTreeObserver();
    848 
    849             /*
    850              * Add a pre-draw listener to the observer to prepare for any possible animations to
    851              * the alarms within the list view.  The animations will occur if an alarm has been
    852              * removed or added.
    853              *
    854              * For alarm removal, the remaining children should all retain their initial starting
    855              * positions, and transition to their new positions.
    856              *
    857              * For alarm addition, the other children should all retain their initial starting
    858              * positions, transition to their new positions, and at the end of that transition, the
    859              * newly added alarm should appear in the designated space.
    860              */
    861             observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    862 
    863                 private View mAddedView;
    864 
    865                 @Override
    866                 public boolean onPreDraw() {
    867                     // Remove the pre-draw listener, as this only needs to occur once.
    868                     if (observer.isAlive()) {
    869                         observer.removeOnPreDrawListener(this);
    870                     }
    871                     boolean firstAnimation = true;
    872                     int firstVisiblePosition = list.getFirstVisiblePosition();
    873 
    874                     // Iterate through the children to prepare the add/remove animation.
    875                     for (int i = 0; i< list.getChildCount(); i++) {
    876                         final View child = list.getChildAt(i);
    877 
    878                         int position = firstVisiblePosition + i;
    879                         long itemId = mAdapter.getItemId(position);
    880 
    881                         // If this is the added alarm, set it invisible for now, and animate later.
    882                         if (mAddedAlarm != null && itemId == mAddedAlarm.id) {
    883                             mAddedView = child;
    884                             mAddedView.setAlpha(0.0f);
    885                             continue;
    886                         }
    887 
    888                         // The cached starting position of the child view.
    889                         Integer startTop = mItemIdTopMap.get(itemId);
    890                         // The new starting position of the child view.
    891                         int top = child.getTop();
    892 
    893                         // If there is no cached starting position, determine whether the item has
    894                         // come from the top of bottom of the list view.
    895                         if (startTop == null) {
    896                             int childHeight = child.getHeight() + list.getDividerHeight();
    897                             startTop = top + (i > 0 ? childHeight : -childHeight);
    898                         }
    899 
    900                         Log.d("Start Top: " + startTop + ", Top: " + top);
    901                         // If the starting position of the child view is different from the
    902                         // current position, animate the child.
    903                         if (startTop != top) {
    904                             int delta = startTop - top;
    905                             child.setTranslationY(delta);
    906                             child.animate().setDuration(ANIMATION_DURATION).translationY(0);
    907                             final View addedView = mAddedView;
    908                             if (firstAnimation) {
    909 
    910                                 // If this is the first child being animated, then after the
    911                                 // animation is complete, and animate in the added alarm (if one
    912                                 // exists).
    913                                 child.animate().withEndAction(new Runnable() {
    914 
    915                                     @Override
    916                                     public void run() {
    917 
    918 
    919                                         // If there was an added view, animate it in after
    920                                         // the other views have animated.
    921                                         if (addedView != null) {
    922                                             addedView.animate().alpha(1.0f)
    923                                                 .setDuration(ANIMATION_DURATION)
    924                                                 .withEndAction(new Runnable() {
    925 
    926                                                     @Override
    927                                                     public void run() {
    928                                                         // Re-enable the list after the add
    929                                                         // animation is complete.
    930                                                         list.setEnabled(true);
    931                                                     }
    932 
    933                                                 });
    934                                         } else {
    935                                             // Re-enable the list after animations are complete.
    936                                             list.setEnabled(true);
    937                                         }
    938                                     }
    939 
    940                                 });
    941                                 firstAnimation = false;
    942                             }
    943                         }
    944                     }
    945 
    946                     // If there were no child views (outside of a possible added view)
    947                     // that require animation...
    948                     if (firstAnimation) {
    949                         if (mAddedView != null) {
    950                             // If there is an added view, prepare animation for the added view.
    951                             Log.d("Animating added view...");
    952                             mAddedView.animate().alpha(1.0f)
    953                                 .setDuration(ANIMATION_DURATION)
    954                                 .withEndAction(new Runnable() {
    955                                     @Override
    956                                     public void run() {
    957                                         // Re-enable the list after animations are complete.
    958                                         list.setEnabled(true);
    959                                     }
    960                                 });
    961                         } else {
    962                             // Re-enable the list after animations are complete.
    963                             list.setEnabled(true);
    964                         }
    965                     }
    966 
    967                     mAddedAlarm = null;
    968                     mItemIdTopMap.clear();
    969                     return true;
    970                 }
    971             });
    972             return c;
    973         }
    974 
    975         private void setNewHolder(View view) {
    976             // standard view holder optimization
    977             final ItemHolder holder = new ItemHolder();
    978             holder.alarmItem = (LinearLayout) view.findViewById(R.id.alarm_item);
    979             holder.clock = (TextTime) view.findViewById(R.id.digital_clock);
    980             holder.onoff = (Switch) view.findViewById(R.id.onoff);
    981             holder.onoff.setTypeface(mRobotoNormal);
    982             holder.daysOfWeek = (TextView) view.findViewById(R.id.daysOfWeek);
    983             holder.label = (TextView) view.findViewById(R.id.label);
    984             holder.delete = (ImageView) view.findViewById(R.id.delete);
    985             holder.summary = view.findViewById(R.id.summary);
    986             holder.expandArea = view.findViewById(R.id.expand_area);
    987             holder.hairLine = view.findViewById(R.id.hairline);
    988             holder.arrow = view.findViewById(R.id.arrow);
    989             holder.repeat = (CheckBox) view.findViewById(R.id.repeat_onoff);
    990             holder.clickableLabel = (TextView) view.findViewById(R.id.edit_label);
    991             holder.repeatDays = (LinearLayout) view.findViewById(R.id.repeat_days);
    992             holder.collapseExpandArea = view.findViewById(R.id.collapse_expand);
    993             holder.footerFiller = view.findViewById(R.id.alarm_footer_filler);
    994             holder.footerFiller.setOnClickListener(new OnClickListener() {
    995 
    996                 @Override
    997                 public void onClick(View v) {
    998                     // Do nothing.
    999                 }
   1000             });
   1001 
   1002             // Build button for each day.
   1003             for (int i = 0; i < 7; i++) {
   1004                 final ViewGroup viewgroup = (ViewGroup) mFactory.inflate(R.layout.day_button,
   1005                         holder.repeatDays, false);
   1006                 final ToggleButton button = (ToggleButton) viewgroup.getChildAt(0);
   1007                 final int dayToShowIndex = DAY_ORDER[i];
   1008                 button.setText(mShortWeekDayStrings[dayToShowIndex]);
   1009                 button.setTextOn(mShortWeekDayStrings[dayToShowIndex]);
   1010                 button.setTextOff(mShortWeekDayStrings[dayToShowIndex]);
   1011                 button.setContentDescription(mLongWeekDayStrings[dayToShowIndex]);
   1012                 holder.repeatDays.addView(viewgroup);
   1013                 holder.dayButtons[i] = button;
   1014                 holder.dayButtonParents[i] = viewgroup;
   1015             }
   1016             holder.vibrate = (CheckBox) view.findViewById(R.id.vibrate_onoff);
   1017             holder.ringtone = (TextView) view.findViewById(R.id.choose_ringtone);
   1018 
   1019             view.setTag(holder);
   1020         }
   1021 
   1022         @Override
   1023         public void bindView(final View view, Context context, final Cursor cursor) {
   1024             final Alarm alarm = new Alarm(cursor);
   1025             Object tag = view.getTag();
   1026             if (tag == null) {
   1027                 // The view was converted but somehow lost its tag.
   1028                 setNewHolder(view);
   1029             }
   1030             final ItemHolder itemHolder = (ItemHolder) tag;
   1031             itemHolder.alarm = alarm;
   1032 
   1033             // We must unset the listener first because this maybe a recycled view so changing the
   1034             // state would affect the wrong alarm.
   1035             itemHolder.onoff.setOnCheckedChangeListener(null);
   1036             itemHolder.onoff.setChecked(alarm.enabled);
   1037 
   1038             if (mSelectedAlarms.contains(itemHolder.alarm.id)) {
   1039                 itemHolder.alarmItem.setBackgroundColor(mBackgroundColorExpanded);
   1040                 setItemAlpha(itemHolder, true);
   1041                 itemHolder.onoff.setEnabled(false);
   1042             } else {
   1043                 itemHolder.onoff.setEnabled(true);
   1044                 itemHolder.alarmItem.setBackgroundResource(mBackgroundColor);
   1045                 setItemAlpha(itemHolder, itemHolder.onoff.isChecked());
   1046             }
   1047             itemHolder.clock.setFormat(
   1048                     (int)mContext.getResources().getDimension(R.dimen.alarm_label_size));
   1049             itemHolder.clock.setTime(alarm.hour, alarm.minutes);
   1050             itemHolder.clock.setClickable(true);
   1051             itemHolder.clock.setOnClickListener(new View.OnClickListener() {
   1052                 @Override
   1053                 public void onClick(View view) {
   1054                     mSelectedAlarm = itemHolder.alarm;
   1055                     AlarmUtils.showTimeEditDialog(getChildFragmentManager(),
   1056                             alarm, AlarmClockFragment.this
   1057                             , DateFormat.is24HourFormat(getActivity()));
   1058                     expandAlarm(itemHolder, true);
   1059                     itemHolder.alarmItem.post(mScrollRunnable);
   1060                 }
   1061             });
   1062 
   1063             final CompoundButton.OnCheckedChangeListener onOffListener =
   1064                     new CompoundButton.OnCheckedChangeListener() {
   1065                         @Override
   1066                         public void onCheckedChanged(CompoundButton compoundButton,
   1067                                 boolean checked) {
   1068                             if (checked != alarm.enabled) {
   1069                                 setItemAlpha(itemHolder, checked);
   1070                                 alarm.enabled = checked;
   1071                                 asyncUpdateAlarm(alarm, alarm.enabled);
   1072                             }
   1073                         }
   1074                     };
   1075 
   1076             itemHolder.onoff.setOnCheckedChangeListener(onOffListener);
   1077 
   1078             boolean expanded = isAlarmExpanded(alarm);
   1079             itemHolder.expandArea.setVisibility(expanded? View.VISIBLE : View.GONE);
   1080             itemHolder.summary.setVisibility(expanded? View.GONE : View.VISIBLE);
   1081 
   1082             String labelSpace = "";
   1083             // Set the repeat text or leave it blank if it does not repeat.
   1084             final String daysOfWeekStr =
   1085                     alarm.daysOfWeek.toString(AlarmClockFragment.this.getActivity(), false);
   1086             if (daysOfWeekStr != null && daysOfWeekStr.length() != 0) {
   1087                 itemHolder.daysOfWeek.setText(daysOfWeekStr);
   1088                 itemHolder.daysOfWeek.setContentDescription(alarm.daysOfWeek.toAccessibilityString(
   1089                         AlarmClockFragment.this.getActivity()));
   1090                 itemHolder.daysOfWeek.setVisibility(View.VISIBLE);
   1091                 labelSpace = "  ";
   1092                 itemHolder.daysOfWeek.setOnClickListener(new View.OnClickListener() {
   1093                     @Override
   1094                     public void onClick(View view) {
   1095                         expandAlarm(itemHolder, true);
   1096                         itemHolder.alarmItem.post(mScrollRunnable);
   1097                     }
   1098                 });
   1099 
   1100             } else {
   1101                 itemHolder.daysOfWeek.setVisibility(View.GONE);
   1102             }
   1103 
   1104             if (alarm.label != null && alarm.label.length() != 0) {
   1105                 itemHolder.label.setText(alarm.label + labelSpace);
   1106                 itemHolder.label.setVisibility(View.VISIBLE);
   1107                 itemHolder.label.setContentDescription(
   1108                         mContext.getResources().getString(R.string.label_description) + " "
   1109                         + alarm.label);
   1110                 itemHolder.label.setOnClickListener(new View.OnClickListener() {
   1111                     @Override
   1112                     public void onClick(View view) {
   1113                         expandAlarm(itemHolder, true);
   1114                         itemHolder.alarmItem.post(mScrollRunnable);
   1115                     }
   1116                 });
   1117             } else {
   1118                 itemHolder.label.setVisibility(View.GONE);
   1119             }
   1120 
   1121             itemHolder.delete.setOnClickListener(new View.OnClickListener() {
   1122                 @Override
   1123                 public void onClick(View v) {
   1124                     mDeletedAlarm = alarm;
   1125 
   1126                     view.animate().setDuration(ANIMATION_DURATION).alpha(0).translationY(-1)
   1127                     .withEndAction(new Runnable() {
   1128 
   1129                         @Override
   1130                         public void run() {
   1131                             asyncDeleteAlarm(mDeletedAlarm, view);
   1132                         }
   1133                     });
   1134                 }
   1135             });
   1136 
   1137             if (expanded) {
   1138                 expandAlarm(itemHolder, false);
   1139             } else {
   1140                 collapseAlarm(itemHolder, false);
   1141             }
   1142 
   1143             itemHolder.alarmItem.setOnClickListener(new View.OnClickListener() {
   1144                 @Override
   1145                 public void onClick(View view) {
   1146                     if (isAlarmExpanded(alarm)) {
   1147                         collapseAlarm(itemHolder, true);
   1148                     } else {
   1149                         expandAlarm(itemHolder, true);
   1150                     }
   1151                 }
   1152             });
   1153         }
   1154 
   1155         private void bindExpandArea(final ItemHolder itemHolder, final Alarm alarm) {
   1156             // Views in here are not bound until the item is expanded.
   1157 
   1158             if (alarm.label != null && alarm.label.length() > 0) {
   1159                 itemHolder.clickableLabel.setText(alarm.label);
   1160                 itemHolder.clickableLabel.setTextColor(mColorLit);
   1161             } else {
   1162                 itemHolder.clickableLabel.setText(R.string.label);
   1163                 itemHolder.clickableLabel.setTextColor(mColorDim);
   1164             }
   1165             itemHolder.clickableLabel.setOnClickListener(new View.OnClickListener() {
   1166                 @Override
   1167                 public void onClick(View view) {
   1168                     showLabelDialog(alarm);
   1169                 }
   1170             });
   1171 
   1172             if (mRepeatChecked.contains(alarm.id) || itemHolder.alarm.daysOfWeek.isRepeating()) {
   1173                 itemHolder.repeat.setChecked(true);
   1174                 itemHolder.repeatDays.setVisibility(View.VISIBLE);
   1175             } else {
   1176                 itemHolder.repeat.setChecked(false);
   1177                 itemHolder.repeatDays.setVisibility(View.GONE);
   1178             }
   1179             itemHolder.repeat.setOnClickListener(new View.OnClickListener() {
   1180                 @Override
   1181                 public void onClick(View view) {
   1182                     final boolean checked = ((CheckBox) view).isChecked();
   1183                     if (checked) {
   1184                         // Show days
   1185                         itemHolder.repeatDays.setVisibility(View.VISIBLE);
   1186                         mRepeatChecked.add(alarm.id);
   1187 
   1188                         // Set all previously set days
   1189                         // or
   1190                         // Set all days if no previous.
   1191                         final int bitSet = mPreviousDaysOfWeekMap.getInt("" + alarm.id);
   1192                         alarm.daysOfWeek.setBitSet(bitSet);
   1193                         if (!alarm.daysOfWeek.isRepeating()) {
   1194                             alarm.daysOfWeek.setDaysOfWeek(true, DAY_ORDER);
   1195                         }
   1196                         updateDaysOfWeekButtons(itemHolder, alarm.daysOfWeek);
   1197                     } else {
   1198                         itemHolder.repeatDays.setVisibility(View.GONE);
   1199                         mRepeatChecked.remove(alarm.id);
   1200 
   1201                         // Remember the set days in case the user wants it back.
   1202                         final int bitSet = alarm.daysOfWeek.getBitSet();
   1203                         mPreviousDaysOfWeekMap.putInt("" + alarm.id, bitSet);
   1204 
   1205                         // Remove all repeat days
   1206                         alarm.daysOfWeek.clearAllDays();
   1207                     }
   1208                     asyncUpdateAlarm(alarm, false);
   1209                 }
   1210             });
   1211 
   1212             updateDaysOfWeekButtons(itemHolder, alarm.daysOfWeek);
   1213             for (int i = 0; i < 7; i++) {
   1214                 final int buttonIndex = i;
   1215 
   1216                 itemHolder.dayButtonParents[i].setOnClickListener(new View.OnClickListener() {
   1217                     @Override
   1218                     public void onClick(View view) {
   1219                         itemHolder.dayButtons[buttonIndex].toggle();
   1220                         final boolean checked = itemHolder.dayButtons[buttonIndex].isChecked();
   1221                         int day = DAY_ORDER[buttonIndex];
   1222                         alarm.daysOfWeek.setDaysOfWeek(checked, day);
   1223                         if (checked) {
   1224                             turnOnDayOfWeek(itemHolder, buttonIndex);
   1225                         } else {
   1226                             turnOffDayOfWeek(itemHolder, buttonIndex);
   1227 
   1228                             // See if this was the last day, if so, un-check the repeat box.
   1229                             if (!alarm.daysOfWeek.isRepeating()) {
   1230                                 itemHolder.repeatDays.setVisibility(View.GONE);
   1231                                 itemHolder.repeat.setTextColor(mColorDim);
   1232                                 mRepeatChecked.remove(alarm.id);
   1233 
   1234                                 // Set history to no days, so it will be everyday when repeat is
   1235                                 // turned back on
   1236                                 mPreviousDaysOfWeekMap.putInt("" + alarm.id,
   1237                                         DaysOfWeek.NO_DAYS_SET);
   1238                             }
   1239                         }
   1240                         asyncUpdateAlarm(alarm, false);
   1241                     }
   1242                 });
   1243             }
   1244 
   1245 
   1246             if (!mHasVibrator) {
   1247                 itemHolder.vibrate.setVisibility(View.INVISIBLE);
   1248             } else {
   1249                 itemHolder.vibrate.setVisibility(View.VISIBLE);
   1250                 if (!alarm.vibrate) {
   1251                     itemHolder.vibrate.setChecked(false);
   1252                     itemHolder.vibrate.setTextColor(mColorDim);
   1253                 } else {
   1254                     itemHolder.vibrate.setChecked(true);
   1255                     itemHolder.vibrate.setTextColor(mColorLit);
   1256                 }
   1257             }
   1258 
   1259             itemHolder.vibrate.setOnClickListener(new View.OnClickListener() {
   1260                 @Override
   1261                 public void onClick(View v) {
   1262                     final boolean checked = ((CheckBox) v).isChecked();
   1263                     if (checked) {
   1264                         itemHolder.vibrate.setTextColor(mColorLit);
   1265                     } else {
   1266                         itemHolder.vibrate.setTextColor(mColorDim);
   1267                     }
   1268                     alarm.vibrate = checked;
   1269                     asyncUpdateAlarm(alarm, false);
   1270                 }
   1271             });
   1272 
   1273             final String ringtone;
   1274             if (Alarm.NO_RINGTONE_URI.equals(alarm.alert)) {
   1275                 ringtone = mContext.getResources().getString(R.string.silent_alarm_summary);
   1276             } else {
   1277                 ringtone = getRingToneTitle(alarm.alert);
   1278             }
   1279             itemHolder.ringtone.setText(ringtone);
   1280             itemHolder.ringtone.setContentDescription(
   1281                     mContext.getResources().getString(R.string.ringtone_description) + " "
   1282                             + ringtone);
   1283             itemHolder.ringtone.setOnClickListener(new View.OnClickListener() {
   1284                 @Override
   1285                 public void onClick(View view) {
   1286                     launchRingTonePicker(alarm);
   1287                 }
   1288             });
   1289         }
   1290 
   1291         // Sets the alpha of the item except the on/off switch. This gives a visual effect
   1292         // for enabled/disabled alarm while leaving the on/off switch more visible
   1293         private void setItemAlpha(ItemHolder holder, boolean enabled) {
   1294             float alpha = enabled ? 1f : 0.5f;
   1295             holder.clock.setAlpha(alpha);
   1296             holder.summary.setAlpha(alpha);
   1297             holder.expandArea.setAlpha(alpha);
   1298             holder.delete.setAlpha(alpha);
   1299             holder.daysOfWeek.setAlpha(alpha);
   1300         }
   1301 
   1302         private void updateDaysOfWeekButtons(ItemHolder holder, DaysOfWeek daysOfWeek) {
   1303             HashSet<Integer> setDays = daysOfWeek.getSetDays();
   1304             for (int i = 0; i < 7; i++) {
   1305                 if (setDays.contains(DAY_ORDER[i])) {
   1306                     turnOnDayOfWeek(holder, i);
   1307                 } else {
   1308                     turnOffDayOfWeek(holder, i);
   1309                 }
   1310             }
   1311         }
   1312 
   1313         public void toggleSelectState(View v) {
   1314             // long press could be on the parent view or one of its childs, so find the parent view
   1315             v = getTopParent(v);
   1316             if (v != null) {
   1317                 long id = ((ItemHolder)v.getTag()).alarm.id;
   1318                 if (mSelectedAlarms.contains(id)) {
   1319                     mSelectedAlarms.remove(id);
   1320                 } else {
   1321                     mSelectedAlarms.add(id);
   1322                 }
   1323             }
   1324         }
   1325 
   1326         private View getTopParent(View v) {
   1327             while (v != null && v.getId() != R.id.alarm_item) {
   1328                 v = (View) v.getParent();
   1329             }
   1330             return v;
   1331         }
   1332 
   1333         public int getSelectedItemsNum() {
   1334             return mSelectedAlarms.size();
   1335         }
   1336 
   1337         private void turnOffDayOfWeek(ItemHolder holder, int dayIndex) {
   1338             holder.dayButtons[dayIndex].setChecked(false);
   1339             holder.dayButtons[dayIndex].setTextColor(mColorDim);
   1340             holder.dayButtons[dayIndex].setTypeface(mRobotoNormal);
   1341         }
   1342 
   1343         private void turnOnDayOfWeek(ItemHolder holder, int dayIndex) {
   1344             holder.dayButtons[dayIndex].setChecked(true);
   1345             holder.dayButtons[dayIndex].setTextColor(mColorLit);
   1346             holder.dayButtons[dayIndex].setTypeface(mRobotoBold);
   1347         }
   1348 
   1349 
   1350         /**
   1351          * Does a read-through cache for ringtone titles.
   1352          *
   1353          * @param uri The uri of the ringtone.
   1354          * @return The ringtone title. {@literal null} if no matching ringtone found.
   1355          */
   1356         private String getRingToneTitle(Uri uri) {
   1357             // Try the cache first
   1358             String title = mRingtoneTitleCache.getString(uri.toString());
   1359             if (title == null) {
   1360                 // This is slow because a media player is created during Ringtone object creation.
   1361                 Ringtone ringTone = RingtoneManager.getRingtone(mContext, uri);
   1362                 title = ringTone.getTitle(mContext);
   1363                 if (title != null) {
   1364                     mRingtoneTitleCache.putString(uri.toString(), title);
   1365                 }
   1366             }
   1367             return title;
   1368         }
   1369 
   1370         public void setNewAlarm(long alarmId) {
   1371             mExpanded.add(alarmId);
   1372         }
   1373 
   1374         /**
   1375          * Expands the alarm for editing.
   1376          *
   1377          * @param itemHolder The item holder instance.
   1378          */
   1379         private void expandAlarm(final ItemHolder itemHolder, boolean animate) {
   1380             mExpanded.add(itemHolder.alarm.id);
   1381             bindExpandArea(itemHolder, itemHolder.alarm);
   1382             // Scroll the view to make sure it is fully viewed
   1383             mScrollAlarmId = itemHolder.alarm.id;
   1384 
   1385             // Save the starting height so we can animate from this value.
   1386             final int startingHeight = itemHolder.alarmItem.getHeight();
   1387 
   1388             // Set the expand area to visible so we can measure the height to animate to.
   1389             itemHolder.alarmItem.setBackgroundColor(mBackgroundColorExpanded);
   1390             itemHolder.expandArea.setVisibility(View.VISIBLE);
   1391 
   1392             if (!animate) {
   1393                 // Set the "end" layout and don't do the animation.
   1394                 itemHolder.arrow.setRotation(180);
   1395                 // We need to translate the hairline up, so the height of the collapseArea
   1396                 // needs to be measured to know how high to translate it.
   1397                 final ViewTreeObserver observer = mAlarmsList.getViewTreeObserver();
   1398                 observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
   1399                     @Override
   1400                     public boolean onPreDraw() {
   1401                         // We don't want to continue getting called for every listview drawing.
   1402                         if (observer.isAlive()) {
   1403                             observer.removeOnPreDrawListener(this);
   1404                         }
   1405                         int hairlineHeight = itemHolder.hairLine.getHeight();
   1406                         int collapseHeight =
   1407                                 itemHolder.collapseExpandArea.getHeight() - hairlineHeight;
   1408                         itemHolder.hairLine.setTranslationY(-collapseHeight);
   1409                         return true;
   1410                     }
   1411                 });
   1412                 return;
   1413             }
   1414 
   1415             // Add an onPreDrawListener, which gets called after measurement but before the draw.
   1416             // This way we can check the height we need to animate to before any drawing.
   1417             // Note the series of events:
   1418             //  * expandArea is set to VISIBLE, which causes a layout pass
   1419             //  * the view is measured, and our onPreDrawListener is called
   1420             //  * we set up the animation using the start and end values.
   1421             //  * the height is set back to the starting point so it can be animated down.
   1422             //  * request another layout pass.
   1423             //  * return false so that onDraw() is not called for the single frame before
   1424             //    the animations have started.
   1425             final ViewTreeObserver observer = mAlarmsList.getViewTreeObserver();
   1426             observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
   1427                 @Override
   1428                 public boolean onPreDraw() {
   1429                     // We don't want to continue getting called for every listview drawing.
   1430                     if (observer.isAlive()) {
   1431                         observer.removeOnPreDrawListener(this);
   1432                     }
   1433                     // Calculate some values to help with the animation.
   1434                     final int endingHeight = itemHolder.alarmItem.getHeight();
   1435                     final int distance = endingHeight - startingHeight;
   1436                     final int collapseHeight = itemHolder.collapseExpandArea.getHeight();
   1437                     int hairlineHeight = itemHolder.hairLine.getHeight();
   1438                     final int hairlineDistance = collapseHeight - hairlineHeight;
   1439 
   1440                     // Set the height back to the start state of the animation.
   1441                     itemHolder.alarmItem.getLayoutParams().height = startingHeight;
   1442                     // To allow the expandArea to glide in with the expansion animation, set a
   1443                     // negative top margin, which will animate down to a margin of 0 as the height
   1444                     // is increased.
   1445                     // Note that we need to maintain the bottom margin as a fixed value (instead of
   1446                     // just using a listview, to allow for a flatter hierarchy) to fit the bottom
   1447                     // bar underneath.
   1448                     FrameLayout.LayoutParams expandParams = (FrameLayout.LayoutParams)
   1449                             itemHolder.expandArea.getLayoutParams();
   1450                     expandParams.setMargins(0, -distance, 0, collapseHeight);
   1451                     itemHolder.alarmItem.requestLayout();
   1452 
   1453                     // Set up the animator to animate the expansion.
   1454                     ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f)
   1455                             .setDuration(EXPAND_DURATION);
   1456                     animator.setInterpolator(mExpandInterpolator);
   1457                     animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   1458                         @Override
   1459                         public void onAnimationUpdate(ValueAnimator animator) {
   1460                             Float value = (Float) animator.getAnimatedValue();
   1461 
   1462                             // For each value from 0 to 1, animate the various parts of the layout.
   1463                             itemHolder.alarmItem.getLayoutParams().height =
   1464                                     (int) (value * distance + startingHeight);
   1465                             FrameLayout.LayoutParams expandParams = (FrameLayout.LayoutParams)
   1466                                     itemHolder.expandArea.getLayoutParams();
   1467                             expandParams.setMargins(
   1468                                     0, (int) -((1 - value) * distance), 0, collapseHeight);
   1469                             itemHolder.arrow.setRotation(180 * value);
   1470                             itemHolder.hairLine.setTranslationY(-hairlineDistance * value);
   1471                             itemHolder.summary.setAlpha(1 - value);
   1472 
   1473                             itemHolder.alarmItem.requestLayout();
   1474                         }
   1475                     });
   1476                     // Set everything to their final values when the animation's done.
   1477                     animator.addListener(new AnimatorListener() {
   1478                         @Override
   1479                         public void onAnimationEnd(Animator animation) {
   1480                             // Set it back to wrap content since we'd explicitly set the height.
   1481                             itemHolder.alarmItem.getLayoutParams().height =
   1482                                     LayoutParams.WRAP_CONTENT;
   1483                             itemHolder.arrow.setRotation(180);
   1484                             itemHolder.hairLine.setTranslationY(-hairlineDistance);
   1485                             itemHolder.summary.setVisibility(View.GONE);
   1486                         }
   1487 
   1488                         @Override
   1489                         public void onAnimationCancel(Animator animation) {
   1490                             // TODO we may have to deal with cancelations of the animation.
   1491                         }
   1492 
   1493                         @Override
   1494                         public void onAnimationRepeat(Animator animation) { }
   1495                         @Override
   1496                         public void onAnimationStart(Animator animation) { }
   1497                     });
   1498                     animator.start();
   1499 
   1500                     // Return false so this draw does not occur to prevent the final frame from
   1501                     // being drawn for the single frame before the animations start.
   1502                     return false;
   1503                 }
   1504             });
   1505         }
   1506 
   1507         private boolean isAlarmExpanded(Alarm alarm) {
   1508             return mExpanded.contains(alarm.id);
   1509         }
   1510 
   1511         private void collapseAlarm(final ItemHolder itemHolder, boolean animate) {
   1512             mExpanded.remove(itemHolder.alarm.id);
   1513 
   1514             // Save the starting height so we can animate from this value.
   1515             final int startingHeight = itemHolder.alarmItem.getHeight();
   1516 
   1517             // Set the expand area to gone so we can measure the height to animate to.
   1518             itemHolder.alarmItem.setBackgroundResource(mBackgroundColor);
   1519             itemHolder.expandArea.setVisibility(View.GONE);
   1520 
   1521             if (!animate) {
   1522                 // Set the "end" layout and don't do the animation.
   1523                 itemHolder.arrow.setRotation(0);
   1524                 itemHolder.hairLine.setTranslationY(0);
   1525                 return;
   1526             }
   1527 
   1528             // Add an onPreDrawListener, which gets called after measurement but before the draw.
   1529             // This way we can check the height we need to animate to before any drawing.
   1530             // Note the series of events:
   1531             //  * expandArea is set to GONE, which causes a layout pass
   1532             //  * the view is measured, and our onPreDrawListener is called
   1533             //  * we set up the animation using the start and end values.
   1534             //  * expandArea is set to VISIBLE again so it can be shown animating.
   1535             //  * request another layout pass.
   1536             //  * return false so that onDraw() is not called for the single frame before
   1537             //    the animations have started.
   1538             final ViewTreeObserver observer = mAlarmsList.getViewTreeObserver();
   1539             observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
   1540                 @Override
   1541                 public boolean onPreDraw() {
   1542                     if (observer.isAlive()) {
   1543                         observer.removeOnPreDrawListener(this);
   1544                     }
   1545 
   1546                     // Calculate some values to help with the animation.
   1547                     final int endingHeight = itemHolder.alarmItem.getHeight();
   1548                     final int distance = endingHeight - startingHeight;
   1549                     int hairlineHeight = itemHolder.hairLine.getHeight();
   1550                     final int hairlineDistance = mCollapseExpandHeight - hairlineHeight;
   1551 
   1552                     // Re-set the visibilities for the start state of the animation.
   1553                     itemHolder.expandArea.setVisibility(View.VISIBLE);
   1554                     itemHolder.summary.setVisibility(View.VISIBLE);
   1555                     itemHolder.summary.setAlpha(1);
   1556 
   1557                     // Set up the animator to animate the expansion.
   1558                     ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f)
   1559                             .setDuration(COLLAPSE_DURATION);
   1560                     animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   1561                         @Override
   1562                         public void onAnimationUpdate(ValueAnimator animator) {
   1563                             Float value = (Float) animator.getAnimatedValue();
   1564 
   1565                             // For each value from 0 to 1, animate the various parts of the layout.
   1566                             itemHolder.alarmItem.getLayoutParams().height =
   1567                                     (int) (value * distance + startingHeight);
   1568                             FrameLayout.LayoutParams expandParams = (FrameLayout.LayoutParams)
   1569                                     itemHolder.expandArea.getLayoutParams();
   1570                             expandParams.setMargins(
   1571                                     0, (int) (value * distance), 0, mCollapseExpandHeight);
   1572                             itemHolder.arrow.setRotation(180 * (1 - value));
   1573                             itemHolder.hairLine.setTranslationY(-hairlineDistance * (1 - value));
   1574                             itemHolder.summary.setAlpha(value);
   1575 
   1576                             itemHolder.alarmItem.requestLayout();
   1577                         }
   1578                     });
   1579                     animator.setInterpolator(mCollapseInterpolator);
   1580                     // Set everything to their final values when the animation's done.
   1581                     animator.addListener(new AnimatorListener() {
   1582                         @Override
   1583                         public void onAnimationEnd(Animator animation) {
   1584                             // Set it back to wrap content since we'd explicitly set the height.
   1585                             itemHolder.alarmItem.getLayoutParams().height =
   1586                                     LayoutParams.WRAP_CONTENT;
   1587 
   1588                             FrameLayout.LayoutParams expandParams = (FrameLayout.LayoutParams)
   1589                                     itemHolder.expandArea.getLayoutParams();
   1590                             expandParams.setMargins(0, 0, 0, mCollapseExpandHeight);
   1591 
   1592                             itemHolder.expandArea.setVisibility(View.GONE);
   1593                             itemHolder.arrow.setRotation(0);
   1594                             itemHolder.hairLine.setTranslationY(0);
   1595                         }
   1596 
   1597                         @Override
   1598                         public void onAnimationCancel(Animator animation) {
   1599                             // TODO we may have to deal with cancelations of the animation.
   1600                         }
   1601 
   1602                         @Override
   1603                         public void onAnimationRepeat(Animator animation) { }
   1604                         @Override
   1605                         public void onAnimationStart(Animator animation) { }
   1606                     });
   1607                     animator.start();
   1608 
   1609                     return false;
   1610                 }
   1611             });
   1612         }
   1613 
   1614         @Override
   1615         public int getViewTypeCount() {
   1616             return 1;
   1617         }
   1618 
   1619         private View getViewById(long id) {
   1620             for (int i = 0; i < mList.getCount(); i++) {
   1621                 View v = mList.getChildAt(i);
   1622                 if (v != null) {
   1623                     ItemHolder h = (ItemHolder)(v.getTag());
   1624                     if (h != null && h.alarm.id == id) {
   1625                         return v;
   1626                     }
   1627                 }
   1628             }
   1629             return null;
   1630         }
   1631 
   1632         public long[] getExpandedArray() {
   1633             int index = 0;
   1634             long[] ids = new long[mExpanded.size()];
   1635             for (long id : mExpanded) {
   1636                 ids[index] = id;
   1637                 index++;
   1638             }
   1639             return ids;
   1640         }
   1641 
   1642         public long[] getSelectedAlarmsArray() {
   1643             int index = 0;
   1644             long[] ids = new long[mSelectedAlarms.size()];
   1645             for (long id : mSelectedAlarms) {
   1646                 ids[index] = id;
   1647                 index++;
   1648             }
   1649             return ids;
   1650         }
   1651 
   1652         public long[] getRepeatArray() {
   1653             int index = 0;
   1654             long[] ids = new long[mRepeatChecked.size()];
   1655             for (long id : mRepeatChecked) {
   1656                 ids[index] = id;
   1657                 index++;
   1658             }
   1659             return ids;
   1660         }
   1661 
   1662         public Bundle getPreviousDaysOfWeekMap() {
   1663             return mPreviousDaysOfWeekMap;
   1664         }
   1665 
   1666         private void buildHashSetFromArray(long[] ids, HashSet<Long> set) {
   1667             for (long id : ids) {
   1668                 set.add(id);
   1669             }
   1670         }
   1671     }
   1672 
   1673     private void startCreatingAlarm() {
   1674         // Set the "selected" alarm as null, and we'll create the new one when the timepicker
   1675         // comes back.
   1676         mSelectedAlarm = null;
   1677         AlarmUtils.showTimeEditDialog(getChildFragmentManager(),
   1678                 null, AlarmClockFragment.this, DateFormat.is24HourFormat(getActivity()));
   1679     }
   1680 
   1681     private static AlarmInstance setupAlarmInstance(Context context, Alarm alarm) {
   1682         ContentResolver cr = context.getContentResolver();
   1683         AlarmInstance newInstance = alarm.createInstanceAfter(Calendar.getInstance());
   1684         newInstance = AlarmInstance.addInstance(cr, newInstance);
   1685         // Register instance to state manager
   1686         AlarmStateManager.registerInstance(context, newInstance, true);
   1687         return newInstance;
   1688     }
   1689 
   1690     private void asyncDeleteAlarm(final Alarm alarm, final View viewToRemove) {
   1691         final Context context = AlarmClockFragment.this.getActivity().getApplicationContext();
   1692         final AsyncTask<Void, Void, Void> deleteTask = new AsyncTask<Void, Void, Void>() {
   1693             @Override
   1694             public synchronized void onPreExecute() {
   1695                 if (viewToRemove == null) {
   1696                     return;
   1697                 }
   1698                 // The alarm list needs to be disabled until the animation finishes to prevent
   1699                 // possible concurrency issues.  It becomes re-enabled after the animations have
   1700                 // completed.
   1701                 mAlarmsList.setEnabled(false);
   1702 
   1703                 // Store all of the current list view item positions in memory for animation.
   1704                 final ListView list = mAlarmsList;
   1705                 int firstVisiblePosition = list.getFirstVisiblePosition();
   1706                 for (int i=0; i<list.getChildCount(); i++) {
   1707                     View child = list.getChildAt(i);
   1708                     if (child != viewToRemove) {
   1709                         int position = firstVisiblePosition + i;
   1710                         long itemId = mAdapter.getItemId(position);
   1711                         mItemIdTopMap.put(itemId, child.getTop());
   1712                     }
   1713                 }
   1714             }
   1715 
   1716             @Override
   1717             protected Void doInBackground(Void... parameters) {
   1718                 // Activity may be closed at this point , make sure data is still valid
   1719                 if (context != null && alarm != null) {
   1720                     ContentResolver cr = context.getContentResolver();
   1721                     AlarmStateManager.deleteAllInstances(context, alarm.id);
   1722                     Alarm.deleteAlarm(cr, alarm.id);
   1723                 }
   1724                 return null;
   1725             }
   1726         };
   1727         mUndoShowing = true;
   1728         deleteTask.execute();
   1729     }
   1730 
   1731     private void asyncAddAlarm(final Alarm alarm) {
   1732         final Context context = AlarmClockFragment.this.getActivity().getApplicationContext();
   1733         final AsyncTask<Void, Void, AlarmInstance> updateTask =
   1734                 new AsyncTask<Void, Void, AlarmInstance>() {
   1735             @Override
   1736             public synchronized void onPreExecute() {
   1737                 final ListView list = mAlarmsList;
   1738                 // The alarm list needs to be disabled until the animation finishes to prevent
   1739                 // possible concurrency issues.  It becomes re-enabled after the animations have
   1740                 // completed.
   1741                 mAlarmsList.setEnabled(false);
   1742 
   1743                 // Store all of the current list view item positions in memory for animation.
   1744                 int firstVisiblePosition = list.getFirstVisiblePosition();
   1745                 for (int i=0; i<list.getChildCount(); i++) {
   1746                     View child = list.getChildAt(i);
   1747                     int position = firstVisiblePosition + i;
   1748                     long itemId = mAdapter.getItemId(position);
   1749                     mItemIdTopMap.put(itemId, child.getTop());
   1750                 }
   1751             }
   1752 
   1753             @Override
   1754             protected AlarmInstance doInBackground(Void... parameters) {
   1755                 if (context != null && alarm != null) {
   1756                     ContentResolver cr = context.getContentResolver();
   1757 
   1758                     // Add alarm to db
   1759                     Alarm newAlarm = Alarm.addAlarm(cr, alarm);
   1760                     mScrollToAlarmId = newAlarm.id;
   1761 
   1762                     // Create and add instance to db
   1763                     if (newAlarm.enabled) {
   1764                         return setupAlarmInstance(context, newAlarm);
   1765                     }
   1766                 }
   1767                 return null;
   1768             }
   1769 
   1770             @Override
   1771             protected void onPostExecute(AlarmInstance instance) {
   1772                 if (instance != null) {
   1773                     AlarmUtils.popAlarmSetToast(context, instance.getAlarmTime().getTimeInMillis());
   1774                 }
   1775             }
   1776         };
   1777         updateTask.execute();
   1778     }
   1779 
   1780     private void asyncUpdateAlarm(final Alarm alarm, final boolean popToast) {
   1781         final Context context = AlarmClockFragment.this.getActivity().getApplicationContext();
   1782         final AsyncTask<Void, Void, AlarmInstance> updateTask =
   1783                 new AsyncTask<Void, Void, AlarmInstance>() {
   1784             @Override
   1785             protected AlarmInstance doInBackground(Void ... parameters) {
   1786                 ContentResolver cr = context.getContentResolver();
   1787 
   1788                 // Dismiss all old instances
   1789                 AlarmStateManager.deleteAllInstances(context, alarm.id);
   1790 
   1791                 // Update alarm
   1792                 Alarm.updateAlarm(cr, alarm);
   1793                 if (alarm.enabled) {
   1794                     return setupAlarmInstance(context, alarm);
   1795                 }
   1796 
   1797                 return null;
   1798             }
   1799 
   1800             @Override
   1801             protected void onPostExecute(AlarmInstance instance) {
   1802                 if (popToast && instance != null) {
   1803                     AlarmUtils.popAlarmSetToast(context, instance.getAlarmTime().getTimeInMillis());
   1804                 }
   1805             }
   1806         };
   1807         updateTask.execute();
   1808     }
   1809 
   1810     @Override
   1811     public boolean onTouch(View v, MotionEvent event) {
   1812         hideUndoBar(true, event);
   1813         return false;
   1814     }
   1815 }
   1816