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
17package com.android.deskclock.alarms;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.os.AsyncTask;
22import android.support.design.widget.Snackbar;
23import android.text.format.DateFormat;
24import android.view.View;
25import android.view.ViewGroup;
26
27import com.android.deskclock.AlarmUtils;
28import com.android.deskclock.R;
29import com.android.deskclock.events.Events;
30import com.android.deskclock.provider.Alarm;
31import com.android.deskclock.provider.AlarmInstance;
32import com.android.deskclock.widget.toast.SnackbarManager;
33
34import java.util.Calendar;
35import java.util.List;
36
37/**
38 * API for asynchronously mutating a single alarm.
39 */
40public 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
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     */
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     */
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     */
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     */
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     */
195    public void hideUndoBar() {
196        mDeletedAlarm = null;
197        SnackbarManager.dismiss();
198    }
199
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
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