Initial Contribution
[android/platform/packages/apps/Calendar.git] / src / com / android / calendar / DeleteEventHelper.java
1 /*
2  * Copyright (C) 2008 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.calendar;
18
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.content.ContentResolver;
22 import android.content.ContentUris;
23 import android.content.ContentValues;
24 import android.content.DialogInterface;
25 import android.database.Cursor;
26 import android.net.Uri;
27 import android.pim.EventRecurrence;
28 import android.pim.Time;
29 import android.provider.Calendar;
30 import android.provider.Calendar.Events;
31
32 /**
33  * A helper class for deleting events.  If a normal event is selected for
34  * deletion, then this pops up a confirmation dialog.  If the user confirms,
35  * then the normal event is deleted.
36  * 
37  * <p>
38  * If a repeating event is selected for deletion, then this pops up dialog
39  * asking if the user wants to delete just this one instance, or all the
40  * events in the series, or this event plus all following events.  The user
41  * may also cancel the delete.
42  * </p>
43  * 
44  * <p>
45  * To use this class, create an instance, passing in the parent activity
46  * and a boolean that determines if the parent activity should exit if the
47  * event is deleted.  Then to use the instance, call one of the
48  * {@link delete()} methods on this class.
49  * 
50  * An instance of this class may be created once and reused (by calling
51  * {@link #delete()} multiple times).
52  */
53 public class DeleteEventHelper {
54     
55     private final Activity mParent;
56     private final ContentResolver mContentResolver;
57     
58     private long mStartMillis;
59     private long mEndMillis;
60     private Cursor mCursor;
61     
62     /**
63      * If true, then call finish() on the parent activity when done.
64      */
65     private boolean mExitWhenDone;
66     
67     /**
68      * These are the corresponding indices into the array of strings
69      * "R.array.delete_repeating_labels" in the resource file.
70      */
71     static final int DELETE_SELECTED = 0;
72     static final int DELETE_ALL_FOLLOWING = 1;
73     static final int DELETE_ALL = 2;
74     
75     private int mWhichDelete;
76
77     private static final String[] EVENT_PROJECTION = new String[] {
78         Events._ID,
79         Events.TITLE,
80         Events.ALL_DAY,
81         Events.CALENDAR_ID,
82         Events.RRULE,
83         Events.DTSTART,
84         Events._SYNC_ID,
85         Events.EVENT_TIMEZONE,
86     };
87     
88     private int mEventIndexId;
89     private int mEventIndexRrule;
90     
91     public DeleteEventHelper(Activity parent, boolean exitWhenDone) {
92         mParent = parent;
93         mContentResolver = mParent.getContentResolver();
94         mExitWhenDone = exitWhenDone;
95     }
96     
97     public void setExitWhenDone(boolean exitWhenDone) {
98         mExitWhenDone = exitWhenDone;
99     }
100
101     /**
102      * This callback is used when a normal event is deleted.
103      */
104     private DialogInterface.OnClickListener mDeleteNormalDialogListener =
105             new DialogInterface.OnClickListener() {
106         public void onClick(DialogInterface dialog, int button) {
107             long id = mCursor.getInt(mEventIndexId);
108             Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
109             mContentResolver.delete(uri, null /* where */, null /* selectionArgs */);
110             if (mExitWhenDone) {
111                 mParent.finish();
112             }
113         }
114     };
115
116     /**
117      * This callback is used when a list item for a repeating event is selected
118      */
119     private DialogInterface.OnClickListener mDeleteListListener =
120             new DialogInterface.OnClickListener() {
121         public void onClick(DialogInterface dialog, int button) {
122             mWhichDelete = button;
123         }
124     };
125
126     /**
127      * This callback is used when a repeating event is deleted.
128      */
129     private DialogInterface.OnClickListener mDeleteRepeatingDialogListener =
130             new DialogInterface.OnClickListener() {
131         public void onClick(DialogInterface dialog, int button) {
132             if (mWhichDelete != -1) {
133                 deleteRepeatingEvent(mWhichDelete);
134             }
135         }
136     };
137     
138     /**
139      * Does the required processing for deleting an event, which includes
140      * first popping up a dialog asking for confirmation (if the event is
141      * a normal event) or a dialog asking which events to delete (if the
142      * event is a repeating event).  The "which" parameter is used to check
143      * the initial selection and is only used for repeating events.  Set
144      * "which" to -1 to have nothing selected initially.
145      * 
146      * @param begin the begin time of the event, in UTC milliseconds
147      * @param end the end time of the event, in UTC milliseconds
148      * @param eventId the event id
149      * @param which one of the values {@link DELETE_SELECTED},
150      *  {@link DELETE_ALL_FOLLOWING}, {@link DELETE_ALL}, or -1
151      */
152     public void delete(long begin, long end, long eventId, int which) {
153         Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, eventId);
154         Cursor cursor = mParent.managedQuery(uri, EVENT_PROJECTION, null, null);
155         if (cursor == null) {
156             return;
157         }
158         cursor.moveToFirst();
159         delete(begin, end, cursor, which);
160     }
161     
162     /**
163      * Does the required processing for deleting an event.  This method
164      * takes a {@link Cursor} object as a parameter, which must point to
165      * a row in the Events table containing the required database fields.
166      * The required fields for a normal event are:
167      * 
168      * <ul>
169      *   <li> Events._ID </li>
170      *   <li> Events.TITLE </li>
171      *   <li> Events.RRULE </li>
172      * </ul>
173      * 
174      * The required fields for a repeating event include the above plus the
175      * following fields:
176      * 
177      * <ul>
178      *   <li> Events.ALL_DAY </li>
179      *   <li> Events.CALENDAR_ID </li>
180      *   <li> Events.DTSTART </li>
181      *   <li> Events._SYNC_ID </li>
182      *   <li> Events.EVENT_TIMEZONE </li>
183      * </ul>
184      * 
185      * @param begin the begin time of the event, in UTC milliseconds
186      * @param end the end time of the event, in UTC milliseconds
187      * @param cursor the database cursor containing the required fields
188      * @param which one of the values {@link DELETE_SELECTED},
189      *  {@link DELETE_ALL_FOLLOWING}, {@link DELETE_ALL}, or -1
190      */
191     public void delete(long begin, long end, Cursor cursor, int which) {
192         mWhichDelete = which;
193         mStartMillis = begin;
194         mEndMillis = end;
195         mCursor = cursor;
196         mEventIndexId = mCursor.getColumnIndexOrThrow(Events._ID);
197         mEventIndexRrule = mCursor.getColumnIndexOrThrow(Events.RRULE);
198         
199         // If this is a repeating event, then pop up a dialog asking the
200         // user if they want to delete all of the repeating events or
201         // just some of them.
202         String rRule = mCursor.getString(mEventIndexRrule);
203         if (rRule == null) {
204             // This is a normal event. Pop up a confirmation dialog.
205             new AlertDialog.Builder(mParent)
206             .setTitle(R.string.delete_title)
207             .setMessage(R.string.delete_this_event_title)
208             .setIcon(android.R.drawable.ic_dialog_alert)
209             .setPositiveButton(R.string.ok_label, mDeleteNormalDialogListener)
210             .setNegativeButton(R.string.cancel_label, null)
211             .show();
212         } else {
213             // This is a repeating event.  Pop up a dialog asking which events
214             // to delete.
215             new AlertDialog.Builder(mParent)
216             .setTitle(R.string.delete_title)
217             .setIcon(android.R.drawable.ic_dialog_alert)
218             .setSingleChoiceItems(R.array.delete_repeating_labels, which, mDeleteListListener)
219             .setPositiveButton(R.string.ok_label, mDeleteRepeatingDialogListener)
220             .setNegativeButton(R.string.cancel_label, null)
221             .show();
222         }
223     }
224     
225     private void deleteRepeatingEvent(int which) {
226         int indexDtstart = mCursor.getColumnIndexOrThrow(Events.DTSTART);
227         int indexSyncId = mCursor.getColumnIndexOrThrow(Events._SYNC_ID);
228         int indexAllDay = mCursor.getColumnIndexOrThrow(Events.ALL_DAY);
229         int indexTitle = mCursor.getColumnIndexOrThrow(Events.TITLE);
230         int indexTimezone = mCursor.getColumnIndexOrThrow(Events.EVENT_TIMEZONE);
231         int indexCalendarId = mCursor.getColumnIndexOrThrow(Events.CALENDAR_ID);
232
233         String rRule = mCursor.getString(mEventIndexRrule);
234         boolean allDay = mCursor.getInt(indexAllDay) != 0;
235         long dtstart = mCursor.getLong(indexDtstart);
236         long id = mCursor.getInt(mEventIndexId);
237
238         switch (which) {
239             case DELETE_SELECTED:
240             {
241                 // Create a recurrence exception by creating a new event
242                 // with the status "cancelled".
243                 ContentValues values = new ContentValues();
244                 
245                 // The title might not be necessary, but it makes it easier
246                 // to find this entry in the database when there is a problem.
247                 String title = mCursor.getString(indexTitle);
248                 values.put(Events.TITLE, title);
249                 
250                 String syncId = mCursor.getString(indexSyncId);
251                 String timezone = mCursor.getString(indexTimezone);
252                 int calendarId = mCursor.getInt(indexCalendarId);
253                 values.put(Events.EVENT_TIMEZONE, timezone);
254                 values.put(Events.CALENDAR_ID, calendarId);
255                 values.put(Events.DTSTART, mStartMillis);
256                 values.put(Events.DTEND, mEndMillis);
257                 values.put(Events.ORIGINAL_EVENT, syncId);
258                 values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
259                 values.put(Events.STATUS, Events.STATUS_CANCELED);
260                 
261                 mContentResolver.insert(Events.CONTENT_URI, values);
262                 break;
263             }
264             case DELETE_ALL: {
265                 Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
266                 mContentResolver.delete(uri, null /* where */, null /* selectionArgs */);
267                 break;
268             }
269             case DELETE_ALL_FOLLOWING: {
270                 // If we are deleting the first event in the series and all
271                 // following events, then delete them all.
272                 if (dtstart == mStartMillis) {
273                     Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
274                     mContentResolver.delete(uri, null /* where */, null /* selectionArgs */);
275                     break;
276                 }
277                 
278                 // Modify the repeating event to end just before this event time
279                 EventRecurrence eventRecurrence = new EventRecurrence();
280                 eventRecurrence.parse(rRule);
281                 Time date = new Time();
282                 if (allDay) {
283                     date.timezone = Time.TIMEZONE_UTC;
284                 }
285                 date.set(mStartMillis);
286                 date.second--;
287                 date.normalize(false);
288                 
289                 // Google calendar seems to require the UNTIL string to be
290                 // in UTC.
291                 date.switchTimezone(Time.TIMEZONE_UTC);
292                 eventRecurrence.until = date.format2445();
293                 
294                 ContentValues values = new ContentValues();
295                 values.put(Events.DTSTART, dtstart);
296                 values.put(Events.RRULE, eventRecurrence.toString());
297                 Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
298                 mContentResolver.update(uri, values, null, null);
299                 break;
300             }
301         }
302         if (mExitWhenDone) {
303             mParent.finish();
304         }
305     }
306 }