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 com.google.android.material.snackbar.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                         ContentResolver cr = mAppContext.getContentResolver();
109 
110                         // Update alarm
111                         Alarm.updateAlarm(cr, alarm);
112 
113                         if (minorUpdate) {
114                             // just update the instance in the database and update notifications.
115                             final List<AlarmInstance> instanceList =
116                                     AlarmInstance.getInstancesByAlarmId(cr, alarm.id);
117                             for (AlarmInstance instance : instanceList) {
118                                 // Make a copy of the existing instance
119                                 final AlarmInstance newInstance = new AlarmInstance(instance);
120                                 // Copy over minor change data to the instance; we don't know
121                                 // exactly which minor field changed, so just copy them all.
122                                 newInstance.mVibrate = alarm.vibrate;
123                                 newInstance.mRingtone = alarm.alert;
124                                 newInstance.mLabel = alarm.label;
125                                 // Since we copied the mId of the old instance and the mId is used
126                                 // as the primary key in the AlarmInstance table, this will replace
127                                 // the existing instance.
128                                 AlarmInstance.updateInstance(cr, newInstance);
129                                 // Update the notification for this instance.
130                                 AlarmNotifications.updateNotification(mAppContext, newInstance);
131                             }
132                             return null;
133                         }
134                         // Otherwise, this is a major update and we're going to re-create the alarm
135                         AlarmStateManager.deleteAllInstances(mAppContext, alarm.id);
136 
137                         return alarm.enabled ? setupAlarmInstance(alarm) : null;
138                     }
139 
140                     @Override
141                     protected void onPostExecute(AlarmInstance instance) {
142                         if (popToast && instance != null) {
143                             AlarmUtils.popAlarmSetSnackbar(
144                                     mSnackbarAnchor, instance.getAlarmTime().getTimeInMillis());
145                         }
146                     }
147                 };
148         updateTask.execute();
149     }
150 
151     /**
152      * Deletes an alarm on the background.
153      *
154      * @param alarm The alarm to be deleted.
155      */
asyncDeleteAlarm(final Alarm alarm)156     public void asyncDeleteAlarm(final Alarm alarm) {
157         final AsyncTask<Void, Void, Boolean> deleteTask = new AsyncTask<Void, Void, Boolean>() {
158             @Override
159             protected Boolean doInBackground(Void... parameters) {
160                 // Activity may be closed at this point , make sure data is still valid
161                 if (alarm == null) {
162                     // Nothing to do here, just return.
163                     return false;
164                 }
165                 AlarmStateManager.deleteAllInstances(mAppContext, alarm.id);
166                 return Alarm.deleteAlarm(mAppContext.getContentResolver(), alarm.id);
167             }
168 
169             @Override
170             protected void onPostExecute(Boolean deleted) {
171                 if (deleted) {
172                     mDeletedAlarm = alarm;
173                     showUndoBar();
174                 }
175             }
176         };
177         deleteTask.execute();
178     }
179 
180     /**
181      * Show a toast when an alarm is predismissed.
182      *
183      * @param instance Instance being predismissed.
184      */
showPredismissToast(AlarmInstance instance)185     public void showPredismissToast(AlarmInstance instance) {
186         final String time = DateFormat.getTimeFormat(mAppContext).format(
187                 instance.getAlarmTime().getTime());
188         final String text = mAppContext.getString(R.string.alarm_is_dismissed, time);
189         SnackbarManager.show(Snackbar.make(mSnackbarAnchor, text, Snackbar.LENGTH_SHORT));
190     }
191 
192     /**
193      * Hides any undo toast.
194      */
hideUndoBar()195     public void hideUndoBar() {
196         mDeletedAlarm = null;
197         SnackbarManager.dismiss();
198     }
199 
showUndoBar()200     private void showUndoBar() {
201         final Alarm deletedAlarm = mDeletedAlarm;
202         final Snackbar snackbar = Snackbar.make(mSnackbarAnchor,
203                 mAppContext.getString(R.string.alarm_deleted), Snackbar.LENGTH_LONG)
204                 .setAction(R.string.alarm_undo, new View.OnClickListener() {
205                     @Override
206                     public void onClick(View v) {
207                         mDeletedAlarm = null;
208                         asyncAddAlarm(deletedAlarm);
209                     }
210                 });
211         SnackbarManager.show(snackbar);
212     }
213 
setupAlarmInstance(Alarm alarm)214     private AlarmInstance setupAlarmInstance(Alarm alarm) {
215         final ContentResolver cr = mAppContext.getContentResolver();
216         AlarmInstance newInstance = alarm.createInstanceAfter(Calendar.getInstance());
217         newInstance = AlarmInstance.addInstance(cr, newInstance);
218         // Register instance to state manager
219         AlarmStateManager.registerInstance(mAppContext, newInstance, true);
220         return newInstance;
221     }
222 }
223