1 /*
2  * Copyright (C) 2015 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.alarms;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.os.AsyncTask;
22 import android.support.design.widget.Snackbar;
23 import android.text.format.DateFormat;
24 import android.view.View;
25 import android.view.ViewGroup;
26 
27 import com.android.deskclock.AlarmUtils;
28 import com.android.deskclock.R;
29 import com.android.deskclock.events.Events;
30 import com.android.deskclock.provider.Alarm;
31 import com.android.deskclock.provider.AlarmInstance;
32 import com.android.deskclock.widget.toast.SnackbarManager;
33 
34 import java.util.Calendar;
35 import java.util.List;
36 
37 /**
38  * API for asynchronously mutating a single alarm.
39  */
40 public final class AlarmUpdateHandler {
41 
42     private final Context mAppContext;
43     private final ScrollHandler mScrollHandler;
44     private final View mSnackbarAnchor;
45 
46     // For undo
47     private Alarm mDeletedAlarm;
48 
AlarmUpdateHandler(Context context, ScrollHandler scrollHandler, ViewGroup snackbarAnchor)49     public AlarmUpdateHandler(Context context, ScrollHandler scrollHandler,
50             ViewGroup snackbarAnchor) {
51         mAppContext = context.getApplicationContext();
52         mScrollHandler = scrollHandler;
53         mSnackbarAnchor = snackbarAnchor;
54     }
55 
56     /**
57      * Adds a new alarm on the background.
58      *
59      * @param alarm The alarm to be added.
60      */
asyncAddAlarm(final Alarm alarm)61     public void asyncAddAlarm(final Alarm alarm) {
62         final AsyncTask<Void, Void, AlarmInstance> updateTask =
63                 new AsyncTask<Void, Void, AlarmInstance>() {
64                     @Override
65                     protected AlarmInstance doInBackground(Void... parameters) {
66                         if (alarm != null) {
67                             Events.sendAlarmEvent(R.string.action_create, R.string.label_deskclock);
68                             ContentResolver cr = mAppContext.getContentResolver();
69 
70                             // Add alarm to db
71                             Alarm newAlarm = Alarm.addAlarm(cr, alarm);
72 
73                             // Be ready to scroll to this alarm on UI later.
74                             mScrollHandler.setSmoothScrollStableId(newAlarm.id);
75 
76                             // Create and add instance to db
77                             if (newAlarm.enabled) {
78                                 return setupAlarmInstance(newAlarm);
79                             }
80                         }
81                         return null;
82                     }
83 
84                     @Override
85                     protected void onPostExecute(AlarmInstance instance) {
86                         if (instance != null) {
87                             AlarmUtils.popAlarmSetSnackbar(
88                                     mSnackbarAnchor, instance.getAlarmTime().getTimeInMillis());
89                         }
90                     }
91                 };
92         updateTask.execute();
93     }
94 
95     /**
96      * Modifies an alarm on the background, and optionally show a toast when done.
97      *
98      * @param alarm       The alarm to be modified.
99      * @param popToast    whether or not a toast should be displayed when done.
100      * @param minorUpdate if true, don't affect any currently snoozed instances.
101      */
asyncUpdateAlarm(final Alarm alarm, final boolean popToast, final boolean minorUpdate)102     public void asyncUpdateAlarm(final Alarm alarm, final boolean popToast,
103             final boolean minorUpdate) {
104         final AsyncTask<Void, Void, AlarmInstance> updateTask =
105                 new AsyncTask<Void, Void, AlarmInstance>() {
106                     @Override
107                     protected AlarmInstance doInBackground(Void... parameters) {
108                         Events.sendAlarmEvent(R.string.action_update, R.string.label_deskclock);
109                         ContentResolver cr = mAppContext.getContentResolver();
110 
111                         // Update alarm
112                         Alarm.updateAlarm(cr, alarm);
113 
114                         if (minorUpdate) {
115                             // just update the instance in the database and update notifications.
116                             final List<AlarmInstance> instanceList =
117                                     AlarmInstance.getInstancesByAlarmId(cr, alarm.id);
118                             for (AlarmInstance instance : instanceList) {
119                                 // Make a copy of the existing instance
120                                 final AlarmInstance newInstance = new AlarmInstance(instance);
121                                 // Copy over minor change data to the instance; we don't know
122                                 // exactly which minor field changed, so just copy them all.
123                                 newInstance.mVibrate = alarm.vibrate;
124                                 newInstance.mRingtone = alarm.alert;
125                                 newInstance.mLabel = alarm.label;
126                                 // Since we copied the mId of the old instance and the mId is used
127                                 // as the primary key in the AlarmInstance table, this will replace
128                                 // the existing instance.
129                                 AlarmInstance.updateInstance(cr, newInstance);
130                                 // Update the notification for this instance.
131                                 AlarmNotifications.updateNotification(mAppContext, newInstance);
132                             }
133                             return null;
134                         }
135                         // Otherwise, this is a major update and we're going to re-create the alarm
136                         AlarmStateManager.deleteAllInstances(mAppContext, alarm.id);
137 
138                         return alarm.enabled ? setupAlarmInstance(alarm) : null;
139                     }
140 
141                     @Override
142                     protected void onPostExecute(AlarmInstance instance) {
143                         if (popToast && instance != null) {
144                             AlarmUtils.popAlarmSetSnackbar(
145                                     mSnackbarAnchor, instance.getAlarmTime().getTimeInMillis());
146                         }
147                     }
148                 };
149         updateTask.execute();
150     }
151 
152     /**
153      * Deletes an alarm on the background.
154      *
155      * @param alarm The alarm to be deleted.
156      */
asyncDeleteAlarm(final Alarm alarm)157     public void asyncDeleteAlarm(final Alarm alarm) {
158         final AsyncTask<Void, Void, Boolean> deleteTask = new AsyncTask<Void, Void, Boolean>() {
159             @Override
160             protected Boolean doInBackground(Void... parameters) {
161                 // Activity may be closed at this point , make sure data is still valid
162                 if (alarm == null) {
163                     // Nothing to do here, just return.
164                     return false;
165                 }
166                 Events.sendAlarmEvent(R.string.action_delete, R.string.label_deskclock);
167                 AlarmStateManager.deleteAllInstances(mAppContext, alarm.id);
168                 return Alarm.deleteAlarm(mAppContext.getContentResolver(), alarm.id);
169             }
170 
171             @Override
172             protected void onPostExecute(Boolean deleted) {
173                 if (deleted) {
174                     mDeletedAlarm = alarm;
175                     showUndoBar();
176                 }
177             }
178         };
179         deleteTask.execute();
180     }
181 
182     /**
183      * Show a toast when an alarm is predismissed.
184      *
185      * @param instance Instance being predismissed.
186      */
showPredismissToast(AlarmInstance instance)187     public void showPredismissToast(AlarmInstance instance) {
188         final String time = DateFormat.getTimeFormat(mAppContext).format(
189                 instance.getAlarmTime().getTime());
190         final String text = mAppContext.getString(R.string.alarm_is_dismissed, time);
191         SnackbarManager.show(Snackbar.make(mSnackbarAnchor, text, Snackbar.LENGTH_SHORT));
192     }
193 
194     /**
195      * Hides any undo toast.
196      */
hideUndoBar()197     public void hideUndoBar() {
198         mDeletedAlarm = null;
199         SnackbarManager.dismiss();
200     }
201 
showUndoBar()202     private void showUndoBar() {
203         final Alarm deletedAlarm = mDeletedAlarm;
204         final Snackbar snackbar = Snackbar.make(mSnackbarAnchor,
205                 mAppContext.getString(R.string.alarm_deleted), Snackbar.LENGTH_LONG)
206                 .setAction(R.string.alarm_undo, new View.OnClickListener() {
207                     @Override
208                     public void onClick(View v) {
209                         mDeletedAlarm = null;
210                         asyncAddAlarm(deletedAlarm);
211                     }
212                 });
213         SnackbarManager.show(snackbar);
214     }
215 
setupAlarmInstance(Alarm alarm)216     private AlarmInstance setupAlarmInstance(Alarm alarm) {
217         final ContentResolver cr = mAppContext.getContentResolver();
218         AlarmInstance newInstance = alarm.createInstanceAfter(Calendar.getInstance());
219         newInstance = AlarmInstance.addInstance(cr, newInstance);
220         // Register instance to state manager
221         AlarmStateManager.registerInstance(mAppContext, newInstance, true);
222         return newInstance;
223     }
224 }
225