FileDocCategorySizeDatePackage
DeleteEventHelper.javaAPI DocAndroid 1.5 API14145Wed May 06 22:42:42 BST 2009com.android.calendar

DeleteEventHelper.java

/*
 * 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();
        }
    }
}