Home | History | Annotate | Download | only in calendar
      1 /*
      2  * Copyright (C) 2010 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 static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY;
     20 import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
     21 import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
     22 import static com.android.calendar.CalendarController.EVENT_EDIT_ON_LAUNCH;
     23 
     24 import android.animation.Animator;
     25 import android.animation.AnimatorListenerAdapter;
     26 import android.animation.ObjectAnimator;
     27 import android.app.Activity;
     28 import android.app.Dialog;
     29 import android.app.DialogFragment;
     30 import android.app.FragmentManager;
     31 import android.app.Service;
     32 import android.content.ActivityNotFoundException;
     33 import android.content.ContentProviderOperation;
     34 import android.content.ContentResolver;
     35 import android.content.ContentUris;
     36 import android.content.ContentValues;
     37 import android.content.Context;
     38 import android.content.DialogInterface;
     39 import android.content.Intent;
     40 import android.content.SharedPreferences;
     41 import android.content.pm.ApplicationInfo;
     42 import android.content.pm.PackageManager;
     43 import android.content.pm.PackageManager.NameNotFoundException;
     44 import android.content.res.Resources;
     45 import android.database.Cursor;
     46 import android.graphics.Color;
     47 import android.graphics.Rect;
     48 import android.graphics.drawable.Drawable;
     49 import android.net.Uri;
     50 import android.os.Bundle;
     51 import android.provider.CalendarContract;
     52 import android.provider.CalendarContract.Attendees;
     53 import android.provider.CalendarContract.Calendars;
     54 import android.provider.CalendarContract.Colors;
     55 import android.provider.CalendarContract.Events;
     56 import android.provider.CalendarContract.Reminders;
     57 import android.provider.ContactsContract;
     58 import android.provider.ContactsContract.CommonDataKinds;
     59 import android.provider.ContactsContract.Intents;
     60 import android.provider.ContactsContract.QuickContact;
     61 import android.text.Spannable;
     62 import android.text.SpannableStringBuilder;
     63 import android.text.TextUtils;
     64 import android.text.format.Time;
     65 import android.text.method.LinkMovementMethod;
     66 import android.text.method.MovementMethod;
     67 import android.text.style.ForegroundColorSpan;
     68 import android.text.util.Rfc822Token;
     69 import android.util.Log;
     70 import android.util.SparseIntArray;
     71 import android.view.Gravity;
     72 import android.view.LayoutInflater;
     73 import android.view.Menu;
     74 import android.view.MenuInflater;
     75 import android.view.MenuItem;
     76 import android.view.MotionEvent;
     77 import android.view.View;
     78 import android.view.View.OnClickListener;
     79 import android.view.View.OnTouchListener;
     80 import android.view.ViewGroup;
     81 import android.view.Window;
     82 import android.view.WindowManager;
     83 import android.view.accessibility.AccessibilityEvent;
     84 import android.view.accessibility.AccessibilityManager;
     85 import android.widget.AdapterView;
     86 import android.widget.AdapterView.OnItemSelectedListener;
     87 import android.widget.Button;
     88 import android.widget.LinearLayout;
     89 import android.widget.RadioButton;
     90 import android.widget.RadioGroup;
     91 import android.widget.RadioGroup.OnCheckedChangeListener;
     92 import android.widget.ScrollView;
     93 import android.widget.TextView;
     94 import android.widget.Toast;
     95 
     96 import com.android.calendar.CalendarController.EventInfo;
     97 import com.android.calendar.CalendarController.EventType;
     98 import com.android.calendar.CalendarEventModel.Attendee;
     99 import com.android.calendar.CalendarEventModel.ReminderEntry;
    100 import com.android.calendar.alerts.QuickResponseActivity;
    101 import com.android.calendar.event.AttendeesView;
    102 import com.android.calendar.event.EditEventActivity;
    103 import com.android.calendar.event.EditEventHelper;
    104 import com.android.calendar.event.EventColorPickerDialog;
    105 import com.android.calendar.event.EventViewUtils;
    106 import com.android.calendarcommon2.DateException;
    107 import com.android.calendarcommon2.Duration;
    108 import com.android.calendarcommon2.EventRecurrence;
    109 import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
    110 import com.android.colorpicker.HsvColorComparator;
    111 
    112 import java.util.ArrayList;
    113 import java.util.Arrays;
    114 import java.util.Collections;
    115 import java.util.List;
    116 
    117 public class EventInfoFragment extends DialogFragment implements OnCheckedChangeListener,
    118         CalendarController.EventHandler, OnClickListener, DeleteEventHelper.DeleteNotifyListener,
    119         OnColorSelectedListener {
    120 
    121     public static final boolean DEBUG = false;
    122 
    123     public static final String TAG = "EventInfoFragment";
    124     public static final String COLOR_PICKER_DIALOG_TAG = "EventColorPickerDialog";
    125 
    126     private static final int REQUEST_CODE_COLOR_PICKER = 0;
    127 
    128     protected static final String BUNDLE_KEY_EVENT_ID = "key_event_id";
    129     protected static final String BUNDLE_KEY_START_MILLIS = "key_start_millis";
    130     protected static final String BUNDLE_KEY_END_MILLIS = "key_end_millis";
    131     protected static final String BUNDLE_KEY_IS_DIALOG = "key_fragment_is_dialog";
    132     protected static final String BUNDLE_KEY_DELETE_DIALOG_VISIBLE = "key_delete_dialog_visible";
    133     protected static final String BUNDLE_KEY_WINDOW_STYLE = "key_window_style";
    134     protected static final String BUNDLE_KEY_CALENDAR_COLOR = "key_calendar_color";
    135     protected static final String BUNDLE_KEY_CALENDAR_COLOR_INIT = "key_calendar_color_init";
    136     protected static final String BUNDLE_KEY_CURRENT_COLOR = "key_current_color";
    137     protected static final String BUNDLE_KEY_CURRENT_COLOR_KEY = "key_current_color_key";
    138     protected static final String BUNDLE_KEY_CURRENT_COLOR_INIT = "key_current_color_init";
    139     protected static final String BUNDLE_KEY_ORIGINAL_COLOR = "key_original_color";
    140     protected static final String BUNDLE_KEY_ORIGINAL_COLOR_INIT = "key_original_color_init";
    141     protected static final String BUNDLE_KEY_ATTENDEE_RESPONSE = "key_attendee_response";
    142     protected static final String BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE =
    143             "key_user_set_attendee_response";
    144     protected static final String BUNDLE_KEY_TENTATIVE_USER_RESPONSE =
    145             "key_tentative_user_response";
    146     protected static final String BUNDLE_KEY_RESPONSE_WHICH_EVENTS = "key_response_which_events";
    147     protected static final String BUNDLE_KEY_REMINDER_MINUTES = "key_reminder_minutes";
    148     protected static final String BUNDLE_KEY_REMINDER_METHODS = "key_reminder_methods";
    149 
    150 
    151     private static final String PERIOD_SPACE = ". ";
    152 
    153     private static final String NO_EVENT_COLOR = "";
    154 
    155     /**
    156      * These are the corresponding indices into the array of strings
    157      * "R.array.change_response_labels" in the resource file.
    158      */
    159     static final int UPDATE_SINGLE = 0;
    160     static final int UPDATE_ALL = 1;
    161 
    162     // Style of view
    163     public static final int FULL_WINDOW_STYLE = 0;
    164     public static final int DIALOG_WINDOW_STYLE = 1;
    165 
    166     private int mWindowStyle = DIALOG_WINDOW_STYLE;
    167 
    168     // Query tokens for QueryHandler
    169     private static final int TOKEN_QUERY_EVENT = 1 << 0;
    170     private static final int TOKEN_QUERY_CALENDARS = 1 << 1;
    171     private static final int TOKEN_QUERY_ATTENDEES = 1 << 2;
    172     private static final int TOKEN_QUERY_DUPLICATE_CALENDARS = 1 << 3;
    173     private static final int TOKEN_QUERY_REMINDERS = 1 << 4;
    174     private static final int TOKEN_QUERY_VISIBLE_CALENDARS = 1 << 5;
    175     private static final int TOKEN_QUERY_COLORS = 1 << 6;
    176 
    177     private static final int TOKEN_QUERY_ALL = TOKEN_QUERY_DUPLICATE_CALENDARS
    178             | TOKEN_QUERY_ATTENDEES | TOKEN_QUERY_CALENDARS | TOKEN_QUERY_EVENT
    179             | TOKEN_QUERY_REMINDERS | TOKEN_QUERY_VISIBLE_CALENDARS | TOKEN_QUERY_COLORS;
    180 
    181     private int mCurrentQuery = 0;
    182 
    183     private static final String[] EVENT_PROJECTION = new String[] {
    184         Events._ID,                  // 0  do not remove; used in DeleteEventHelper
    185         Events.TITLE,                // 1  do not remove; used in DeleteEventHelper
    186         Events.RRULE,                // 2  do not remove; used in DeleteEventHelper
    187         Events.ALL_DAY,              // 3  do not remove; used in DeleteEventHelper
    188         Events.CALENDAR_ID,          // 4  do not remove; used in DeleteEventHelper
    189         Events.DTSTART,              // 5  do not remove; used in DeleteEventHelper
    190         Events._SYNC_ID,             // 6  do not remove; used in DeleteEventHelper
    191         Events.EVENT_TIMEZONE,       // 7  do not remove; used in DeleteEventHelper
    192         Events.DESCRIPTION,          // 8
    193         Events.EVENT_LOCATION,       // 9
    194         Calendars.CALENDAR_ACCESS_LEVEL, // 10
    195         Events.CALENDAR_COLOR,       // 11
    196         Events.EVENT_COLOR,          // 12
    197         Events.HAS_ATTENDEE_DATA,    // 13
    198         Events.ORGANIZER,            // 14
    199         Events.HAS_ALARM,            // 15
    200         Calendars.MAX_REMINDERS,     // 16
    201         Calendars.ALLOWED_REMINDERS, // 17
    202         Events.CUSTOM_APP_PACKAGE,   // 18
    203         Events.CUSTOM_APP_URI,       // 19
    204         Events.DTEND,                // 20
    205         Events.DURATION,             // 21
    206         Events.ORIGINAL_SYNC_ID      // 22 do not remove; used in DeleteEventHelper
    207     };
    208     private static final int EVENT_INDEX_ID = 0;
    209     private static final int EVENT_INDEX_TITLE = 1;
    210     private static final int EVENT_INDEX_RRULE = 2;
    211     private static final int EVENT_INDEX_ALL_DAY = 3;
    212     private static final int EVENT_INDEX_CALENDAR_ID = 4;
    213     private static final int EVENT_INDEX_DTSTART = 5;
    214     private static final int EVENT_INDEX_SYNC_ID = 6;
    215     private static final int EVENT_INDEX_EVENT_TIMEZONE = 7;
    216     private static final int EVENT_INDEX_DESCRIPTION = 8;
    217     private static final int EVENT_INDEX_EVENT_LOCATION = 9;
    218     private static final int EVENT_INDEX_ACCESS_LEVEL = 10;
    219     private static final int EVENT_INDEX_CALENDAR_COLOR = 11;
    220     private static final int EVENT_INDEX_EVENT_COLOR = 12;
    221     private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 13;
    222     private static final int EVENT_INDEX_ORGANIZER = 14;
    223     private static final int EVENT_INDEX_HAS_ALARM = 15;
    224     private static final int EVENT_INDEX_MAX_REMINDERS = 16;
    225     private static final int EVENT_INDEX_ALLOWED_REMINDERS = 17;
    226     private static final int EVENT_INDEX_CUSTOM_APP_PACKAGE = 18;
    227     private static final int EVENT_INDEX_CUSTOM_APP_URI = 19;
    228     private static final int EVENT_INDEX_DTEND = 20;
    229     private static final int EVENT_INDEX_DURATION = 21;
    230 
    231     private static final String[] ATTENDEES_PROJECTION = new String[] {
    232         Attendees._ID,                      // 0
    233         Attendees.ATTENDEE_NAME,            // 1
    234         Attendees.ATTENDEE_EMAIL,           // 2
    235         Attendees.ATTENDEE_RELATIONSHIP,    // 3
    236         Attendees.ATTENDEE_STATUS,          // 4
    237         Attendees.ATTENDEE_IDENTITY,        // 5
    238         Attendees.ATTENDEE_ID_NAMESPACE     // 6
    239     };
    240     private static final int ATTENDEES_INDEX_ID = 0;
    241     private static final int ATTENDEES_INDEX_NAME = 1;
    242     private static final int ATTENDEES_INDEX_EMAIL = 2;
    243     private static final int ATTENDEES_INDEX_RELATIONSHIP = 3;
    244     private static final int ATTENDEES_INDEX_STATUS = 4;
    245     private static final int ATTENDEES_INDEX_IDENTITY = 5;
    246     private static final int ATTENDEES_INDEX_ID_NAMESPACE = 6;
    247 
    248     static {
    249         if (!Utils.isJellybeanOrLater()) {
    250             EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_PACKAGE] = Events._ID; // dummy value
    251             EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_URI] = Events._ID; // dummy value
    252 
    253             ATTENDEES_PROJECTION[ATTENDEES_INDEX_IDENTITY] = Attendees._ID; // dummy value
    254             ATTENDEES_PROJECTION[ATTENDEES_INDEX_ID_NAMESPACE] = Attendees._ID; // dummy value
    255         }
    256     }
    257 
    258     private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
    259 
    260     private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, "
    261             + Attendees.ATTENDEE_EMAIL + " ASC";
    262 
    263     private static final String[] REMINDERS_PROJECTION = new String[] {
    264         Reminders._ID,                      // 0
    265         Reminders.MINUTES,            // 1
    266         Reminders.METHOD           // 2
    267     };
    268     private static final int REMINDERS_INDEX_ID = 0;
    269     private static final int REMINDERS_MINUTES_ID = 1;
    270     private static final int REMINDERS_METHOD_ID = 2;
    271 
    272     private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=?";
    273 
    274     static final String[] CALENDARS_PROJECTION = new String[] {
    275         Calendars._ID,           // 0
    276         Calendars.CALENDAR_DISPLAY_NAME,  // 1
    277         Calendars.OWNER_ACCOUNT, // 2
    278         Calendars.CAN_ORGANIZER_RESPOND, // 3
    279         Calendars.ACCOUNT_NAME, // 4
    280         Calendars.ACCOUNT_TYPE  // 5
    281     };
    282     static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
    283     static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
    284     static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3;
    285     static final int CALENDARS_INDEX_ACCOUNT_NAME = 4;
    286     static final int CALENDARS_INDEX_ACCOUNT_TYPE = 5;
    287 
    288     static final String CALENDARS_WHERE = Calendars._ID + "=?";
    289     static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.CALENDAR_DISPLAY_NAME + "=?";
    290     static final String CALENDARS_VISIBLE_WHERE = Calendars.VISIBLE + "=?";
    291 
    292     static final String[] COLORS_PROJECTION = new String[] {
    293         Colors._ID, // 0
    294         Colors.COLOR, // 1
    295         Colors.COLOR_KEY // 2
    296     };
    297 
    298     static final String COLORS_WHERE = Colors.ACCOUNT_NAME + "=? AND " + Colors.ACCOUNT_TYPE +
    299         "=? AND " + Colors.COLOR_TYPE + "=" + Colors.TYPE_EVENT;
    300 
    301     public static final int COLORS_INDEX_COLOR = 1;
    302     public static final int COLORS_INDEX_COLOR_KEY = 2;
    303 
    304     private View mView;
    305 
    306     private Uri mUri;
    307     private long mEventId;
    308     private Cursor mEventCursor;
    309     private Cursor mAttendeesCursor;
    310     private Cursor mCalendarsCursor;
    311     private Cursor mRemindersCursor;
    312 
    313     private static float mScale = 0; // Used for supporting different screen densities
    314 
    315     private static int mCustomAppIconSize = 32;
    316 
    317     private long mStartMillis;
    318     private long mEndMillis;
    319     private boolean mAllDay;
    320 
    321     private boolean mHasAttendeeData;
    322     private String mEventOrganizerEmail;
    323     private String mEventOrganizerDisplayName = "";
    324     private boolean mIsOrganizer;
    325     private long mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE;
    326     private boolean mOwnerCanRespond;
    327     private String mSyncAccountName;
    328     private String mCalendarOwnerAccount;
    329     private boolean mCanModifyCalendar;
    330     private boolean mCanModifyEvent;
    331     private boolean mIsBusyFreeCalendar;
    332     private int mNumOfAttendees;
    333     private EditResponseHelper mEditResponseHelper;
    334     private boolean mDeleteDialogVisible = false;
    335     private DeleteEventHelper mDeleteHelper;
    336 
    337     private int mOriginalAttendeeResponse;
    338     private int mAttendeeResponseFromIntent = Attendees.ATTENDEE_STATUS_NONE;
    339     private int mUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
    340     private int mWhichEvents = -1;
    341     // Used as the temporary response until the dialog is confirmed. It is also
    342     // able to be used as a state marker for configuration changes.
    343     private int mTentativeUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
    344     private boolean mIsRepeating;
    345     private boolean mHasAlarm;
    346     private int mMaxReminders;
    347     private String mCalendarAllowedReminders;
    348     // Used to prevent saving changes in event if it is being deleted.
    349     private boolean mEventDeletionStarted = false;
    350 
    351     private TextView mTitle;
    352     private TextView mWhenDateTime;
    353     private TextView mWhere;
    354     private ExpandableTextView mDesc;
    355     private AttendeesView mLongAttendees;
    356     private Button emailAttendeesButton;
    357     private Menu mMenu = null;
    358     private View mHeadlines;
    359     private ScrollView mScrollView;
    360     private View mLoadingMsgView;
    361     private ObjectAnimator mAnimateAlpha;
    362     private long mLoadingMsgStartTime;
    363 
    364     private EventColorPickerDialog mColorPickerDialog;
    365     private SparseIntArray mDisplayColorKeyMap = new SparseIntArray();
    366     private int[] mColors;
    367     private int mOriginalColor = -1;
    368     private boolean mOriginalColorInitialized = false;
    369     private int mCalendarColor = -1;
    370     private boolean mCalendarColorInitialized = false;
    371     private int mCurrentColor = -1;
    372     private boolean mCurrentColorInitialized = false;
    373     private int mCurrentColorKey = -1;
    374 
    375     private static final int FADE_IN_TIME = 300;   // in milliseconds
    376     private static final int LOADING_MSG_DELAY = 600;   // in milliseconds
    377     private static final int LOADING_MSG_MIN_DISPLAY_TIME = 600;
    378     private boolean mNoCrossFade = false;  // Used to prevent repeated cross-fade
    379     private RadioGroup mResponseRadioGroup;
    380 
    381     ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>();
    382     ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>();
    383     ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>();
    384     ArrayList<Attendee> mNoResponseAttendees = new ArrayList<Attendee>();
    385     ArrayList<String> mToEmails = new ArrayList<String>();
    386     ArrayList<String> mCcEmails = new ArrayList<String>();
    387 
    388     private int mDefaultReminderMinutes;
    389     private final ArrayList<LinearLayout> mReminderViews = new ArrayList<LinearLayout>(0);
    390     public ArrayList<ReminderEntry> mReminders;
    391     public ArrayList<ReminderEntry> mOriginalReminders = new ArrayList<ReminderEntry>();
    392     public ArrayList<ReminderEntry> mUnsupportedReminders = new ArrayList<ReminderEntry>();
    393     private boolean mUserModifiedReminders = false;
    394 
    395     /**
    396      * Contents of the "minutes" spinner.  This has default values from the XML file, augmented
    397      * with any additional values that were already associated with the event.
    398      */
    399     private ArrayList<Integer> mReminderMinuteValues;
    400     private ArrayList<String> mReminderMinuteLabels;
    401 
    402     /**
    403      * Contents of the "methods" spinner.  The "values" list specifies the method constant
    404      * (e.g. {@link Reminders#METHOD_ALERT}) associated with the labels.  Any methods that
    405      * aren't allowed by the Calendar will be removed.
    406      */
    407     private ArrayList<Integer> mReminderMethodValues;
    408     private ArrayList<String> mReminderMethodLabels;
    409 
    410     private QueryHandler mHandler;
    411 
    412 
    413     private final Runnable mTZUpdater = new Runnable() {
    414         @Override
    415         public void run() {
    416             updateEvent(mView);
    417         }
    418     };
    419 
    420     private final Runnable mLoadingMsgAlphaUpdater = new Runnable() {
    421         @Override
    422         public void run() {
    423             // Since this is run after a delay, make sure to only show the message
    424             // if the event's data is not shown yet.
    425             if (!mAnimateAlpha.isRunning() && mScrollView.getAlpha() == 0) {
    426                 mLoadingMsgStartTime = System.currentTimeMillis();
    427                 mLoadingMsgView.setAlpha(1);
    428             }
    429         }
    430     };
    431 
    432     private OnItemSelectedListener mReminderChangeListener;
    433 
    434     private static int mDialogWidth = 500;
    435     private static int mDialogHeight = 600;
    436     private static int DIALOG_TOP_MARGIN = 8;
    437     private boolean mIsDialog = false;
    438     private boolean mIsPaused = true;
    439     private boolean mDismissOnResume = false;
    440     private int mX = -1;
    441     private int mY = -1;
    442     private int mMinTop;         // Dialog cannot be above this location
    443     private boolean mIsTabletConfig;
    444     private Activity mActivity;
    445     private Context mContext;
    446 
    447     private CalendarController mController;
    448 
    449     private class QueryHandler extends AsyncQueryService {
    450         public QueryHandler(Context context) {
    451             super(context);
    452         }
    453 
    454         @Override
    455         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    456             // if the activity is finishing, then close the cursor and return
    457             final Activity activity = getActivity();
    458             if (activity == null || activity.isFinishing()) {
    459                 if (cursor != null) {
    460                     cursor.close();
    461                 }
    462                 return;
    463             }
    464 
    465             switch (token) {
    466             case TOKEN_QUERY_EVENT:
    467                 mEventCursor = Utils.matrixCursorFromCursor(cursor);
    468                 if (initEventCursor()) {
    469                     // The cursor is empty. This can happen if the event was
    470                     // deleted.
    471                     // FRAG_TODO we should no longer rely on Activity.finish()
    472                     activity.finish();
    473                     return;
    474                 }
    475                 if (!mCalendarColorInitialized) {
    476                     mCalendarColor = Utils.getDisplayColorFromColor(
    477                             mEventCursor.getInt(EVENT_INDEX_CALENDAR_COLOR));
    478                     mCalendarColorInitialized = true;
    479                 }
    480 
    481                 if (!mOriginalColorInitialized) {
    482                     mOriginalColor = mEventCursor.isNull(EVENT_INDEX_EVENT_COLOR)
    483                             ? mCalendarColor : Utils.getDisplayColorFromColor(
    484                                     mEventCursor.getInt(EVENT_INDEX_EVENT_COLOR));
    485                     mOriginalColorInitialized = true;
    486                 }
    487 
    488                 if (!mCurrentColorInitialized) {
    489                     mCurrentColor = mOriginalColor;
    490                     mCurrentColorInitialized = true;
    491                 }
    492 
    493                 updateEvent(mView);
    494                 prepareReminders();
    495 
    496                 // start calendar query
    497                 Uri uri = Calendars.CONTENT_URI;
    498                 String[] args = new String[] {
    499                         Long.toString(mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID))};
    500                 startQuery(TOKEN_QUERY_CALENDARS, null, uri, CALENDARS_PROJECTION,
    501                         CALENDARS_WHERE, args, null);
    502                 break;
    503             case TOKEN_QUERY_CALENDARS:
    504                 mCalendarsCursor = Utils.matrixCursorFromCursor(cursor);
    505                 updateCalendar(mView);
    506                 // FRAG_TODO fragments shouldn't set the title anymore
    507                 updateTitle();
    508 
    509                 args = new String[] {
    510                         mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME),
    511                         mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_TYPE) };
    512                 uri = Colors.CONTENT_URI;
    513                 startQuery(TOKEN_QUERY_COLORS, null, uri, COLORS_PROJECTION, COLORS_WHERE, args,
    514                         null);
    515 
    516                 if (!mIsBusyFreeCalendar) {
    517                     args = new String[] { Long.toString(mEventId) };
    518 
    519                     // start attendees query
    520                     uri = Attendees.CONTENT_URI;
    521                     startQuery(TOKEN_QUERY_ATTENDEES, null, uri, ATTENDEES_PROJECTION,
    522                             ATTENDEES_WHERE, args, ATTENDEES_SORT_ORDER);
    523                 } else {
    524                     sendAccessibilityEventIfQueryDone(TOKEN_QUERY_ATTENDEES);
    525                 }
    526                 if (mHasAlarm) {
    527                     // start reminders query
    528                     args = new String[] { Long.toString(mEventId) };
    529                     uri = Reminders.CONTENT_URI;
    530                     startQuery(TOKEN_QUERY_REMINDERS, null, uri,
    531                             REMINDERS_PROJECTION, REMINDERS_WHERE, args, null);
    532                 } else {
    533                     sendAccessibilityEventIfQueryDone(TOKEN_QUERY_REMINDERS);
    534                 }
    535                 break;
    536             case TOKEN_QUERY_COLORS:
    537                 ArrayList<Integer> colors = new ArrayList<Integer>();
    538                 if (cursor.moveToFirst()) {
    539                     do
    540                     {
    541                         int colorKey = cursor.getInt(COLORS_INDEX_COLOR_KEY);
    542                         int rawColor = cursor.getInt(COLORS_INDEX_COLOR);
    543                         int displayColor = Utils.getDisplayColorFromColor(rawColor);
    544                         mDisplayColorKeyMap.put(displayColor, colorKey);
    545                         colors.add(displayColor);
    546                     } while (cursor.moveToNext());
    547                 }
    548                 cursor.close();
    549                 Integer[] sortedColors = new Integer[colors.size()];
    550                 Arrays.sort(colors.toArray(sortedColors), new HsvColorComparator());
    551                 mColors = new int[sortedColors.length];
    552                 for (int i = 0; i < sortedColors.length; i++) {
    553                     mColors[i] = sortedColors[i].intValue();
    554 
    555                     float[] hsv = new float[3];
    556                     Color.colorToHSV(mColors[i], hsv);
    557                     if (DEBUG) {
    558                         Log.d("Color", "H:" + hsv[0] + ",S:" + hsv[1] + ",V:" + hsv[2]);
    559                     }
    560                 }
    561                 if (mCanModifyCalendar) {
    562                     View button = mView.findViewById(R.id.change_color);
    563                     if (button != null && mColors.length > 0) {
    564                         button.setEnabled(true);
    565                         button.setVisibility(View.VISIBLE);
    566                     }
    567                 }
    568                 updateMenu();
    569                 break;
    570             case TOKEN_QUERY_ATTENDEES:
    571                 mAttendeesCursor = Utils.matrixCursorFromCursor(cursor);
    572                 initAttendeesCursor(mView);
    573                 updateResponse(mView);
    574                 break;
    575             case TOKEN_QUERY_REMINDERS:
    576                 mRemindersCursor = Utils.matrixCursorFromCursor(cursor);
    577                 initReminders(mView, mRemindersCursor);
    578                 break;
    579             case TOKEN_QUERY_VISIBLE_CALENDARS:
    580                 if (cursor.getCount() > 1) {
    581                     // Start duplicate calendars query to detect whether to add the calendar
    582                     // email to the calendar owner display.
    583                     String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
    584                     mHandler.startQuery(TOKEN_QUERY_DUPLICATE_CALENDARS, null,
    585                             Calendars.CONTENT_URI, CALENDARS_PROJECTION,
    586                             CALENDARS_DUPLICATE_NAME_WHERE, new String[] {displayName}, null);
    587                 } else {
    588                     // Don't need to display the calendar owner when there is only a single
    589                     // calendar.  Skip the duplicate calendars query.
    590                     setVisibilityCommon(mView, R.id.calendar_container, View.GONE);
    591                     mCurrentQuery |= TOKEN_QUERY_DUPLICATE_CALENDARS;
    592                 }
    593                 break;
    594             case TOKEN_QUERY_DUPLICATE_CALENDARS:
    595                 SpannableStringBuilder sb = new SpannableStringBuilder();
    596 
    597                 // Calendar display name
    598                 String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
    599                 sb.append(calendarName);
    600 
    601                 // Show email account if display name is not unique and
    602                 // display name != email
    603                 String email = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
    604                 if (cursor.getCount() > 1 && !calendarName.equalsIgnoreCase(email) &&
    605                         Utils.isValidEmail(email)) {
    606                     sb.append(" (").append(email).append(")");
    607                 }
    608 
    609                 setVisibilityCommon(mView, R.id.calendar_container, View.VISIBLE);
    610                 setTextCommon(mView, R.id.calendar_name, sb);
    611                 break;
    612             }
    613             cursor.close();
    614             sendAccessibilityEventIfQueryDone(token);
    615 
    616             // All queries are done, show the view.
    617             if (mCurrentQuery == TOKEN_QUERY_ALL) {
    618                 if (mLoadingMsgView.getAlpha() == 1) {
    619                     // Loading message is showing, let it stay a bit more (to prevent
    620                     // flashing) by adding a start delay to the event animation
    621                     long timeDiff = LOADING_MSG_MIN_DISPLAY_TIME - (System.currentTimeMillis() -
    622                             mLoadingMsgStartTime);
    623                     if (timeDiff > 0) {
    624                         mAnimateAlpha.setStartDelay(timeDiff);
    625                     }
    626                 }
    627                 if (!mAnimateAlpha.isRunning() &&!mAnimateAlpha.isStarted() && !mNoCrossFade) {
    628                     mAnimateAlpha.start();
    629                 } else {
    630                     mScrollView.setAlpha(1);
    631                     mLoadingMsgView.setVisibility(View.GONE);
    632                 }
    633             }
    634         }
    635     }
    636 
    637     private void sendAccessibilityEventIfQueryDone(int token) {
    638         mCurrentQuery |= token;
    639         if (mCurrentQuery == TOKEN_QUERY_ALL) {
    640             sendAccessibilityEvent();
    641         }
    642     }
    643 
    644     public EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis,
    645             int attendeeResponse, boolean isDialog, int windowStyle,
    646             ArrayList<ReminderEntry> reminders) {
    647 
    648         Resources r = context.getResources();
    649         if (mScale == 0) {
    650             mScale = context.getResources().getDisplayMetrics().density;
    651             if (mScale != 1) {
    652                 mCustomAppIconSize *= mScale;
    653                 if (isDialog) {
    654                     DIALOG_TOP_MARGIN *= mScale;
    655                 }
    656             }
    657         }
    658         if (isDialog) {
    659             setDialogSize(r);
    660         }
    661         mIsDialog = isDialog;
    662 
    663         setStyle(DialogFragment.STYLE_NO_TITLE, 0);
    664         mUri = uri;
    665         mStartMillis = startMillis;
    666         mEndMillis = endMillis;
    667         mAttendeeResponseFromIntent = attendeeResponse;
    668         mWindowStyle = windowStyle;
    669 
    670         // Pass in null if no reminders are being specified.
    671         // This may be used to explicitly show certain reminders already known
    672         // about, such as during configuration changes.
    673         mReminders = reminders;
    674     }
    675 
    676     // This is currently required by the fragment manager.
    677     public EventInfoFragment() {
    678     }
    679 
    680     public EventInfoFragment(Context context, long eventId, long startMillis, long endMillis,
    681             int attendeeResponse, boolean isDialog, int windowStyle,
    682             ArrayList<ReminderEntry> reminders) {
    683         this(context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis,
    684                 endMillis, attendeeResponse, isDialog, windowStyle, reminders);
    685         mEventId = eventId;
    686     }
    687 
    688     @Override
    689     public void onActivityCreated(Bundle savedInstanceState) {
    690         super.onActivityCreated(savedInstanceState);
    691 
    692         mReminderChangeListener = new OnItemSelectedListener() {
    693             @Override
    694             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    695                 Integer prevValue = (Integer) parent.getTag();
    696                 if (prevValue == null || prevValue != position) {
    697                     parent.setTag(position);
    698                     mUserModifiedReminders = true;
    699                 }
    700             }
    701 
    702             @Override
    703             public void onNothingSelected(AdapterView<?> parent) {
    704                 // do nothing
    705             }
    706 
    707         };
    708 
    709         if (savedInstanceState != null) {
    710             mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false);
    711             mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE,
    712                     DIALOG_WINDOW_STYLE);
    713         }
    714 
    715         if (mIsDialog) {
    716             applyDialogParams();
    717         }
    718 
    719         final Activity activity = getActivity();
    720         mContext = activity;
    721         mColorPickerDialog = (EventColorPickerDialog) activity.getFragmentManager()
    722                 .findFragmentByTag(COLOR_PICKER_DIALOG_TAG);
    723         if (mColorPickerDialog != null) {
    724             mColorPickerDialog.setOnColorSelectedListener(this);
    725         }
    726     }
    727 
    728     private void applyDialogParams() {
    729         Dialog dialog = getDialog();
    730         dialog.setCanceledOnTouchOutside(true);
    731 
    732         Window window = dialog.getWindow();
    733         window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
    734 
    735         WindowManager.LayoutParams a = window.getAttributes();
    736         a.dimAmount = .4f;
    737 
    738         a.width = mDialogWidth;
    739         a.height = mDialogHeight;
    740 
    741 
    742         // On tablets , do smart positioning of dialog
    743         // On phones , use the whole screen
    744 
    745         if (mX != -1 || mY != -1) {
    746             a.x = mX - mDialogWidth / 2;
    747             a.y = mY - mDialogHeight / 2;
    748             if (a.y < mMinTop) {
    749                 a.y = mMinTop + DIALOG_TOP_MARGIN;
    750             }
    751             a.gravity = Gravity.LEFT | Gravity.TOP;
    752         }
    753         window.setAttributes(a);
    754     }
    755 
    756     public void setDialogParams(int x, int y, int minTop) {
    757         mX = x;
    758         mY = y;
    759         mMinTop = minTop;
    760     }
    761 
    762     // Implements OnCheckedChangeListener
    763     @Override
    764     public void onCheckedChanged(RadioGroup group, int checkedId) {
    765         // If we haven't finished the return from the dialog yet, don't display.
    766         if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
    767             return;
    768         }
    769 
    770         // If this is not a repeating event, then don't display the dialog
    771         // asking which events to change.
    772         int response = getResponseFromButtonId(checkedId);
    773         if (!mIsRepeating) {
    774             mUserSetResponse = response;
    775             return;
    776         }
    777 
    778         // If the selection is the same as the original, then don't display the
    779         // dialog asking which events to change.
    780         if (checkedId == findButtonIdForResponse(mOriginalAttendeeResponse)) {
    781             mUserSetResponse = response;
    782             return;
    783         }
    784 
    785         // This is a repeating event. We need to ask the user if they mean to
    786         // change just this one instance or all instances.
    787         mTentativeUserSetResponse = response;
    788         mEditResponseHelper.showDialog(mWhichEvents);
    789     }
    790 
    791     public void onNothingSelected(AdapterView<?> parent) {
    792     }
    793 
    794     @Override
    795     public void onDetach() {
    796         super.onDetach();
    797         mController.deregisterEventHandler(R.layout.event_info);
    798     }
    799 
    800     @Override
    801     public void onAttach(Activity activity) {
    802         super.onAttach(activity);
    803         mActivity = activity;
    804         // Ensure that mIsTabletConfig is set before creating the menu.
    805         mIsTabletConfig = Utils.getConfigBool(mActivity, R.bool.tablet_config);
    806         mController = CalendarController.getInstance(mActivity);
    807         mController.registerEventHandler(R.layout.event_info, this);
    808         mEditResponseHelper = new EditResponseHelper(activity);
    809         mEditResponseHelper.setDismissListener(
    810                 new DialogInterface.OnDismissListener() {
    811             @Override
    812             public void onDismiss(DialogInterface dialog) {
    813                 // If the user dismisses the dialog (without hitting OK),
    814                 // then we want to revert the selection that opened the dialog.
    815                 if (mEditResponseHelper.getWhichEvents() != -1) {
    816                     mUserSetResponse = mTentativeUserSetResponse;
    817                     mWhichEvents = mEditResponseHelper.getWhichEvents();
    818                 } else {
    819                     // Revert the attending response radio selection to whatever
    820                     // was selected prior to this selection (possibly nothing).
    821                     int oldResponse;
    822                     if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
    823                         oldResponse = mUserSetResponse;
    824                     } else {
    825                         oldResponse = mOriginalAttendeeResponse;
    826                     }
    827                     int buttonToCheck = findButtonIdForResponse(oldResponse);
    828 
    829                     if (mResponseRadioGroup != null) {
    830                         mResponseRadioGroup.check(buttonToCheck);
    831                     }
    832 
    833                     // If the radio group is being cleared, also clear the
    834                     // dialog's selection of which events should be included
    835                     // in this response.
    836                     if (buttonToCheck == -1) {
    837                         mEditResponseHelper.setWhichEvents(-1);
    838                     }
    839                 }
    840 
    841                 // Since OnPause will force the dialog to dismiss, do
    842                 // not change the dialog status
    843                 if (!mIsPaused) {
    844                     mTentativeUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
    845                 }
    846             }
    847         });
    848 
    849         if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) {
    850             mEditResponseHelper.setWhichEvents(UPDATE_ALL);
    851             mWhichEvents = mEditResponseHelper.getWhichEvents();
    852         }
    853         mHandler = new QueryHandler(activity);
    854         if (!mIsDialog) {
    855             setHasOptionsMenu(true);
    856         }
    857     }
    858 
    859     @Override
    860     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    861             Bundle savedInstanceState) {
    862 
    863         if (savedInstanceState != null) {
    864             mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false);
    865             mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE,
    866                     DIALOG_WINDOW_STYLE);
    867             mDeleteDialogVisible =
    868                 savedInstanceState.getBoolean(BUNDLE_KEY_DELETE_DIALOG_VISIBLE,false);
    869             mCalendarColor = savedInstanceState.getInt(BUNDLE_KEY_CALENDAR_COLOR);
    870             mCalendarColorInitialized =
    871                     savedInstanceState.getBoolean(BUNDLE_KEY_CALENDAR_COLOR_INIT);
    872             mOriginalColor = savedInstanceState.getInt(BUNDLE_KEY_ORIGINAL_COLOR);
    873             mOriginalColorInitialized = savedInstanceState.getBoolean(
    874                     BUNDLE_KEY_ORIGINAL_COLOR_INIT);
    875             mCurrentColor = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_COLOR);
    876             mCurrentColorInitialized = savedInstanceState.getBoolean(
    877                     BUNDLE_KEY_CURRENT_COLOR_INIT);
    878             mCurrentColorKey = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_COLOR_KEY);
    879 
    880             mTentativeUserSetResponse = savedInstanceState.getInt(
    881                             BUNDLE_KEY_TENTATIVE_USER_RESPONSE,
    882                             Attendees.ATTENDEE_STATUS_NONE);
    883             if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE &&
    884                     mEditResponseHelper != null) {
    885                 // If the edit response helper dialog is open, we'll need to
    886                 // know if either of the choices were selected.
    887                 mEditResponseHelper.setWhichEvents(savedInstanceState.getInt(
    888                         BUNDLE_KEY_RESPONSE_WHICH_EVENTS, -1));
    889             }
    890             mUserSetResponse = savedInstanceState.getInt(
    891                     BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE,
    892                     Attendees.ATTENDEE_STATUS_NONE);
    893             if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
    894                 // If the response was set by the user before a configuration
    895                 // change, we'll need to know which choice was selected.
    896                 mWhichEvents = savedInstanceState.getInt(
    897                         BUNDLE_KEY_RESPONSE_WHICH_EVENTS, -1);
    898             }
    899 
    900             mReminders = Utils.readRemindersFromBundle(savedInstanceState);
    901         }
    902 
    903         if (mWindowStyle == DIALOG_WINDOW_STYLE) {
    904             mView = inflater.inflate(R.layout.event_info_dialog, container, false);
    905         } else {
    906             mView = inflater.inflate(R.layout.event_info, container, false);
    907         }
    908         mScrollView = (ScrollView) mView.findViewById(R.id.event_info_scroll_view);
    909         mLoadingMsgView = mView.findViewById(R.id.event_info_loading_msg);
    910         mTitle = (TextView) mView.findViewById(R.id.title);
    911         mWhenDateTime = (TextView) mView.findViewById(R.id.when_datetime);
    912         mWhere = (TextView) mView.findViewById(R.id.where);
    913         mDesc = (ExpandableTextView) mView.findViewById(R.id.description);
    914         mHeadlines = mView.findViewById(R.id.event_info_headline);
    915         mLongAttendees = (AttendeesView) mView.findViewById(R.id.long_attendee_list);
    916 
    917         mResponseRadioGroup = (RadioGroup) mView.findViewById(R.id.response_value);
    918 
    919         if (mUri == null) {
    920             // restore event ID from bundle
    921             mEventId = savedInstanceState.getLong(BUNDLE_KEY_EVENT_ID);
    922             mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
    923             mStartMillis = savedInstanceState.getLong(BUNDLE_KEY_START_MILLIS);
    924             mEndMillis = savedInstanceState.getLong(BUNDLE_KEY_END_MILLIS);
    925         }
    926 
    927         mAnimateAlpha = ObjectAnimator.ofFloat(mScrollView, "Alpha", 0, 1);
    928         mAnimateAlpha.setDuration(FADE_IN_TIME);
    929         mAnimateAlpha.addListener(new AnimatorListenerAdapter() {
    930             int defLayerType;
    931 
    932             @Override
    933             public void onAnimationStart(Animator animation) {
    934                 // Use hardware layer for better performance during animation
    935                 defLayerType = mScrollView.getLayerType();
    936                 mScrollView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    937                 // Ensure that the loading message is gone before showing the
    938                 // event info
    939                 mLoadingMsgView.removeCallbacks(mLoadingMsgAlphaUpdater);
    940                 mLoadingMsgView.setVisibility(View.GONE);
    941             }
    942 
    943             @Override
    944             public void onAnimationCancel(Animator animation) {
    945                 mScrollView.setLayerType(defLayerType, null);
    946             }
    947 
    948             @Override
    949             public void onAnimationEnd(Animator animation) {
    950                 mScrollView.setLayerType(defLayerType, null);
    951                 // Do not cross fade after the first time
    952                 mNoCrossFade = true;
    953             }
    954         });
    955 
    956         mLoadingMsgView.setAlpha(0);
    957         mScrollView.setAlpha(0);
    958         mLoadingMsgView.postDelayed(mLoadingMsgAlphaUpdater, LOADING_MSG_DELAY);
    959 
    960         // start loading the data
    961 
    962         mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
    963                 null, null, null);
    964 
    965         View b = mView.findViewById(R.id.delete);
    966         b.setOnClickListener(new OnClickListener() {
    967             @Override
    968             public void onClick(View v) {
    969                 if (!mCanModifyCalendar) {
    970                     return;
    971                 }
    972                 mDeleteHelper =
    973                         new DeleteEventHelper(mContext, mActivity, !mIsDialog && !mIsTabletConfig /* exitWhenDone */);
    974                 mDeleteHelper.setDeleteNotificationListener(EventInfoFragment.this);
    975                 mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener());
    976                 mDeleteDialogVisible = true;
    977                 mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
    978             }
    979         });
    980 
    981         b = mView.findViewById(R.id.change_color);
    982         b.setOnClickListener(new OnClickListener() {
    983             @Override
    984             public void onClick(View v) {
    985                 if (!mCanModifyCalendar) {
    986                     return;
    987                 }
    988                 showEventColorPickerDialog();
    989             }
    990         });
    991 
    992         // Hide Edit/Delete buttons if in full screen mode on a phone
    993         if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) {
    994             mView.findViewById(R.id.event_info_buttons_container).setVisibility(View.GONE);
    995         }
    996 
    997         // Create a listener for the email guests button
    998         emailAttendeesButton = (Button) mView.findViewById(R.id.email_attendees_button);
    999         if (emailAttendeesButton != null) {
   1000             emailAttendeesButton.setOnClickListener(new View.OnClickListener() {
   1001                 @Override
   1002                 public void onClick(View v) {
   1003                     emailAttendees();
   1004                 }
   1005             });
   1006         }
   1007 
   1008         // Create a listener for the add reminder button
   1009         View reminderAddButton = mView.findViewById(R.id.reminder_add);
   1010         View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
   1011             @Override
   1012             public void onClick(View v) {
   1013                 addReminder();
   1014                 mUserModifiedReminders = true;
   1015             }
   1016         };
   1017         reminderAddButton.setOnClickListener(addReminderOnClickListener);
   1018 
   1019         // Set reminders variables
   1020 
   1021         SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity);
   1022         String defaultReminderString = prefs.getString(
   1023                 GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING);
   1024         mDefaultReminderMinutes = Integer.parseInt(defaultReminderString);
   1025         prepareReminders();
   1026 
   1027         return mView;
   1028     }
   1029 
   1030     private final Runnable onDeleteRunnable = new Runnable() {
   1031         @Override
   1032         public void run() {
   1033             if (EventInfoFragment.this.mIsPaused) {
   1034                 mDismissOnResume = true;
   1035                 return;
   1036             }
   1037             if (EventInfoFragment.this.isVisible()) {
   1038                 EventInfoFragment.this.dismiss();
   1039             }
   1040         }
   1041     };
   1042 
   1043     private void updateTitle() {
   1044         Resources res = getActivity().getResources();
   1045         if (mCanModifyCalendar && !mIsOrganizer) {
   1046             getActivity().setTitle(res.getString(R.string.event_info_title_invite));
   1047         } else {
   1048             getActivity().setTitle(res.getString(R.string.event_info_title));
   1049         }
   1050     }
   1051 
   1052     /**
   1053      * Initializes the event cursor, which is expected to point to the first
   1054      * (and only) result from a query.
   1055      * @return true if the cursor is empty.
   1056      */
   1057     private boolean initEventCursor() {
   1058         if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
   1059             return true;
   1060         }
   1061         mEventCursor.moveToFirst();
   1062         mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
   1063         String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
   1064         mIsRepeating = !TextUtils.isEmpty(rRule);
   1065         // mHasAlarm will be true if it was saved in the event already, or if
   1066         // we've explicitly been provided reminders (e.g. during rotation).
   1067         mHasAlarm = (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) == 1)? true :
   1068             (mReminders != null && mReminders.size() > 0);
   1069         mMaxReminders = mEventCursor.getInt(EVENT_INDEX_MAX_REMINDERS);
   1070         mCalendarAllowedReminders =  mEventCursor.getString(EVENT_INDEX_ALLOWED_REMINDERS);
   1071         return false;
   1072     }
   1073 
   1074     @SuppressWarnings("fallthrough")
   1075     private void initAttendeesCursor(View view) {
   1076         mOriginalAttendeeResponse = Attendees.ATTENDEE_STATUS_NONE;
   1077         mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE;
   1078         mNumOfAttendees = 0;
   1079         if (mAttendeesCursor != null) {
   1080             mNumOfAttendees = mAttendeesCursor.getCount();
   1081             if (mAttendeesCursor.moveToFirst()) {
   1082                 mAcceptedAttendees.clear();
   1083                 mDeclinedAttendees.clear();
   1084                 mTentativeAttendees.clear();
   1085                 mNoResponseAttendees.clear();
   1086 
   1087                 do {
   1088                     int status = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
   1089                     String name = mAttendeesCursor.getString(ATTENDEES_INDEX_NAME);
   1090                     String email = mAttendeesCursor.getString(ATTENDEES_INDEX_EMAIL);
   1091 
   1092                     if (mAttendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP) ==
   1093                             Attendees.RELATIONSHIP_ORGANIZER) {
   1094 
   1095                         // Overwrites the one from Event table if available
   1096                         if (!TextUtils.isEmpty(name)) {
   1097                             mEventOrganizerDisplayName = name;
   1098                             if (!mIsOrganizer) {
   1099                                 setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE);
   1100                                 setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName);
   1101                             }
   1102                         }
   1103                     }
   1104 
   1105                     if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE &&
   1106                             mCalendarOwnerAccount.equalsIgnoreCase(email)) {
   1107                         mCalendarOwnerAttendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID);
   1108                         mOriginalAttendeeResponse = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
   1109                     } else {
   1110                         String identity = null;
   1111                         String idNamespace = null;
   1112 
   1113                         if (Utils.isJellybeanOrLater()) {
   1114                             identity = mAttendeesCursor.getString(ATTENDEES_INDEX_IDENTITY);
   1115                             idNamespace = mAttendeesCursor.getString(ATTENDEES_INDEX_ID_NAMESPACE);
   1116                         }
   1117 
   1118                         // Don't show your own status in the list because:
   1119                         //  1) it doesn't make sense for event without other guests.
   1120                         //  2) there's a spinner for that for events with guests.
   1121                         switch(status) {
   1122                             case Attendees.ATTENDEE_STATUS_ACCEPTED:
   1123                                 mAcceptedAttendees.add(new Attendee(name, email,
   1124                                         Attendees.ATTENDEE_STATUS_ACCEPTED, identity,
   1125                                         idNamespace));
   1126                                 break;
   1127                             case Attendees.ATTENDEE_STATUS_DECLINED:
   1128                                 mDeclinedAttendees.add(new Attendee(name, email,
   1129                                         Attendees.ATTENDEE_STATUS_DECLINED, identity,
   1130                                         idNamespace));
   1131                                 break;
   1132                             case Attendees.ATTENDEE_STATUS_TENTATIVE:
   1133                                 mTentativeAttendees.add(new Attendee(name, email,
   1134                                         Attendees.ATTENDEE_STATUS_TENTATIVE, identity,
   1135                                         idNamespace));
   1136                                 break;
   1137                             default:
   1138                                 mNoResponseAttendees.add(new Attendee(name, email,
   1139                                         Attendees.ATTENDEE_STATUS_NONE, identity,
   1140                                         idNamespace));
   1141                         }
   1142                     }
   1143                 } while (mAttendeesCursor.moveToNext());
   1144                 mAttendeesCursor.moveToFirst();
   1145 
   1146                 updateAttendees(view);
   1147             }
   1148         }
   1149     }
   1150 
   1151     @Override
   1152     public void onSaveInstanceState(Bundle outState) {
   1153         super.onSaveInstanceState(outState);
   1154         outState.putLong(BUNDLE_KEY_EVENT_ID, mEventId);
   1155         outState.putLong(BUNDLE_KEY_START_MILLIS, mStartMillis);
   1156         outState.putLong(BUNDLE_KEY_END_MILLIS, mEndMillis);
   1157         outState.putBoolean(BUNDLE_KEY_IS_DIALOG, mIsDialog);
   1158         outState.putInt(BUNDLE_KEY_WINDOW_STYLE, mWindowStyle);
   1159         outState.putBoolean(BUNDLE_KEY_DELETE_DIALOG_VISIBLE, mDeleteDialogVisible);
   1160         outState.putInt(BUNDLE_KEY_CALENDAR_COLOR, mCalendarColor);
   1161         outState.putBoolean(BUNDLE_KEY_CALENDAR_COLOR_INIT, mCalendarColorInitialized);
   1162         outState.putInt(BUNDLE_KEY_ORIGINAL_COLOR, mOriginalColor);
   1163         outState.putBoolean(BUNDLE_KEY_ORIGINAL_COLOR_INIT, mOriginalColorInitialized);
   1164         outState.putInt(BUNDLE_KEY_CURRENT_COLOR, mCurrentColor);
   1165         outState.putBoolean(BUNDLE_KEY_CURRENT_COLOR_INIT, mCurrentColorInitialized);
   1166         outState.putInt(BUNDLE_KEY_CURRENT_COLOR_KEY, mCurrentColorKey);
   1167 
   1168         // We'll need the temporary response for configuration changes.
   1169         outState.putInt(BUNDLE_KEY_TENTATIVE_USER_RESPONSE, mTentativeUserSetResponse);
   1170         if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE &&
   1171                 mEditResponseHelper != null) {
   1172             outState.putInt(BUNDLE_KEY_RESPONSE_WHICH_EVENTS,
   1173                     mEditResponseHelper.getWhichEvents());
   1174         }
   1175 
   1176         // Save the current response.
   1177         int response;
   1178         if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) {
   1179             response = mAttendeeResponseFromIntent;
   1180         } else {
   1181             response = mOriginalAttendeeResponse;
   1182         }
   1183         outState.putInt(BUNDLE_KEY_ATTENDEE_RESPONSE, response);
   1184         if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
   1185             response = mUserSetResponse;
   1186             outState.putInt(BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE, response);
   1187             outState.putInt(BUNDLE_KEY_RESPONSE_WHICH_EVENTS, mWhichEvents);
   1188         }
   1189 
   1190         // Save the reminders.
   1191         mReminders = EventViewUtils.reminderItemsToReminders(mReminderViews,
   1192                 mReminderMinuteValues, mReminderMethodValues);
   1193         int numReminders = mReminders.size();
   1194         ArrayList<Integer> reminderMinutes =
   1195                 new ArrayList<Integer>(numReminders);
   1196         ArrayList<Integer> reminderMethods =
   1197                 new ArrayList<Integer>(numReminders);
   1198         for (ReminderEntry reminder : mReminders) {
   1199             reminderMinutes.add(reminder.getMinutes());
   1200             reminderMethods.add(reminder.getMethod());
   1201         }
   1202         outState.putIntegerArrayList(
   1203                 BUNDLE_KEY_REMINDER_MINUTES, reminderMinutes);
   1204         outState.putIntegerArrayList(
   1205                 BUNDLE_KEY_REMINDER_METHODS, reminderMethods);
   1206     }
   1207 
   1208     @Override
   1209     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
   1210         super.onCreateOptionsMenu(menu, inflater);
   1211         // Show color/edit/delete buttons only in non-dialog configuration
   1212         if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) {
   1213             inflater.inflate(R.menu.event_info_title_bar, menu);
   1214             mMenu = menu;
   1215             updateMenu();
   1216         }
   1217     }
   1218 
   1219     @Override
   1220     public boolean onOptionsItemSelected(MenuItem item) {
   1221 
   1222         // If we're a dialog we don't want to handle menu buttons
   1223         if (mIsDialog) {
   1224             return false;
   1225         }
   1226         // Handles option menu selections:
   1227         // Home button - close event info activity and start the main calendar
   1228         // one
   1229         // Edit button - start the event edit activity and close the info
   1230         // activity
   1231         // Delete button - start a delete query that calls a runnable that close
   1232         // the info activity
   1233 
   1234         final int itemId = item.getItemId();
   1235         if (itemId == android.R.id.home) {
   1236             Utils.returnToCalendarHome(mContext);
   1237             mActivity.finish();
   1238             return true;
   1239         } else if (itemId == R.id.info_action_edit) {
   1240             doEdit();
   1241             mActivity.finish();
   1242         } else if (itemId == R.id.info_action_delete) {
   1243             mDeleteHelper =
   1244                     new DeleteEventHelper(mActivity, mActivity, true /* exitWhenDone */);
   1245             mDeleteHelper.setDeleteNotificationListener(EventInfoFragment.this);
   1246             mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener());
   1247             mDeleteDialogVisible = true;
   1248             mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
   1249         } else if (itemId == R.id.info_action_change_color) {
   1250             showEventColorPickerDialog();
   1251         }
   1252         return super.onOptionsItemSelected(item);
   1253     }
   1254 
   1255     private void showEventColorPickerDialog() {
   1256         if (mColorPickerDialog == null) {
   1257             mColorPickerDialog = EventColorPickerDialog.newInstance(mColors, mCurrentColor,
   1258                     mCalendarColor, mIsTabletConfig);
   1259             mColorPickerDialog.setOnColorSelectedListener(this);
   1260         }
   1261         final FragmentManager fragmentManager = getFragmentManager();
   1262         fragmentManager.executePendingTransactions();
   1263         if (!mColorPickerDialog.isAdded()) {
   1264             mColorPickerDialog.show(fragmentManager, COLOR_PICKER_DIALOG_TAG);
   1265         }
   1266     }
   1267 
   1268     private boolean saveEventColor() {
   1269         if (mCurrentColor == mOriginalColor) {
   1270             return false;
   1271         }
   1272 
   1273         ContentValues values = new ContentValues();
   1274         if (mCurrentColor != mCalendarColor) {
   1275             values.put(Events.EVENT_COLOR_KEY, mCurrentColorKey);
   1276         } else {
   1277             values.put(Events.EVENT_COLOR_KEY, NO_EVENT_COLOR);
   1278         }
   1279         Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
   1280         mHandler.startUpdate(mHandler.getNextToken(), null, uri, values,
   1281                 null, null, Utils.UNDO_DELAY);
   1282         return true;
   1283     }
   1284 
   1285     @Override
   1286     public void onStop() {
   1287         Activity act = getActivity();
   1288         if (!mEventDeletionStarted && act != null && !act.isChangingConfigurations()) {
   1289 
   1290             boolean responseSaved = saveResponse();
   1291             boolean eventColorSaved = saveEventColor();
   1292             if (saveReminders() || responseSaved || eventColorSaved) {
   1293                 Toast.makeText(getActivity(), R.string.saving_event, Toast.LENGTH_SHORT).show();
   1294             }
   1295         }
   1296         super.onStop();
   1297     }
   1298 
   1299     @Override
   1300     public void onDestroy() {
   1301         if (mEventCursor != null) {
   1302             mEventCursor.close();
   1303         }
   1304         if (mCalendarsCursor != null) {
   1305             mCalendarsCursor.close();
   1306         }
   1307         if (mAttendeesCursor != null) {
   1308             mAttendeesCursor.close();
   1309         }
   1310         super.onDestroy();
   1311     }
   1312 
   1313     /**
   1314      * Asynchronously saves the response to an invitation if the user changed
   1315      * the response. Returns true if the database will be updated.
   1316      *
   1317      * @return true if the database will be changed
   1318      */
   1319     private boolean saveResponse() {
   1320         if (mAttendeesCursor == null || mEventCursor == null) {
   1321             return false;
   1322         }
   1323 
   1324         int status = getResponseFromButtonId(
   1325                 mResponseRadioGroup.getCheckedRadioButtonId());
   1326         if (status == Attendees.ATTENDEE_STATUS_NONE) {
   1327             return false;
   1328         }
   1329 
   1330         // If the status has not changed, then don't update the database
   1331         if (status == mOriginalAttendeeResponse) {
   1332             return false;
   1333         }
   1334 
   1335         // If we never got an owner attendee id we can't set the status
   1336         if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE) {
   1337             return false;
   1338         }
   1339 
   1340         if (!mIsRepeating) {
   1341             // This is a non-repeating event
   1342             updateResponse(mEventId, mCalendarOwnerAttendeeId, status);
   1343             mOriginalAttendeeResponse = status;
   1344             return true;
   1345         }
   1346 
   1347         if (DEBUG) {
   1348             Log.d(TAG, "Repeating event: mWhichEvents=" + mWhichEvents);
   1349         }
   1350         // This is a repeating event
   1351         switch (mWhichEvents) {
   1352             case -1:
   1353                 return false;
   1354             case UPDATE_SINGLE:
   1355                 createExceptionResponse(mEventId, status);
   1356                 mOriginalAttendeeResponse = status;
   1357                 return true;
   1358             case UPDATE_ALL:
   1359                 updateResponse(mEventId, mCalendarOwnerAttendeeId, status);
   1360                 mOriginalAttendeeResponse = status;
   1361                 return true;
   1362             default:
   1363                 Log.e(TAG, "Unexpected choice for updating invitation response");
   1364                 break;
   1365         }
   1366         return false;
   1367     }
   1368 
   1369     private void updateResponse(long eventId, long attendeeId, int status) {
   1370         // Update the attendee status in the attendees table.  the provider
   1371         // takes care of updating the self attendance status.
   1372         ContentValues values = new ContentValues();
   1373 
   1374         if (!TextUtils.isEmpty(mCalendarOwnerAccount)) {
   1375             values.put(Attendees.ATTENDEE_EMAIL, mCalendarOwnerAccount);
   1376         }
   1377         values.put(Attendees.ATTENDEE_STATUS, status);
   1378         values.put(Attendees.EVENT_ID, eventId);
   1379 
   1380         Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, attendeeId);
   1381 
   1382         mHandler.startUpdate(mHandler.getNextToken(), null, uri, values,
   1383                 null, null, Utils.UNDO_DELAY);
   1384     }
   1385 
   1386     /**
   1387      * Creates an exception to a recurring event.  The only change we're making is to the
   1388      * "self attendee status" value.  The provider will take care of updating the corresponding
   1389      * Attendees.attendeeStatus entry.
   1390      *
   1391      * @param eventId The recurring event.
   1392      * @param status The new value for selfAttendeeStatus.
   1393      */
   1394     private void createExceptionResponse(long eventId, int status) {
   1395         ContentValues values = new ContentValues();
   1396         values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
   1397         values.put(Events.SELF_ATTENDEE_STATUS, status);
   1398         values.put(Events.STATUS, Events.STATUS_CONFIRMED);
   1399 
   1400         ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
   1401         Uri exceptionUri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
   1402                 String.valueOf(eventId));
   1403         ops.add(ContentProviderOperation.newInsert(exceptionUri).withValues(values).build());
   1404 
   1405         mHandler.startBatch(mHandler.getNextToken(), null, CalendarContract.AUTHORITY, ops,
   1406                 Utils.UNDO_DELAY);
   1407    }
   1408 
   1409     public static int getResponseFromButtonId(int buttonId) {
   1410         int response;
   1411         if (buttonId == R.id.response_yes) {
   1412             response = Attendees.ATTENDEE_STATUS_ACCEPTED;
   1413         } else if (buttonId == R.id.response_maybe) {
   1414             response = Attendees.ATTENDEE_STATUS_TENTATIVE;
   1415         } else if (buttonId == R.id.response_no) {
   1416             response = Attendees.ATTENDEE_STATUS_DECLINED;
   1417         } else {
   1418             response = Attendees.ATTENDEE_STATUS_NONE;
   1419         }
   1420         return response;
   1421     }
   1422 
   1423     public static int findButtonIdForResponse(int response) {
   1424         int buttonId;
   1425         switch (response) {
   1426             case Attendees.ATTENDEE_STATUS_ACCEPTED:
   1427                 buttonId = R.id.response_yes;
   1428                 break;
   1429             case Attendees.ATTENDEE_STATUS_TENTATIVE:
   1430                 buttonId = R.id.response_maybe;
   1431                 break;
   1432             case Attendees.ATTENDEE_STATUS_DECLINED:
   1433                 buttonId = R.id.response_no;
   1434                 break;
   1435                 default:
   1436                     buttonId = -1;
   1437         }
   1438         return buttonId;
   1439     }
   1440 
   1441     private void doEdit() {
   1442         Context c = getActivity();
   1443         // This ensures that we aren't in the process of closing and have been
   1444         // unattached already
   1445         if (c != null) {
   1446             Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
   1447             Intent intent = new Intent(Intent.ACTION_EDIT, uri);
   1448             intent.setClass(mActivity, EditEventActivity.class);
   1449             intent.putExtra(EXTRA_EVENT_BEGIN_TIME, mStartMillis);
   1450             intent.putExtra(EXTRA_EVENT_END_TIME, mEndMillis);
   1451             intent.putExtra(EXTRA_EVENT_ALL_DAY, mAllDay);
   1452             intent.putExtra(EditEventActivity.EXTRA_EVENT_COLOR, mCurrentColor);
   1453             intent.putExtra(EditEventActivity.EXTRA_EVENT_REMINDERS, EventViewUtils
   1454                     .reminderItemsToReminders(mReminderViews, mReminderMinuteValues,
   1455                     mReminderMethodValues));
   1456             intent.putExtra(EVENT_EDIT_ON_LAUNCH, true);
   1457             startActivity(intent);
   1458         }
   1459     }
   1460 
   1461     private void updateEvent(View view) {
   1462         if (mEventCursor == null || view == null) {
   1463             return;
   1464         }
   1465 
   1466         Context context = view.getContext();
   1467         if (context == null) {
   1468             return;
   1469         }
   1470 
   1471         String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
   1472         if (eventName == null || eventName.length() == 0) {
   1473             eventName = getActivity().getString(R.string.no_title_label);
   1474         }
   1475 
   1476         // 3rd parties might not have specified the start/end time when firing the
   1477         // Events.CONTENT_URI intent.  Update these with values read from the db.
   1478         if (mStartMillis == 0 && mEndMillis == 0) {
   1479             mStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
   1480             mEndMillis = mEventCursor.getLong(EVENT_INDEX_DTEND);
   1481             if (mEndMillis == 0) {
   1482                 String duration = mEventCursor.getString(EVENT_INDEX_DURATION);
   1483                 if (!TextUtils.isEmpty(duration)) {
   1484                     try {
   1485                         Duration d = new Duration();
   1486                         d.parse(duration);
   1487                         long endMillis = mStartMillis + d.getMillis();
   1488                         if (endMillis >= mStartMillis) {
   1489                             mEndMillis = endMillis;
   1490                         } else {
   1491                             Log.d(TAG, "Invalid duration string: " + duration);
   1492                         }
   1493                     } catch (DateException e) {
   1494                         Log.d(TAG, "Error parsing duration string " + duration, e);
   1495                     }
   1496                 }
   1497                 if (mEndMillis == 0) {
   1498                     mEndMillis = mStartMillis;
   1499                 }
   1500             }
   1501         }
   1502 
   1503         mAllDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
   1504         String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
   1505         String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
   1506         String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
   1507         String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
   1508 
   1509         mHeadlines.setBackgroundColor(mCurrentColor);
   1510 
   1511         // What
   1512         if (eventName != null) {
   1513             setTextCommon(view, R.id.title, eventName);
   1514         }
   1515 
   1516         // When
   1517         // Set the date and repeats (if any)
   1518         String localTimezone = Utils.getTimeZone(mActivity, mTZUpdater);
   1519 
   1520         Resources resources = context.getResources();
   1521         String displayedDatetime = Utils.getDisplayedDatetime(mStartMillis, mEndMillis,
   1522                 System.currentTimeMillis(), localTimezone, mAllDay, context);
   1523 
   1524         String displayedTimezone = null;
   1525         if (!mAllDay) {
   1526             displayedTimezone = Utils.getDisplayedTimezone(mStartMillis, localTimezone,
   1527                     eventTimezone);
   1528         }
   1529         // Display the datetime.  Make the timezone (if any) transparent.
   1530         if (displayedTimezone == null) {
   1531             setTextCommon(view, R.id.when_datetime, displayedDatetime);
   1532         } else {
   1533             int timezoneIndex = displayedDatetime.length();
   1534             displayedDatetime += "  " + displayedTimezone;
   1535             SpannableStringBuilder sb = new SpannableStringBuilder(displayedDatetime);
   1536             ForegroundColorSpan transparentColorSpan = new ForegroundColorSpan(
   1537                     resources.getColor(R.color.event_info_headline_transparent_color));
   1538             sb.setSpan(transparentColorSpan, timezoneIndex, displayedDatetime.length(),
   1539                     Spannable.SPAN_INCLUSIVE_INCLUSIVE);
   1540             setTextCommon(view, R.id.when_datetime, sb);
   1541         }
   1542 
   1543         // Display the repeat string (if any)
   1544         String repeatString = null;
   1545         if (!TextUtils.isEmpty(rRule)) {
   1546             EventRecurrence eventRecurrence = new EventRecurrence();
   1547             eventRecurrence.parse(rRule);
   1548             Time date = new Time(localTimezone);
   1549             date.set(mStartMillis);
   1550             if (mAllDay) {
   1551                 date.timezone = Time.TIMEZONE_UTC;
   1552             }
   1553             eventRecurrence.setStartDate(date);
   1554             repeatString = EventRecurrenceFormatter.getRepeatString(mContext, resources,
   1555                     eventRecurrence, true);
   1556         }
   1557         if (repeatString == null) {
   1558             view.findViewById(R.id.when_repeat).setVisibility(View.GONE);
   1559         } else {
   1560             setTextCommon(view, R.id.when_repeat, repeatString);
   1561         }
   1562 
   1563         // Organizer view is setup in the updateCalendar method
   1564 
   1565 
   1566         // Where
   1567         if (location == null || location.trim().length() == 0) {
   1568             setVisibilityCommon(view, R.id.where, View.GONE);
   1569         } else {
   1570             final TextView textView = mWhere;
   1571             if (textView != null) {
   1572                 textView.setAutoLinkMask(0);
   1573                 textView.setText(location.trim());
   1574                 try {
   1575                     textView.setText(Utils.extendedLinkify(textView.getText().toString(), true));
   1576 
   1577                     // Linkify.addLinks() sets the TextView movement method if it finds any links.
   1578                     // We must do the same here, in case linkify by itself did not find any.
   1579                     // (This is cloned from Linkify.addLinkMovementMethod().)
   1580                     MovementMethod mm = textView.getMovementMethod();
   1581                     if ((mm == null) || !(mm instanceof LinkMovementMethod)) {
   1582                         if (textView.getLinksClickable()) {
   1583                             textView.setMovementMethod(LinkMovementMethod.getInstance());
   1584                         }
   1585                     }
   1586                 } catch (Exception ex) {
   1587                     // unexpected
   1588                     Log.e(TAG, "Linkification failed", ex);
   1589                 }
   1590 
   1591                 textView.setOnTouchListener(new OnTouchListener() {
   1592                     @Override
   1593                     public boolean onTouch(View v, MotionEvent event) {
   1594                         try {
   1595                             return v.onTouchEvent(event);
   1596                         } catch (ActivityNotFoundException e) {
   1597                             // ignore
   1598                             return true;
   1599                         }
   1600                     }
   1601                 });
   1602             }
   1603         }
   1604 
   1605         // Description
   1606         if (description != null && description.length() != 0) {
   1607             mDesc.setText(description);
   1608         }
   1609 
   1610         // Launch Custom App
   1611         if (Utils.isJellybeanOrLater()) {
   1612             updateCustomAppButton();
   1613         }
   1614     }
   1615 
   1616     private void updateCustomAppButton() {
   1617         buttonSetup: {
   1618             final Button launchButton = (Button) mView.findViewById(R.id.launch_custom_app_button);
   1619             if (launchButton == null)
   1620                 break buttonSetup;
   1621 
   1622             final String customAppPackage = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_PACKAGE);
   1623             final String customAppUri = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_URI);
   1624 
   1625             if (TextUtils.isEmpty(customAppPackage) || TextUtils.isEmpty(customAppUri))
   1626                 break buttonSetup;
   1627 
   1628             PackageManager pm = mContext.getPackageManager();
   1629             if (pm == null)
   1630                 break buttonSetup;
   1631 
   1632             ApplicationInfo info;
   1633             try {
   1634                 info = pm.getApplicationInfo(customAppPackage, 0);
   1635                 if (info == null)
   1636                     break buttonSetup;
   1637             } catch (NameNotFoundException e) {
   1638                 break buttonSetup;
   1639             }
   1640 
   1641             Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
   1642             final Intent intent = new Intent(CalendarContract.ACTION_HANDLE_CUSTOM_EVENT, uri);
   1643             intent.setPackage(customAppPackage);
   1644             intent.putExtra(CalendarContract.EXTRA_CUSTOM_APP_URI, customAppUri);
   1645             intent.putExtra(EXTRA_EVENT_BEGIN_TIME, mStartMillis);
   1646 
   1647             // See if we have a taker for our intent
   1648             if (pm.resolveActivity(intent, 0) == null)
   1649                 break buttonSetup;
   1650 
   1651             Drawable icon = pm.getApplicationIcon(info);
   1652             if (icon != null) {
   1653 
   1654                 Drawable[] d = launchButton.getCompoundDrawables();
   1655                 icon.setBounds(0, 0, mCustomAppIconSize, mCustomAppIconSize);
   1656                 launchButton.setCompoundDrawables(icon, d[1], d[2], d[3]);
   1657             }
   1658 
   1659             CharSequence label = pm.getApplicationLabel(info);
   1660             if (label != null && label.length() != 0) {
   1661                 launchButton.setText(label);
   1662             } else if (icon == null) {
   1663                 // No icon && no label. Hide button?
   1664                 break buttonSetup;
   1665             }
   1666 
   1667             // Launch custom app
   1668             launchButton.setOnClickListener(new View.OnClickListener() {
   1669                 @Override
   1670                 public void onClick(View v) {
   1671                     try {
   1672                         startActivityForResult(intent, 0);
   1673                     } catch (ActivityNotFoundException e) {
   1674                         // Shouldn't happen as we checked it already
   1675                         setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
   1676                     }
   1677                 }
   1678             });
   1679 
   1680             setVisibilityCommon(mView, R.id.launch_custom_app_container, View.VISIBLE);
   1681             return;
   1682 
   1683         }
   1684 
   1685         setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
   1686         return;
   1687     }
   1688 
   1689     private void sendAccessibilityEvent() {
   1690         AccessibilityManager am =
   1691             (AccessibilityManager) getActivity().getSystemService(Service.ACCESSIBILITY_SERVICE);
   1692         if (!am.isEnabled()) {
   1693             return;
   1694         }
   1695 
   1696         AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
   1697         event.setClassName(getClass().getName());
   1698         event.setPackageName(getActivity().getPackageName());
   1699         List<CharSequence> text = event.getText();
   1700 
   1701         addFieldToAccessibilityEvent(text, mTitle, null);
   1702         addFieldToAccessibilityEvent(text, mWhenDateTime, null);
   1703         addFieldToAccessibilityEvent(text, mWhere, null);
   1704         addFieldToAccessibilityEvent(text, null, mDesc);
   1705 
   1706         if (mResponseRadioGroup.getVisibility() == View.VISIBLE) {
   1707             int id = mResponseRadioGroup.getCheckedRadioButtonId();
   1708             if (id != View.NO_ID) {
   1709                 text.add(((TextView) getView().findViewById(R.id.response_label)).getText());
   1710                 text.add((((RadioButton) (mResponseRadioGroup.findViewById(id)))
   1711                         .getText() + PERIOD_SPACE));
   1712             }
   1713         }
   1714 
   1715         am.sendAccessibilityEvent(event);
   1716     }
   1717 
   1718     private void addFieldToAccessibilityEvent(List<CharSequence> text, TextView tv,
   1719             ExpandableTextView etv) {
   1720         CharSequence cs;
   1721         if (tv != null) {
   1722             cs = tv.getText();
   1723         } else if (etv != null) {
   1724             cs = etv.getText();
   1725         } else {
   1726             return;
   1727         }
   1728 
   1729         if (!TextUtils.isEmpty(cs)) {
   1730             cs = cs.toString().trim();
   1731             if (cs.length() > 0) {
   1732                 text.add(cs);
   1733                 text.add(PERIOD_SPACE);
   1734             }
   1735         }
   1736     }
   1737 
   1738     private void updateCalendar(View view) {
   1739 
   1740         mCalendarOwnerAccount = "";
   1741         if (mCalendarsCursor != null && mEventCursor != null) {
   1742             mCalendarsCursor.moveToFirst();
   1743             String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
   1744             mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount;
   1745             mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0;
   1746             mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME);
   1747 
   1748             // start visible calendars query
   1749             mHandler.startQuery(TOKEN_QUERY_VISIBLE_CALENDARS, null, Calendars.CONTENT_URI,
   1750                     CALENDARS_PROJECTION, CALENDARS_VISIBLE_WHERE, new String[] {"1"}, null);
   1751 
   1752             mEventOrganizerEmail = mEventCursor.getString(EVENT_INDEX_ORGANIZER);
   1753             mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(mEventOrganizerEmail);
   1754 
   1755             if (!TextUtils.isEmpty(mEventOrganizerEmail) &&
   1756                     !mEventOrganizerEmail.endsWith(Utils.MACHINE_GENERATED_ADDRESS)) {
   1757                 mEventOrganizerDisplayName = mEventOrganizerEmail;
   1758             }
   1759 
   1760             if (!mIsOrganizer && !TextUtils.isEmpty(mEventOrganizerDisplayName)) {
   1761                 setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName);
   1762                 setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE);
   1763             } else {
   1764                 setVisibilityCommon(view, R.id.organizer_container, View.GONE);
   1765             }
   1766             mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
   1767             mCanModifyCalendar = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL)
   1768                     >= Calendars.CAL_ACCESS_CONTRIBUTOR;
   1769             // TODO add "|| guestCanModify" after b/1299071 is fixed
   1770             mCanModifyEvent = mCanModifyCalendar && mIsOrganizer;
   1771             mIsBusyFreeCalendar =
   1772                     mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.CAL_ACCESS_FREEBUSY;
   1773 
   1774             if (!mIsBusyFreeCalendar) {
   1775 
   1776                 View b = mView.findViewById(R.id.edit);
   1777                 b.setEnabled(true);
   1778                 b.setOnClickListener(new OnClickListener() {
   1779                     @Override
   1780                     public void onClick(View v) {
   1781                         doEdit();
   1782                         // For dialogs, just close the fragment
   1783                         // For full screen, close activity on phone, leave it for tablet
   1784                         if (mIsDialog) {
   1785                             EventInfoFragment.this.dismiss();
   1786                         }
   1787                         else if (!mIsTabletConfig){
   1788                             getActivity().finish();
   1789                         }
   1790                     }
   1791                 });
   1792             }
   1793             View button;
   1794             if (mCanModifyCalendar) {
   1795                 button = mView.findViewById(R.id.delete);
   1796                 if (button != null) {
   1797                     button.setEnabled(true);
   1798                     button.setVisibility(View.VISIBLE);
   1799                 }
   1800             }
   1801             if (mCanModifyEvent) {
   1802                 button = mView.findViewById(R.id.edit);
   1803                 if (button != null) {
   1804                     button.setEnabled(true);
   1805                     button.setVisibility(View.VISIBLE);
   1806                 }
   1807             }
   1808             if ((!mIsDialog && !mIsTabletConfig ||
   1809                     mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) && mMenu != null) {
   1810                 mActivity.invalidateOptionsMenu();
   1811             }
   1812         } else {
   1813             setVisibilityCommon(view, R.id.calendar, View.GONE);
   1814             sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS);
   1815         }
   1816     }
   1817 
   1818     /**
   1819      *
   1820      */
   1821     private void updateMenu() {
   1822         if (mMenu == null) {
   1823             return;
   1824         }
   1825         MenuItem delete = mMenu.findItem(R.id.info_action_delete);
   1826         MenuItem edit = mMenu.findItem(R.id.info_action_edit);
   1827         MenuItem changeColor = mMenu.findItem(R.id.info_action_change_color);
   1828         if (delete != null) {
   1829             delete.setVisible(mCanModifyCalendar);
   1830             delete.setEnabled(mCanModifyCalendar);
   1831         }
   1832         if (edit != null) {
   1833             edit.setVisible(mCanModifyEvent);
   1834             edit.setEnabled(mCanModifyEvent);
   1835         }
   1836         if (changeColor != null && mColors != null && mColors.length > 0) {
   1837             changeColor.setVisible(mCanModifyCalendar);
   1838             changeColor.setEnabled(mCanModifyCalendar);
   1839         }
   1840     }
   1841 
   1842     private void updateAttendees(View view) {
   1843         if (mAcceptedAttendees.size() + mDeclinedAttendees.size() +
   1844                 mTentativeAttendees.size() + mNoResponseAttendees.size() > 0) {
   1845             mLongAttendees.clearAttendees();
   1846             (mLongAttendees).addAttendees(mAcceptedAttendees);
   1847             (mLongAttendees).addAttendees(mDeclinedAttendees);
   1848             (mLongAttendees).addAttendees(mTentativeAttendees);
   1849             (mLongAttendees).addAttendees(mNoResponseAttendees);
   1850             mLongAttendees.setEnabled(false);
   1851             mLongAttendees.setVisibility(View.VISIBLE);
   1852         } else {
   1853             mLongAttendees.setVisibility(View.GONE);
   1854         }
   1855 
   1856         if (hasEmailableAttendees()) {
   1857             setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE);
   1858             if (emailAttendeesButton != null) {
   1859                 emailAttendeesButton.setText(R.string.email_guests_label);
   1860             }
   1861         } else if (hasEmailableOrganizer()) {
   1862             setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE);
   1863             if (emailAttendeesButton != null) {
   1864                 emailAttendeesButton.setText(R.string.email_organizer_label);
   1865             }
   1866         } else {
   1867             setVisibilityCommon(mView, R.id.email_attendees_container, View.GONE);
   1868         }
   1869     }
   1870 
   1871     /**
   1872      * Returns true if there is at least 1 attendee that is not the viewer.
   1873      */
   1874     private boolean hasEmailableAttendees() {
   1875         for (Attendee attendee : mAcceptedAttendees) {
   1876             if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
   1877                 return true;
   1878             }
   1879         }
   1880         for (Attendee attendee : mTentativeAttendees) {
   1881             if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
   1882                 return true;
   1883             }
   1884         }
   1885         for (Attendee attendee : mNoResponseAttendees) {
   1886             if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
   1887                 return true;
   1888             }
   1889         }
   1890         for (Attendee attendee : mDeclinedAttendees) {
   1891             if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
   1892                 return true;
   1893             }
   1894         }
   1895         return false;
   1896     }
   1897 
   1898     private boolean hasEmailableOrganizer() {
   1899         return mEventOrganizerEmail != null &&
   1900                 Utils.isEmailableFrom(mEventOrganizerEmail, mSyncAccountName);
   1901     }
   1902 
   1903     public void initReminders(View view, Cursor cursor) {
   1904 
   1905         // Add reminders
   1906         mOriginalReminders.clear();
   1907         mUnsupportedReminders.clear();
   1908         while (cursor.moveToNext()) {
   1909             int minutes = cursor.getInt(EditEventHelper.REMINDERS_INDEX_MINUTES);
   1910             int method = cursor.getInt(EditEventHelper.REMINDERS_INDEX_METHOD);
   1911 
   1912             if (method != Reminders.METHOD_DEFAULT && !mReminderMethodValues.contains(method)) {
   1913                 // Stash unsupported reminder types separately so we don't alter
   1914                 // them in the UI
   1915                 mUnsupportedReminders.add(ReminderEntry.valueOf(minutes, method));
   1916             } else {
   1917                 mOriginalReminders.add(ReminderEntry.valueOf(minutes, method));
   1918             }
   1919         }
   1920         // Sort appropriately for display (by time, then type)
   1921         Collections.sort(mOriginalReminders);
   1922 
   1923         if (mUserModifiedReminders) {
   1924             // If the user has changed the list of reminders don't change what's
   1925             // shown.
   1926             return;
   1927         }
   1928 
   1929         LinearLayout parent = (LinearLayout) mScrollView
   1930                 .findViewById(R.id.reminder_items_container);
   1931         if (parent != null) {
   1932             parent.removeAllViews();
   1933         }
   1934         if (mReminderViews != null) {
   1935             mReminderViews.clear();
   1936         }
   1937 
   1938         if (mHasAlarm) {
   1939             ArrayList<ReminderEntry> reminders;
   1940             // If applicable, use reminders saved in the bundle.
   1941             if (mReminders != null) {
   1942                 reminders = mReminders;
   1943             } else {
   1944                 reminders = mOriginalReminders;
   1945             }
   1946             // Insert any minute values that aren't represented in the minutes list.
   1947             for (ReminderEntry re : reminders) {
   1948                 EventViewUtils.addMinutesToList(
   1949                         mActivity, mReminderMinuteValues, mReminderMinuteLabels, re.getMinutes());
   1950             }
   1951             // Create a UI element for each reminder.  We display all of the reminders we get
   1952             // from the provider, even if the count exceeds the calendar maximum.  (Also, for
   1953             // a new event, we won't have a maxReminders value available.)
   1954             for (ReminderEntry re : reminders) {
   1955                 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
   1956                         mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
   1957                         mReminderMethodLabels, re, Integer.MAX_VALUE, mReminderChangeListener);
   1958             }
   1959             EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
   1960             // TODO show unsupported reminder types in some fashion.
   1961         }
   1962     }
   1963 
   1964     void updateResponse(View view) {
   1965         // we only let the user accept/reject/etc. a meeting if:
   1966         // a) you can edit the event's containing calendar AND
   1967         // b) you're not the organizer and only attendee AND
   1968         // c) organizerCanRespond is enabled for the calendar
   1969         // (if the attendee data has been hidden, the visible number of attendees
   1970         // will be 1 -- the calendar owner's).
   1971         // (there are more cases involved to be 100% accurate, such as
   1972         // paying attention to whether or not an attendee status was
   1973         // included in the feed, but we're currently omitting those corner cases
   1974         // for simplicity).
   1975 
   1976         // TODO Switch to EditEventHelper.canRespond when this class uses CalendarEventModel.
   1977         if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) ||
   1978                 (mIsOrganizer && !mOwnerCanRespond)) {
   1979             setVisibilityCommon(view, R.id.response_container, View.GONE);
   1980             return;
   1981         }
   1982 
   1983         setVisibilityCommon(view, R.id.response_container, View.VISIBLE);
   1984 
   1985 
   1986         int response;
   1987         if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
   1988             response = mTentativeUserSetResponse;
   1989         } else if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
   1990             response = mUserSetResponse;
   1991         } else if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) {
   1992             response = mAttendeeResponseFromIntent;
   1993         } else {
   1994             response = mOriginalAttendeeResponse;
   1995         }
   1996 
   1997         int buttonToCheck = findButtonIdForResponse(response);
   1998         mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons
   1999         mResponseRadioGroup.setOnCheckedChangeListener(this);
   2000     }
   2001 
   2002     private void setTextCommon(View view, int id, CharSequence text) {
   2003         TextView textView = (TextView) view.findViewById(id);
   2004         if (textView == null)
   2005             return;
   2006         textView.setText(text);
   2007     }
   2008 
   2009     private void setVisibilityCommon(View view, int id, int visibility) {
   2010         View v = view.findViewById(id);
   2011         if (v != null) {
   2012             v.setVisibility(visibility);
   2013         }
   2014         return;
   2015     }
   2016 
   2017     /**
   2018      * Taken from com.google.android.gm.HtmlConversationActivity
   2019      *
   2020      * Send the intent that shows the Contact info corresponding to the email address.
   2021      */
   2022     public void showContactInfo(Attendee attendee, Rect rect) {
   2023         // First perform lookup query to find existing contact
   2024         final ContentResolver resolver = getActivity().getContentResolver();
   2025         final String address = attendee.mEmail;
   2026         final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI,
   2027                 Uri.encode(address));
   2028         final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri);
   2029 
   2030         if (lookupUri != null) {
   2031             // Found matching contact, trigger QuickContact
   2032             QuickContact.showQuickContact(getActivity(), rect, lookupUri,
   2033                     QuickContact.MODE_MEDIUM, null);
   2034         } else {
   2035             // No matching contact, ask user to create one
   2036             final Uri mailUri = Uri.fromParts("mailto", address, null);
   2037             final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri);
   2038 
   2039             // Pass along full E-mail string for possible create dialog
   2040             Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null);
   2041             intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString());
   2042 
   2043             // Only provide personal name hint if we have one
   2044             final String senderPersonal = attendee.mName;
   2045             if (!TextUtils.isEmpty(senderPersonal)) {
   2046                 intent.putExtra(Intents.Insert.NAME, senderPersonal);
   2047             }
   2048 
   2049             startActivity(intent);
   2050         }
   2051     }
   2052 
   2053     @Override
   2054     public void onPause() {
   2055         mIsPaused = true;
   2056         mHandler.removeCallbacks(onDeleteRunnable);
   2057         super.onPause();
   2058         // Remove event deletion alert box since it is being rebuild in the OnResume
   2059         // This is done to get the same behavior on OnResume since the AlertDialog is gone on
   2060         // rotation but not if you press the HOME key
   2061         if (mDeleteDialogVisible && mDeleteHelper != null) {
   2062             mDeleteHelper.dismissAlertDialog();
   2063             mDeleteHelper = null;
   2064         }
   2065         if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE
   2066                 && mEditResponseHelper != null) {
   2067             mEditResponseHelper.dismissAlertDialog();
   2068         }
   2069     }
   2070 
   2071     @Override
   2072     public void onResume() {
   2073         super.onResume();
   2074         if (mIsDialog) {
   2075             setDialogSize(getActivity().getResources());
   2076             applyDialogParams();
   2077         }
   2078         mIsPaused = false;
   2079         if (mDismissOnResume) {
   2080             mHandler.post(onDeleteRunnable);
   2081         }
   2082         // Display the "delete confirmation" or "edit response helper" dialog if needed
   2083         if (mDeleteDialogVisible) {
   2084             mDeleteHelper = new DeleteEventHelper(
   2085                     mContext, mActivity,
   2086                     !mIsDialog && !mIsTabletConfig /* exitWhenDone */);
   2087             mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener());
   2088             mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
   2089         } else if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
   2090             int buttonId = findButtonIdForResponse(mTentativeUserSetResponse);
   2091             mResponseRadioGroup.check(buttonId);
   2092             mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents());
   2093         }
   2094     }
   2095 
   2096     @Override
   2097     public void eventsChanged() {
   2098     }
   2099 
   2100     @Override
   2101     public long getSupportedEventTypes() {
   2102         return EventType.EVENTS_CHANGED;
   2103     }
   2104 
   2105     @Override
   2106     public void handleEvent(EventInfo event) {
   2107         reloadEvents();
   2108     }
   2109 
   2110     public void reloadEvents() {
   2111         if (mHandler != null) {
   2112             mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
   2113                     null, null, null);
   2114         }
   2115     }
   2116 
   2117     @Override
   2118     public void onClick(View view) {
   2119 
   2120         // This must be a click on one of the "remove reminder" buttons
   2121         LinearLayout reminderItem = (LinearLayout) view.getParent();
   2122         LinearLayout parent = (LinearLayout) reminderItem.getParent();
   2123         parent.removeView(reminderItem);
   2124         mReminderViews.remove(reminderItem);
   2125         mUserModifiedReminders = true;
   2126         EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
   2127     }
   2128 
   2129 
   2130     /**
   2131      * Add a new reminder when the user hits the "add reminder" button.  We use the default
   2132      * reminder time and method.
   2133      */
   2134     private void addReminder() {
   2135         // TODO: when adding a new reminder, make it different from the
   2136         // last one in the list (if any).
   2137         if (mDefaultReminderMinutes == GeneralPreferences.NO_REMINDER) {
   2138             EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
   2139                     mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
   2140                     mReminderMethodLabels,
   2141                     ReminderEntry.valueOf(GeneralPreferences.REMINDER_DEFAULT_TIME), mMaxReminders,
   2142                     mReminderChangeListener);
   2143         } else {
   2144             EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
   2145                     mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
   2146                     mReminderMethodLabels, ReminderEntry.valueOf(mDefaultReminderMinutes),
   2147                     mMaxReminders, mReminderChangeListener);
   2148         }
   2149 
   2150         EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
   2151     }
   2152 
   2153     synchronized private void prepareReminders() {
   2154         // Nothing to do if we've already built these lists _and_ we aren't
   2155         // removing not allowed methods
   2156         if (mReminderMinuteValues != null && mReminderMinuteLabels != null
   2157                 && mReminderMethodValues != null && mReminderMethodLabels != null
   2158                 && mCalendarAllowedReminders == null) {
   2159             return;
   2160         }
   2161         // Load the labels and corresponding numeric values for the minutes and methods lists
   2162         // from the assets.  If we're switching calendars, we need to clear and re-populate the
   2163         // lists (which may have elements added and removed based on calendar properties).  This
   2164         // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a
   2165         // new event that aren't in the default set.
   2166         Resources r = mActivity.getResources();
   2167         mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values);
   2168         mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels);
   2169         mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values);
   2170         mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels);
   2171 
   2172         // Remove any reminder methods that aren't allowed for this calendar.  If this is
   2173         // a new event, mCalendarAllowedReminders may not be set the first time we're called.
   2174         if (mCalendarAllowedReminders != null) {
   2175             EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels,
   2176                     mCalendarAllowedReminders);
   2177         }
   2178         if (mView != null) {
   2179             mView.invalidate();
   2180         }
   2181     }
   2182 
   2183 
   2184     private boolean saveReminders() {
   2185         ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3);
   2186 
   2187         // Read reminders from UI
   2188         mReminders = EventViewUtils.reminderItemsToReminders(mReminderViews,
   2189                 mReminderMinuteValues, mReminderMethodValues);
   2190         mOriginalReminders.addAll(mUnsupportedReminders);
   2191         Collections.sort(mOriginalReminders);
   2192         mReminders.addAll(mUnsupportedReminders);
   2193         Collections.sort(mReminders);
   2194 
   2195         // Check if there are any changes in the reminder
   2196         boolean changed = EditEventHelper.saveReminders(ops, mEventId, mReminders,
   2197                 mOriginalReminders, false /* no force save */);
   2198 
   2199         if (!changed) {
   2200             return false;
   2201         }
   2202 
   2203         // save new reminders
   2204         AsyncQueryService service = new AsyncQueryService(getActivity());
   2205         service.startBatch(0, null, Calendars.CONTENT_URI.getAuthority(), ops, 0);
   2206         mOriginalReminders = mReminders;
   2207         // Update the "hasAlarm" field for the event
   2208         Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
   2209         int len = mReminders.size();
   2210         boolean hasAlarm = len > 0;
   2211         if (hasAlarm != mHasAlarm) {
   2212             ContentValues values = new ContentValues();
   2213             values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0);
   2214             service.startUpdate(0, null, uri, values, null, null, 0);
   2215         }
   2216         return true;
   2217     }
   2218 
   2219     /**
   2220      * Email all the attendees of the event, except for the viewer (so as to not email
   2221      * himself) and resources like conference rooms.
   2222      */
   2223     private void emailAttendees() {
   2224         Intent i = new Intent(getActivity(), QuickResponseActivity.class);
   2225         i.putExtra(QuickResponseActivity.EXTRA_EVENT_ID, mEventId);
   2226         i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   2227         startActivity(i);
   2228     }
   2229 
   2230     /**
   2231      * Loads an integer array asset into a list.
   2232      */
   2233     private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) {
   2234         int[] vals = r.getIntArray(resNum);
   2235         int size = vals.length;
   2236         ArrayList<Integer> list = new ArrayList<Integer>(size);
   2237 
   2238         for (int i = 0; i < size; i++) {
   2239             list.add(vals[i]);
   2240         }
   2241 
   2242         return list;
   2243     }
   2244     /**
   2245      * Loads a String array asset into a list.
   2246      */
   2247     private static ArrayList<String> loadStringArray(Resources r, int resNum) {
   2248         String[] labels = r.getStringArray(resNum);
   2249         ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels));
   2250         return list;
   2251     }
   2252 
   2253     @Override
   2254     public void onDeleteStarted() {
   2255         mEventDeletionStarted = true;
   2256     }
   2257 
   2258     private Dialog.OnDismissListener createDeleteOnDismissListener() {
   2259         return new Dialog.OnDismissListener() {
   2260                     @Override
   2261                     public void onDismiss(DialogInterface dialog) {
   2262                         // Since OnPause will force the dialog to dismiss , do
   2263                         // not change the dialog status
   2264                         if (!mIsPaused) {
   2265                             mDeleteDialogVisible = false;
   2266                         }
   2267                     }
   2268                 };
   2269     }
   2270 
   2271     public long getEventId() {
   2272         return mEventId;
   2273     }
   2274 
   2275     public long getStartMillis() {
   2276         return mStartMillis;
   2277     }
   2278     public long getEndMillis() {
   2279         return mEndMillis;
   2280     }
   2281     private void setDialogSize(Resources r) {
   2282         mDialogWidth = (int)r.getDimension(R.dimen.event_info_dialog_width);
   2283         mDialogHeight = (int)r.getDimension(R.dimen.event_info_dialog_height);
   2284     }
   2285 
   2286     @Override
   2287     public void onColorSelected(int color) {
   2288         mCurrentColor = color;
   2289         mCurrentColorKey = mDisplayColorKeyMap.get(color);
   2290         mHeadlines.setBackgroundColor(color);
   2291     }
   2292 }
   2293