blob: d0693fa9bfa3cd7b756b8ed87a28f2ca1c5c6364 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.calendar;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.database.Cursor;
import android.net.Uri;
import android.pim.EventRecurrence;
import android.provider.Calendar;
import android.provider.Calendar.Events;
import android.text.format.Time;
import android.widget.Button;
/**
* A helper class for deleting events. If a normal event is selected for
* deletion, then this pops up a confirmation dialog. If the user confirms,
* then the normal event is deleted.
*
* <p>
* If a repeating event is selected for deletion, then this pops up dialog
* asking if the user wants to delete just this one instance, or all the
* events in the series, or this event plus all following events. The user
* may also cancel the delete.
* </p>
*
* <p>
* To use this class, create an instance, passing in the parent activity
* and a boolean that determines if the parent activity should exit if the
* event is deleted. Then to use the instance, call one of the
* {@link delete()} methods on this class.
*
* An instance of this class may be created once and reused (by calling
* {@link #delete()} multiple times).
*/
public class DeleteEventHelper {
private static final String TAG = "DeleteEventHelper";
private final Activity mParent;
private final ContentResolver mContentResolver;
private long mStartMillis;
private long mEndMillis;
private Cursor mCursor;
/**
* If true, then call finish() on the parent activity when done.
*/
private boolean mExitWhenDone;
/**
* These are the corresponding indices into the array of strings
* "R.array.delete_repeating_labels" in the resource file.
*/
static final int DELETE_SELECTED = 0;
static final int DELETE_ALL_FOLLOWING = 1;
static final int DELETE_ALL = 2;
private int mWhichDelete;
private AlertDialog mAlertDialog;
private static final String[] EVENT_PROJECTION = new String[] {
Events._ID,
Events.TITLE,
Events.ALL_DAY,
Events.CALENDAR_ID,
Events.RRULE,
Events.DTSTART,
Events._SYNC_ID,
Events.EVENT_TIMEZONE,
};
private int mEventIndexId;
private int mEventIndexRrule;
private String mSyncId;
public DeleteEventHelper(Activity parent, boolean exitWhenDone) {
mParent = parent;
mContentResolver = mParent.getContentResolver();
mExitWhenDone = exitWhenDone;
}
public void setExitWhenDone(boolean exitWhenDone) {
mExitWhenDone = exitWhenDone;
}
/**
* This callback is used when a normal event is deleted.
*/
private DialogInterface.OnClickListener mDeleteNormalDialogListener =
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int button) {
long id = mCursor.getInt(mEventIndexId);
Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
mContentResolver.delete(uri, null /* where */, null /* selectionArgs */);
if (mExitWhenDone) {
mParent.finish();
}
}
};
/**
* This callback is used when a list item for a repeating event is selected
*/
private DialogInterface.OnClickListener mDeleteListListener =
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int button) {
mWhichDelete = button;
// Enable the "ok" button now that the user has selected which
// events in the series to delete.
Button ok = mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
ok.setEnabled(true);
}
};
/**
* This callback is used when a repeating event is deleted.
*/
private DialogInterface.OnClickListener mDeleteRepeatingDialogListener =
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int button) {
if (mWhichDelete != -1) {
deleteRepeatingEvent(mWhichDelete);
}
}
};
/**
* Does the required processing for deleting an event, which includes
* first popping up a dialog asking for confirmation (if the event is
* a normal event) or a dialog asking which events to delete (if the
* event is a repeating event). The "which" parameter is used to check
* the initial selection and is only used for repeating events. Set
* "which" to -1 to have nothing selected initially.
*
* @param begin the begin time of the event, in UTC milliseconds
* @param end the end time of the event, in UTC milliseconds
* @param eventId the event id
* @param which one of the values {@link DELETE_SELECTED},
* {@link DELETE_ALL_FOLLOWING}, {@link DELETE_ALL}, or -1
*/
public void delete(long begin, long end, long eventId, int which) {
Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, eventId);
Cursor cursor = mParent.managedQuery(uri, EVENT_PROJECTION, null, null);
if (cursor == null) {
return;
}
cursor.moveToFirst();
delete(begin, end, cursor, which);
}
/**
* Does the required processing for deleting an event. This method
* takes a {@link Cursor} object as a parameter, which must point to
* a row in the Events table containing the required database fields.
* The required fields for a normal event are:
*
* <ul>
* <li> Events._ID </li>
* <li> Events.TITLE </li>
* <li> Events.RRULE </li>
* </ul>
*
* The required fields for a repeating event include the above plus the
* following fields:
*
* <ul>
* <li> Events.ALL_DAY </li>
* <li> Events.CALENDAR_ID </li>
* <li> Events.DTSTART </li>
* <li> Events._SYNC_ID </li>
* <li> Events.EVENT_TIMEZONE </li>
* </ul>
*
* @param begin the begin time of the event, in UTC milliseconds
* @param end the end time of the event, in UTC milliseconds
* @param cursor the database cursor containing the required fields
* @param which one of the values {@link DELETE_SELECTED},
* {@link DELETE_ALL_FOLLOWING}, {@link DELETE_ALL}, or -1
*/
public void delete(long begin, long end, Cursor cursor, int which) {
mWhichDelete = which;
mStartMillis = begin;
mEndMillis = end;
mCursor = cursor;
mEventIndexId = mCursor.getColumnIndexOrThrow(Events._ID);
mEventIndexRrule = mCursor.getColumnIndexOrThrow(Events.RRULE);
int eventIndexSyncId = mCursor.getColumnIndexOrThrow(Events._SYNC_ID);
mSyncId = mCursor.getString(eventIndexSyncId);
// If this is a repeating event, then pop up a dialog asking the
// user if they want to delete all of the repeating events or
// just some of them.
String rRule = mCursor.getString(mEventIndexRrule);
if (rRule == null) {
// This is a normal event. Pop up a confirmation dialog.
new AlertDialog.Builder(mParent)
.setTitle(R.string.delete_title)
.setMessage(R.string.delete_this_event_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(android.R.string.ok, mDeleteNormalDialogListener)
.setNegativeButton(android.R.string.cancel, null)
.show();
} else {
// This is a repeating event. Pop up a dialog asking which events
// to delete.
int labelsArrayId = R.array.delete_repeating_labels;
if (mSyncId == null) {
labelsArrayId = R.array.delete_repeating_labels_no_selected;
}
AlertDialog dialog = new AlertDialog.Builder(mParent)
.setTitle(R.string.delete_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setSingleChoiceItems(labelsArrayId, which, mDeleteListListener)
.setPositiveButton(android.R.string.ok, mDeleteRepeatingDialogListener)
.setNegativeButton(android.R.string.cancel, null)
.show();
mAlertDialog = dialog;
if (which == -1) {
// Disable the "Ok" button until the user selects which events
// to delete.
Button ok = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
ok.setEnabled(false);
}
}
}
private void deleteRepeatingEvent(int which) {
int indexDtstart = mCursor.getColumnIndexOrThrow(Events.DTSTART);
int indexAllDay = mCursor.getColumnIndexOrThrow(Events.ALL_DAY);
int indexTitle = mCursor.getColumnIndexOrThrow(Events.TITLE);
int indexTimezone = mCursor.getColumnIndexOrThrow(Events.EVENT_TIMEZONE);
int indexCalendarId = mCursor.getColumnIndexOrThrow(Events.CALENDAR_ID);
String rRule = mCursor.getString(mEventIndexRrule);
boolean allDay = mCursor.getInt(indexAllDay) != 0;
long dtstart = mCursor.getLong(indexDtstart);
long id = mCursor.getInt(mEventIndexId);
// If the repeating event has not been given a sync id from the server
// yet, then we can't delete a single instance of this event. (This is
// a deficiency in the CalendarProvider and sync code.) We checked for
// that when creating the list of items in the dialog and we removed
// the first element ("DELETE_SELECTED") from the dialog in that case.
// The "which" value is a 0-based index into the list of items, where
// the "DELETE_SELECTED" item is at index 0.
if (mSyncId == null) {
which += 1;
}
switch (which) {
case DELETE_SELECTED:
{
// If we are deleting the first event in the series, then
// instead of creating a recurrence exception, just change
// the start time of the recurrence.
if (dtstart == mStartMillis) {
// TODO
}
// Create a recurrence exception by creating a new event
// with the status "cancelled".
ContentValues values = new ContentValues();
// The title might not be necessary, but it makes it easier
// to find this entry in the database when there is a problem.
String title = mCursor.getString(indexTitle);
values.put(Events.TITLE, title);
String timezone = mCursor.getString(indexTimezone);
int calendarId = mCursor.getInt(indexCalendarId);
values.put(Events.EVENT_TIMEZONE, timezone);
values.put(Events.ALL_DAY, allDay ? 1 : 0);
values.put(Events.CALENDAR_ID, calendarId);
values.put(Events.DTSTART, mStartMillis);
values.put(Events.DTEND, mEndMillis);
values.put(Events.ORIGINAL_EVENT, mSyncId);
values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
values.put(Events.STATUS, Events.STATUS_CANCELED);
mContentResolver.insert(Events.CONTENT_URI, values);
break;
}
case DELETE_ALL: {
Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
mContentResolver.delete(uri, null /* where */, null /* selectionArgs */);
break;
}
case DELETE_ALL_FOLLOWING: {
// If we are deleting the first event in the series and all
// following events, then delete them all.
if (dtstart == mStartMillis) {
Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
mContentResolver.delete(uri, null /* where */, null /* selectionArgs */);
break;
}
// Modify the repeating event to end just before this event time
EventRecurrence eventRecurrence = new EventRecurrence();
eventRecurrence.parse(rRule);
Time date = new Time();
if (allDay) {
date.timezone = Time.TIMEZONE_UTC;
}
date.set(mStartMillis);
date.second--;
date.normalize(false);
// Google calendar seems to require the UNTIL string to be
// in UTC.
date.switchTimezone(Time.TIMEZONE_UTC);
eventRecurrence.until = date.format2445();
ContentValues values = new ContentValues();
values.put(Events.DTSTART, dtstart);
values.put(Events.RRULE, eventRecurrence.toString());
Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
mContentResolver.update(uri, values, null, null);
break;
}
}
if (mExitWhenDone) {
mParent.finish();
}
}
}