1effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang/* 2effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * Copyright (C) 2015 The Android Open Source Project 3effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * 4effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * Licensed under the Apache License, Version 2.0 (the "License"); 5effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * you may not use this file except in compliance with the License. 6effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * You may obtain a copy of the License at 7effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * 8effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * http://www.apache.org/licenses/LICENSE-2.0 9effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * 10effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * Unless required by applicable law or agreed to in writing, software 11effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * distributed under the License is distributed on an "AS IS" BASIS, 12effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * See the License for the specific language governing permissions and 14effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * limitations under the License. 15effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang */ 16effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 17effc01ce01f15cae466235d3a736389c84c7ebefFan Zhangpackage com.android.deskclock.alarms; 18effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 19effc01ce01f15cae466235d3a736389c84c7ebefFan Zhangimport android.content.ContentResolver; 20effc01ce01f15cae466235d3a736389c84c7ebefFan Zhangimport android.content.Context; 21effc01ce01f15cae466235d3a736389c84c7ebefFan Zhangimport android.os.AsyncTask; 227c955e743119c9101b753d151a7a6deba227ddf9Fan Zhangimport android.support.design.widget.Snackbar; 23c8f715a45ad7bb522f48fbd25b500000cb49d1b8Annie Chinimport android.text.format.DateFormat; 24effc01ce01f15cae466235d3a736389c84c7ebefFan Zhangimport android.view.View; 25effc01ce01f15cae466235d3a736389c84c7ebefFan Zhangimport android.view.ViewGroup; 26effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 27effc01ce01f15cae466235d3a736389c84c7ebefFan Zhangimport com.android.deskclock.AlarmUtils; 28effc01ce01f15cae466235d3a736389c84c7ebefFan Zhangimport com.android.deskclock.R; 29effc01ce01f15cae466235d3a736389c84c7ebefFan Zhangimport com.android.deskclock.events.Events; 30effc01ce01f15cae466235d3a736389c84c7ebefFan Zhangimport com.android.deskclock.provider.Alarm; 31effc01ce01f15cae466235d3a736389c84c7ebefFan Zhangimport com.android.deskclock.provider.AlarmInstance; 327c955e743119c9101b753d151a7a6deba227ddf9Fan Zhangimport com.android.deskclock.widget.toast.SnackbarManager; 33effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 34effc01ce01f15cae466235d3a736389c84c7ebefFan Zhangimport java.util.Calendar; 35595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chinimport java.util.List; 36effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 37effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang/** 38effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * API for asynchronously mutating a single alarm. 39effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang */ 407c955e743119c9101b753d151a7a6deba227ddf9Fan Zhangpublic final class AlarmUpdateHandler { 41effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 42effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang private final Context mAppContext; 43effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang private final ScrollHandler mScrollHandler; 447c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang private final View mSnackbarAnchor; 45effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 46effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang // For undo 47effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang private Alarm mDeletedAlarm; 48effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 497c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang public AlarmUpdateHandler(Context context, ScrollHandler scrollHandler, 507c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang ViewGroup snackbarAnchor) { 51effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang mAppContext = context.getApplicationContext(); 52effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang mScrollHandler = scrollHandler; 537c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang mSnackbarAnchor = snackbarAnchor; 54effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 55effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 56effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang /** 57effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * Adds a new alarm on the background. 58effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * 59effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * @param alarm The alarm to be added. 60effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang */ 61effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang public void asyncAddAlarm(final Alarm alarm) { 62effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang final AsyncTask<Void, Void, AlarmInstance> updateTask = 63effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang new AsyncTask<Void, Void, AlarmInstance>() { 64effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang @Override 65effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang protected AlarmInstance doInBackground(Void... parameters) { 66effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang if (alarm != null) { 67effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang Events.sendAlarmEvent(R.string.action_create, R.string.label_deskclock); 68effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang ContentResolver cr = mAppContext.getContentResolver(); 69effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 70effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang // Add alarm to db 71effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang Alarm newAlarm = Alarm.addAlarm(cr, alarm); 72effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 73effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang // Be ready to scroll to this alarm on UI later. 74effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang mScrollHandler.setSmoothScrollStableId(newAlarm.id); 75effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 76effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang // Create and add instance to db 77effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang if (newAlarm.enabled) { 78effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang return setupAlarmInstance(newAlarm); 79effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 80effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 81effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang return null; 82effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 83effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 84effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang @Override 85effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang protected void onPostExecute(AlarmInstance instance) { 86effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang if (instance != null) { 877c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang AlarmUtils.popAlarmSetSnackbar( 887c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang mSnackbarAnchor, instance.getAlarmTime().getTimeInMillis()); 89effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 90effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 91effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang }; 92effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang updateTask.execute(); 93effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 94effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 95effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang /** 96effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * Modifies an alarm on the background, and optionally show a toast when done. 97effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * 98effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * @param alarm The alarm to be modified. 99effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * @param popToast whether or not a toast should be displayed when done. 100effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * @param minorUpdate if true, don't affect any currently snoozed instances. 101effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang */ 102effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang public void asyncUpdateAlarm(final Alarm alarm, final boolean popToast, 103effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang final boolean minorUpdate) { 104effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang final AsyncTask<Void, Void, AlarmInstance> updateTask = 105effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang new AsyncTask<Void, Void, AlarmInstance>() { 106effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang @Override 107effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang protected AlarmInstance doInBackground(Void... parameters) { 108effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang ContentResolver cr = mAppContext.getContentResolver(); 109effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 110effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang // Update alarm 111effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang Alarm.updateAlarm(cr, alarm); 112595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin 113595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin if (minorUpdate) { 114595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin // just update the instance in the database and update notifications. 115595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin final List<AlarmInstance> instanceList = 116595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin AlarmInstance.getInstancesByAlarmId(cr, alarm.id); 117595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin for (AlarmInstance instance : instanceList) { 118595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin // Make a copy of the existing instance 119595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin final AlarmInstance newInstance = new AlarmInstance(instance); 120595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin // Copy over minor change data to the instance; we don't know 121595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin // exactly which minor field changed, so just copy them all. 122595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin newInstance.mVibrate = alarm.vibrate; 123595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin newInstance.mRingtone = alarm.alert; 124595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin newInstance.mLabel = alarm.label; 125595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin // Since we copied the mId of the old instance and the mId is used 126595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin // as the primary key in the AlarmInstance table, this will replace 127595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin // the existing instance. 128595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin AlarmInstance.updateInstance(cr, newInstance); 129595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin // Update the notification for this instance. 130595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin AlarmNotifications.updateNotification(mAppContext, newInstance); 131595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin } 132595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin return null; 133effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 134595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin // Otherwise, this is a major update and we're going to re-create the alarm 135595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin AlarmStateManager.deleteAllInstances(mAppContext, alarm.id); 136effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 137595e6b8c56a6810d0d3bd09669ae314fe0d5a902Annie Chin return alarm.enabled ? setupAlarmInstance(alarm) : null; 138effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 139effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 140effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang @Override 141effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang protected void onPostExecute(AlarmInstance instance) { 142effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang if (popToast && instance != null) { 1437c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang AlarmUtils.popAlarmSetSnackbar( 1447c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang mSnackbarAnchor, instance.getAlarmTime().getTimeInMillis()); 145effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 146effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 147effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang }; 148effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang updateTask.execute(); 149effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 150effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 151effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang /** 152effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * Deletes an alarm on the background. 153effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * 154effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * @param alarm The alarm to be deleted. 155effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang */ 156effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang public void asyncDeleteAlarm(final Alarm alarm) { 157effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang final AsyncTask<Void, Void, Boolean> deleteTask = new AsyncTask<Void, Void, Boolean>() { 158effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang @Override 159effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang protected Boolean doInBackground(Void... parameters) { 160effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang // Activity may be closed at this point , make sure data is still valid 161effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang if (alarm == null) { 162effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang // Nothing to do here, just return. 163effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang return false; 164effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 165effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang AlarmStateManager.deleteAllInstances(mAppContext, alarm.id); 166effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang return Alarm.deleteAlarm(mAppContext.getContentResolver(), alarm.id); 167effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 168effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 169effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang @Override 170effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang protected void onPostExecute(Boolean deleted) { 171effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang if (deleted) { 172effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang mDeletedAlarm = alarm; 173effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang showUndoBar(); 174effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 175effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 176effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang }; 177effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang deleteTask.execute(); 178effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 179effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 180effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang /** 181c8f715a45ad7bb522f48fbd25b500000cb49d1b8Annie Chin * Show a toast when an alarm is predismissed. 182c8f715a45ad7bb522f48fbd25b500000cb49d1b8Annie Chin * 183c8f715a45ad7bb522f48fbd25b500000cb49d1b8Annie Chin * @param instance Instance being predismissed. 184c8f715a45ad7bb522f48fbd25b500000cb49d1b8Annie Chin */ 185c8f715a45ad7bb522f48fbd25b500000cb49d1b8Annie Chin public void showPredismissToast(AlarmInstance instance) { 186c8f715a45ad7bb522f48fbd25b500000cb49d1b8Annie Chin final String time = DateFormat.getTimeFormat(mAppContext).format( 187c8f715a45ad7bb522f48fbd25b500000cb49d1b8Annie Chin instance.getAlarmTime().getTime()); 188c8f715a45ad7bb522f48fbd25b500000cb49d1b8Annie Chin final String text = mAppContext.getString(R.string.alarm_is_dismissed, time); 1897c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang SnackbarManager.show(Snackbar.make(mSnackbarAnchor, text, Snackbar.LENGTH_SHORT)); 190c8f715a45ad7bb522f48fbd25b500000cb49d1b8Annie Chin } 191c8f715a45ad7bb522f48fbd25b500000cb49d1b8Annie Chin 192c8f715a45ad7bb522f48fbd25b500000cb49d1b8Annie Chin /** 193effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang * Hides any undo toast. 194effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang */ 1957c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang public void hideUndoBar() { 196effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang mDeletedAlarm = null; 1977c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang SnackbarManager.dismiss(); 198effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 199effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 200effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang private void showUndoBar() { 201effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang final Alarm deletedAlarm = mDeletedAlarm; 2027c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang final Snackbar snackbar = Snackbar.make(mSnackbarAnchor, 2037c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang mAppContext.getString(R.string.alarm_deleted), Snackbar.LENGTH_LONG) 2047c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang .setAction(R.string.alarm_undo, new View.OnClickListener() { 2057c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang @Override 2067c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang public void onClick(View v) { 2077c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang mDeletedAlarm = null; 2087c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang asyncAddAlarm(deletedAlarm); 2097c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang } 2107c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang }); 2117c955e743119c9101b753d151a7a6deba227ddf9Fan Zhang SnackbarManager.show(snackbar); 212effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 213effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang 214effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang private AlarmInstance setupAlarmInstance(Alarm alarm) { 215effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang final ContentResolver cr = mAppContext.getContentResolver(); 216effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang AlarmInstance newInstance = alarm.createInstanceAfter(Calendar.getInstance()); 217effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang newInstance = AlarmInstance.addInstance(cr, newInstance); 218effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang // Register instance to state manager 219effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang AlarmStateManager.registerInstance(mAppContext, newInstance, true); 220effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang return newInstance; 221effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang } 222effc01ce01f15cae466235d3a736389c84c7ebefFan Zhang} 223