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 View mErrorMsgView;
    362     private ObjectAnimator mAnimateAlpha;
    363     private long mLoadingMsgStartTime;
    364 
    365     private EventColorPickerDialog mColorPickerDialog;
    366     private SparseIntArray mDisplayColorKeyMap = new SparseIntArray();
    367     private int[] mColors;
    368     private int mOriginalColor = -1;
    369     private boolean mOriginalColorInitialized = false;
    370     private int mCalendarColor = -1;
    371     private boolean mCalendarColorInitialized = false;
    372     private int mCurrentColor = -1;
    373     private boolean mCurrentColorInitialized = false;
    374     private int mCurrentColorKey = -1;
    375 
    376     private static final int FADE_IN_TIME = 300;   // in milliseconds
    377     private static final int LOADING_MSG_DELAY = 600;   // in milliseconds
    378     private static final int LOADING_MSG_MIN_DISPLAY_TIME = 600;
    379     private boolean mNoCrossFade = false;  // Used to prevent repeated cross-fade
    380     private RadioGroup mResponseRadioGroup;
    381 
    382     ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>();
    383     ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>();
    384     ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>();
    385     ArrayList<Attendee> mNoResponseAttendees = new ArrayList<Attendee>();
    386     ArrayList<String> mToEmails = new ArrayList<String>();
    387     ArrayList<String> mCcEmails = new ArrayList<String>();
    388 
    389     private int mDefaultReminderMinutes;
    390     private final ArrayList<LinearLayout> mReminderViews = new ArrayList<LinearLayout>(0);
    391     public ArrayList<ReminderEntry> mReminders;
    392     public ArrayList<ReminderEntry> mOriginalReminders = new ArrayList<ReminderEntry>();
    393     public ArrayList<ReminderEntry> mUnsupportedReminders = new ArrayList<ReminderEntry>();
    394     private boolean mUserModifiedReminders = false;
    395 
    396     /**
    397      * Contents of the "minutes" spinner.  This has default values from the XML file, augmented
    398      * with any additional values that were already associated with the event.
    399      */
    400     private ArrayList<Integer> mReminderMinuteValues;
    401     private ArrayList<String> mReminderMinuteLabels;
    402 
    403     /**
    404      * Contents of the "methods" spinner.  The "values" list specifies the method constant
    405      * (e.g. {@link Reminders#METHOD_ALERT}) associated with the labels.  Any methods that
    406      * aren't allowed by the Calendar will be removed.
    407      */
    408     private ArrayList<Integer> mReminderMethodValues;
    409     private ArrayList<String> mReminderMethodLabels;
    410 
    411     private QueryHandler mHandler;
    412 
    413 
    414     private final Runnable mTZUpdater = new Runnable() {
    415         @Override
    416         public void run() {
    417             updateEvent(mView);
    418         }
    419     };
    420 
    421     private final Runnable mLoadingMsgAlphaUpdater = new Runnable() {
    422         @Override
    423         public void run() {
    424             // Since this is run after a delay, make sure to only show the message
    425             // if the event's data is not shown yet.
    426             if (!mAnimateAlpha.isRunning() && mScrollView.getAlpha() == 0) {
    427                 mLoadingMsgStartTime = System.currentTimeMillis();
    428                 mLoadingMsgView.setAlpha(1);
    429             }
    430         }
    431     };
    432 
    433     private OnItemSelectedListener mReminderChangeListener;
    434 
    435     private static int mDialogWidth = 500;
    436     private static int mDialogHeight = 600;
    437     private static int DIALOG_TOP_MARGIN = 8;
    438     private boolean mIsDialog = false;
    439     private boolean mIsPaused = true;
    440     private boolean mDismissOnResume = false;
    441     private int mX = -1;
    442     private int mY = -1;
    443     private int mMinTop;         // Dialog cannot be above this location
    444     private boolean mIsTabletConfig;
    445     private Activity mActivity;
    446     private Context mContext;
    447 
    448     private CalendarController mController;
    449 
    450     private class QueryHandler extends AsyncQueryService {
    451         public QueryHandler(Context context) {
    452             super(context);
    453         }
    454 
    455         @Override
    456         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    457             // if the activity is finishing, then close the cursor and return
    458             final Activity activity = getActivity();
    459             if (activity == null || activity.isFinishing()) {
    460                 if (cursor != null) {
    461                     cursor.close();
    462                 }
    463                 return;
    464             }
    465 
    466             switch (token) {
    467             case TOKEN_QUERY_EVENT:
    468                 mEventCursor = Utils.matrixCursorFromCursor(cursor);
    469                 if (!initEventCursor()) {
    470                     displayEventNotFound();
    471                     return;
    472                 }
    473                 if (!mCalendarColorInitialized) {
    474                     mCalendarColor = Utils.getDisplayColorFromColor(
    475                             mEventCursor.getInt(EVENT_INDEX_CALENDAR_COLOR));
    476                     mCalendarColorInitialized = true;
    477                 }
    478 
    479                 if (!mOriginalColorInitialized) {
    480                     mOriginalColor = mEventCursor.isNull(EVENT_INDEX_EVENT_COLOR)
    481                             ? mCalendarColor : Utils.getDisplayColorFromColor(
    482                                     mEventCursor.getInt(EVENT_INDEX_EVENT_COLOR));
    483                     mOriginalColorInitialized = true;
    484                 }
    485 
    486                 if (!mCurrentColorInitialized) {
    487                     mCurrentColor = mOriginalColor;
    488                     mCurrentColorInitialized = true;
    489                 }
    490 
    491                 updateEvent(mView);
    492                 prepareReminders();
    493 
    494                 // start calendar query
    495                 Uri uri = Calendars.CONTENT_URI;
    496                 String[] args = new String[] {
    497                         Long.toString(mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID))};
    498                 startQuery(TOKEN_QUERY_CALENDARS, null, uri, CALENDARS_PROJECTION,
    499                         CALENDARS_WHERE, args, null);
    500                 break;
    501             case TOKEN_QUERY_CALENDARS:
    502                 mCalendarsCursor = Utils.matrixCursorFromCursor(cursor);
    503                 updateCalendar(mView);
    504                 // FRAG_TODO fragments shouldn't set the title anymore
    505                 updateTitle();
    506 
    507                 args = new String[] {
    508                         mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME),
    509                         mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_TYPE) };
    510                 uri = Colors.CONTENT_URI;
    511                 startQuery(TOKEN_QUERY_COLORS, null, uri, COLORS_PROJECTION, COLORS_WHERE, args,
    512                         null);
    513 
    514                 if (!mIsBusyFreeCalendar) {
    515                     args = new String[] { Long.toString(mEventId) };
    516 
    517                     // start attendees query
    518                     uri = Attendees.CONTENT_URI;
    519                     startQuery(TOKEN_QUERY_ATTENDEES, null, uri, ATTENDEES_PROJECTION,
    520                             ATTENDEES_WHERE, args, ATTENDEES_SORT_ORDER);
    521                 } else {
    522                     sendAccessibilityEventIfQueryDone(TOKEN_QUERY_ATTENDEES);
    523                 }
    524                 if (mHasAlarm) {
    525                     // start reminders query
    526                     args = new String[] { Long.toString(mEventId) };
    527                     uri = Reminders.CONTENT_URI;
    528                     startQuery(TOKEN_QUERY_REMINDERS, null, uri,
    529                             REMINDERS_PROJECTION, REMINDERS_WHERE, args, null);
    530                 } else {
    531                     sendAccessibilityEventIfQueryDone(TOKEN_QUERY_REMINDERS);
    532                 }
    533                 break;
    534             case TOKEN_QUERY_COLORS:
    535                 ArrayList<Integer> colors = new ArrayList<Integer>();
    536                 if (cursor.moveToFirst()) {
    537                     do
    538                     {
    539                         int colorKey = cursor.getInt(COLORS_INDEX_COLOR_KEY);
    540                         int rawColor = cursor.getInt(COLORS_INDEX_COLOR);
    541                         int displayColor = Utils.getDisplayColorFromColor(rawColor);
    542                         mDisplayColorKeyMap.put(displayColor, colorKey);
    543                         colors.add(displayColor);
    544                     } while (cursor.moveToNext());
    545                 }
    546                 cursor.close();
    547                 Integer[] sortedColors = new Integer[colors.size()];
    548                 Arrays.sort(colors.toArray(sortedColors), new HsvColorComparator());
    549                 mColors = new int[sortedColors.length];
    550                 for (int i = 0; i < sortedColors.length; i++) {
    551                     mColors[i] = sortedColors[i].intValue();
    552 
    553                     float[] hsv = new float[3];
    554                     Color.colorToHSV(mColors[i], hsv);
    555                     if (DEBUG) {
    556                         Log.d("Color", "H:" + hsv[0] + ",S:" + hsv[1] + ",V:" + hsv[2]);
    557                     }
    558                 }
    559                 if (mCanModifyCalendar) {
    560                     View button = mView.findViewById(R.id.change_color);
    561                     if (button != null && mColors.length > 0) {
    562                         button.setEnabled(true);
    563                         button.setVisibility(View.VISIBLE);
    564                     }
    565                 }
    566                 updateMenu();
    567                 break;
    568             case TOKEN_QUERY_ATTENDEES:
    569                 mAttendeesCursor = Utils.matrixCursorFromCursor(cursor);
    570                 initAttendeesCursor(mView);
    571                 updateResponse(mView);
    572                 break;
    573             case TOKEN_QUERY_REMINDERS:
    574                 mRemindersCursor = Utils.matrixCursorFromCursor(cursor);
    575                 initReminders(mView, mRemindersCursor);
    576                 break;
    577             case TOKEN_QUERY_VISIBLE_CALENDARS:
    578                 if (cursor.getCount() > 1) {
    579                     // Start duplicate calendars query to detect whether to add the calendar
    580                     // email to the calendar owner display.
    581                     String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
    582                     mHandler.startQuery(TOKEN_QUERY_DUPLICATE_CALENDARS, null,
    583                             Calendars.CONTENT_URI, CALENDARS_PROJECTION,
    584                             CALENDARS_DUPLICATE_NAME_WHERE, new String[] {displayName}, null);
    585                 } else {
    586                     // Don't need to display the calendar owner when there is only a single
    587                     // calendar.  Skip the duplicate calendars query.
    588                     setVisibilityCommon(mView, R.id.calendar_container, View.GONE);
    589                     mCurrentQuery |= TOKEN_QUERY_DUPLICATE_CALENDARS;
    590                 }
    591                 break;
    592             case TOKEN_QUERY_DUPLICATE_CALENDARS:
    593                 SpannableStringBuilder sb = new SpannableStringBuilder();
    594 
    595                 // Calendar display name
    596                 String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
    597                 sb.append(calendarName);
    598 
    599                 // Show email account if display name is not unique and
    600                 // display name != email
    601                 String email = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
    602                 if (cursor.getCount() > 1 && !calendarName.equalsIgnoreCase(email) &&
    603                         Utils.isValidEmail(email)) {
    604                     sb.append(" (").append(email).append(")");
    605                 }
    606 
    607                 setVisibilityCommon(mView, R.id.calendar_container, View.VISIBLE);
    608                 setTextCommon(mView, R.id.calendar_name, sb);
    609                 break;
    610             }
    611             cursor.close();
    612             sendAccessibilityEventIfQueryDone(token);
    613 
    614             // All queries are done, show the view.
    615             if (mCurrentQuery == TOKEN_QUERY_ALL) {
    616                 if (mLoadingMsgView.getAlpha() == 1) {
    617                     // Loading message is showing, let it stay a bit more (to prevent
    618                     // flashing) by adding a start delay to the event animation
    619                     long timeDiff = LOADING_MSG_MIN_DISPLAY_TIME - (System.currentTimeMillis() -
    620                             mLoadingMsgStartTime);
    621                     if (timeDiff > 0) {
    622                         mAnimateAlpha.setStartDelay(timeDiff);
    623                     }
    624                 }
    625                 if (!mAnimateAlpha.isRunning() &&!mAnimateAlpha.isStarted() && !mNoCrossFade) {
    626                     mAnimateAlpha.start();
    627                 } else {
    628                     mScrollView.setAlpha(1);
    629                     mLoadingMsgView.setVisibility(View.GONE);
    630                 }
    631             }
    632         }
    633     }
    634 
    635     private void sendAccessibilityEventIfQueryDone(int token) {
    636         mCurrentQuery |= token;
    637         if (mCurrentQuery == TOKEN_QUERY_ALL) {
    638             sendAccessibilityEvent();
    639         }
    640     }
    641 
    642     public EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis,
    643             int attendeeResponse, boolean isDialog, int windowStyle,
    644             ArrayList<ReminderEntry> reminders) {
    645 
    646         Resources r = context.getResources();
    647         if (mScale == 0) {
    648             mScale = context.getResources().getDisplayMetrics().density;
    649             if (mScale != 1) {
    650                 mCustomAppIconSize *= mScale;
    651                 if (isDialog) {
    652                     DIALOG_TOP_MARGIN *= mScale;
    653                 }
    654             }
    655         }
    656         if (isDialog) {
    657             setDialogSize(r);
    658         }
    659         mIsDialog = isDialog;
    660 
    661         setStyle(DialogFragment.STYLE_NO_TITLE, 0);
    662         mUri = uri;
    663         mStartMillis = startMillis;
    664         mEndMillis = endMillis;
    665         mAttendeeResponseFromIntent = attendeeResponse;
    666         mWindowStyle = windowStyle;
    667 
    668         // Pass in null if no reminders are being specified.
    669         // This may be used to explicitly show certain reminders already known
    670         // about, such as during configuration changes.
    671         mReminders = reminders;
    672     }
    673 
    674     // This is currently required by the fragment manager.
    675     public EventInfoFragment() {
    676     }
    677 
    678     public EventInfoFragment(Context context, long eventId, long startMillis, long endMillis,
    679             int attendeeResponse, boolean isDialog, int windowStyle,
    680             ArrayList<ReminderEntry> reminders) {
    681         this(context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis,
    682                 endMillis, attendeeResponse, isDialog, windowStyle, reminders);
    683         mEventId = eventId;
    684     }
    685 
    686     @Override
    687     public void onActivityCreated(Bundle savedInstanceState) {
    688         super.onActivityCreated(savedInstanceState);
    689 
    690         mReminderChangeListener = new OnItemSelectedListener() {
    691             @Override
    692             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    693                 Integer prevValue = (Integer) parent.getTag();
    694                 if (prevValue == null || prevValue != position) {
    695                     parent.setTag(position);
    696                     mUserModifiedReminders = true;
    697                 }
    698             }
    699 
    700             @Override
    701             public void onNothingSelected(AdapterView<?> parent) {
    702                 // do nothing
    703             }
    704 
    705         };
    706 
    707         if (savedInstanceState != null) {
    708             mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false);
    709             mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE,
    710                     DIALOG_WINDOW_STYLE);
    711         }
    712 
    713         if (mIsDialog) {
    714             applyDialogParams();
    715         }
    716 
    717         final Activity activity = getActivity();
    718         mContext = activity;
    719         mColorPickerDialog = (EventColorPickerDialog) activity.getFragmentManager()
    720                 .findFragmentByTag(COLOR_PICKER_DIALOG_TAG);
    721         if (mColorPickerDialog != null) {
    722             mColorPickerDialog.setOnColorSelectedListener(this);
    723         }
    724     }
    725 
    726     private void applyDialogParams() {
    727         Dialog dialog = getDialog();
    728         dialog.setCanceledOnTouchOutside(true);
    729 
    730         Window window = dialog.getWindow();
    731         window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
    732 
    733         WindowManager.LayoutParams a = window.getAttributes();
    734         a.dimAmount = .4f;
    735 
    736         a.width = mDialogWidth;
    737         a.height = mDialogHeight;
    738 
    739 
    740         // On tablets , do smart positioning of dialog
    741         // On phones , use the whole screen
    742 
    743         if (mX != -1 || mY != -1) {
    744             a.x = mX - mDialogWidth / 2;
    745             a.y = mY - mDialogHeight / 2;
    746             if (a.y < mMinTop) {
    747                 a.y = mMinTop + DIALOG_TOP_MARGIN;
    748             }
    749             a.gravity = Gravity.LEFT | Gravity.TOP;
    750         }
    751         window.setAttributes(a);
    752     }
    753 
    754     public void setDialogParams(int x, int y, int minTop) {
    755         mX = x;
    756         mY = y;
    757         mMinTop = minTop;
    758     }
    759 
    760     // Implements OnCheckedChangeListener
    761     @Override
    762     public void onCheckedChanged(RadioGroup group, int checkedId) {
    763         // If we haven't finished the return from the dialog yet, don't display.
    764         if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
    765             return;
    766         }
    767 
    768         // If this is not a repeating event, then don't display the dialog
    769         // asking which events to change.
    770         int response = getResponseFromButtonId(checkedId);
    771         if (!mIsRepeating) {
    772             mUserSetResponse = response;
    773             return;
    774         }
    775 
    776         // If the selection is the same as the original, then don't display the
    777         // dialog asking which events to change.
    778         if (checkedId == findButtonIdForResponse(mOriginalAttendeeResponse)) {
    779             mUserSetResponse = response;
    780             return;
    781         }
    782 
    783         // This is a repeating event. We need to ask the user if they mean to
    784         // change just this one instance or all instances.
    785         mTentativeUserSetResponse = response;
    786         mEditResponseHelper.showDialog(mWhichEvents);
    787     }
    788 
    789     public void onNothingSelected(AdapterView<?> parent) {
    790     }
    791 
    792     @Override
    793     public void onDetach() {
    794         super.onDetach();
    795         mController.deregisterEventHandler(R.layout.event_info);
    796     }
    797 
    798     @Override
    799     public void onAttach(Activity activity) {
    800         super.onAttach(activity);
    801         mActivity = activity;
    802         // Ensure that mIsTabletConfig is set before creating the menu.
    803         mIsTabletConfig = Utils.getConfigBool(mActivity, R.bool.tablet_config);
    804         mController = CalendarController.getInstance(mActivity);
    805         mController.registerEventHandler(R.layout.event_info, this);
    806         mEditResponseHelper = new EditResponseHelper(activity);
    807         mEditResponseHelper.setDismissListener(
    808                 new DialogInterface.OnDismissListener() {
    809             @Override
    810             public void onDismiss(DialogInterface dialog) {
    811                 // If the user dismisses the dialog (without hitting OK),
    812                 // then we want to revert the selection that opened the dialog.
    813                 if (mEditResponseHelper.getWhichEvents() != -1) {
    814                     mUserSetResponse = mTentativeUserSetResponse;
    815                     mWhichEvents = mEditResponseHelper.getWhichEvents();
    816                 } else {
    817                     // Revert the attending response radio selection to whatever
    818                     // was selected prior to this selection (possibly nothing).
    819                     int oldResponse;
    820                     if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
    821                         oldResponse = mUserSetResponse;
    822                     } else {
    823                         oldResponse = mOriginalAttendeeResponse;
    824                     }
    825                     int buttonToCheck = findButtonIdForResponse(oldResponse);
    826 
    827                     if (mResponseRadioGroup != null) {
    828                         mResponseRadioGroup.check(buttonToCheck);
    829                     }
    830 
    831                     // If the radio group is being cleared, also clear the
    832                     // dialog's selection of which events should be included
    833                     // in this response.
    834                     if (buttonToCheck == -1) {
    835                         mEditResponseHelper.setWhichEvents(-1);
    836                     }
    837                 }
    838 
    839                 // Since OnPause will force the dialog to dismiss, do
    840                 // not change the dialog status
    841                 if (!mIsPaused) {
    842                     mTentativeUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
    843                 }
    844             }
    845         });
    846 
    847         if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) {
    848             mEditResponseHelper.setWhichEvents(UPDATE_ALL);
    849             mWhichEvents = mEditResponseHelper.getWhichEvents();
    850         }
    851         mHandler = new QueryHandler(activity);
    852         if (!mIsDialog) {
    853             setHasOptionsMenu(true);
    854         }
    855     }
    856 
    857     @Override
    858     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    859             Bundle savedInstanceState) {
    860 
    861         if (savedInstanceState != null) {
    862             mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false);
    863             mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE,
    864                     DIALOG_WINDOW_STYLE);
    865             mDeleteDialogVisible =
    866                 savedInstanceState.getBoolean(BUNDLE_KEY_DELETE_DIALOG_VISIBLE,false);
    867             mCalendarColor = savedInstanceState.getInt(BUNDLE_KEY_CALENDAR_COLOR);
    868             mCalendarColorInitialized =
    869                     savedInstanceState.getBoolean(BUNDLE_KEY_CALENDAR_COLOR_INIT);
    870             mOriginalColor = savedInstanceState.getInt(BUNDLE_KEY_ORIGINAL_COLOR);
    871             mOriginalColorInitialized = savedInstanceState.getBoolean(
    872                     BUNDLE_KEY_ORIGINAL_COLOR_INIT);
    873             mCurrentColor = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_COLOR);
    874             mCurrentColorInitialized = savedInstanceState.getBoolean(
    875                     BUNDLE_KEY_CURRENT_COLOR_INIT);
    876             mCurrentColorKey = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_COLOR_KEY);
    877 
    878             mTentativeUserSetResponse = savedInstanceState.getInt(
    879                             BUNDLE_KEY_TENTATIVE_USER_RESPONSE,
    880                             Attendees.ATTENDEE_STATUS_NONE);
    881             if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE &&
    882                     mEditResponseHelper != null) {
    883                 // If the edit response helper dialog is open, we'll need to
    884                 // know if either of the choices were selected.
    885                 mEditResponseHelper.setWhichEvents(savedInstanceState.getInt(
    886                         BUNDLE_KEY_RESPONSE_WHICH_EVENTS, -1));
    887             }
    888             mUserSetResponse = savedInstanceState.getInt(
    889                     BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE,
    890                     Attendees.ATTENDEE_STATUS_NONE);
    891             if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
    892                 // If the response was set by the user before a configuration
    893                 // change, we'll need to know which choice was selected.
    894                 mWhichEvents = savedInstanceState.getInt(
    895                         BUNDLE_KEY_RESPONSE_WHICH_EVENTS, -1);
    896             }
    897 
    898             mReminders = Utils.readRemindersFromBundle(savedInstanceState);
    899         }
    900 
    901         if (mWindowStyle == DIALOG_WINDOW_STYLE) {
    902             mView = inflater.inflate(R.layout.event_info_dialog, container, false);
    903         } else {
    904             mView = inflater.inflate(R.layout.event_info, container, false);
    905         }
    906         mScrollView = (ScrollView) mView.findViewById(R.id.event_info_scroll_view);
    907         mLoadingMsgView = mView.findViewById(R.id.event_info_loading_msg);
    908         mErrorMsgView = mView.findViewById(R.id.event_info_error_msg);
    909         mTitle = (TextView) mView.findViewById(R.id.title);
    910         mWhenDateTime = (TextView) mView.findViewById(R.id.when_datetime);
    911         mWhere = (TextView) mView.findViewById(R.id.where);
    912         mDesc = (ExpandableTextView) mView.findViewById(R.id.description);
    913         mHeadlines = mView.findViewById(R.id.event_info_headline);
    914         mLongAttendees = (AttendeesView) mView.findViewById(R.id.long_attendee_list);
    915 
    916         mResponseRadioGroup = (RadioGroup) mView.findViewById(R.id.response_value);
    917 
    918         if (mUri == null) {
    919             // restore event ID from bundle
    920             mEventId = savedInstanceState.getLong(BUNDLE_KEY_EVENT_ID);
    921             mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
    922             mStartMillis = savedInstanceState.getLong(BUNDLE_KEY_START_MILLIS);
    923             mEndMillis = savedInstanceState.getLong(BUNDLE_KEY_END_MILLIS);
    924         }
    925 
    926         mAnimateAlpha = ObjectAnimator.ofFloat(mScrollView, "Alpha", 0, 1);
    927         mAnimateAlpha.setDuration(FADE_IN_TIME);
    928         mAnimateAlpha.addListener(new AnimatorListenerAdapter() {
    929             int defLayerType;
    930 
    931             @Override
    932             public void onAnimationStart(Animator animation) {
    933                 // Use hardware layer for better performance during animation
    934                 defLayerType = mScrollView.getLayerType();
    935                 mScrollView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    936                 // Ensure that the loading message is gone before showing the
    937                 // event info
    938                 mLoadingMsgView.removeCallbacks(mLoadingMsgAlphaUpdater);
    939                 mLoadingMsgView.setVisibility(View.GONE);
    940             }
    941 
    942             @Override
    943             public void onAnimationCancel(Animator animation) {
    944                 mScrollView.setLayerType(defLayerType, null);
    945             }
    946 
    947             @Override
    948             public void onAnimationEnd(Animator animation) {
    949                 mScrollView.setLayerType(defLayerType, null);
    950                 // Do not cross fade after the first time
    951                 mNoCrossFade = true;
    952             }
    953         });
    954 
    955         mLoadingMsgView.setAlpha(0);
    956         mScrollView.setAlpha(0);
    957         mErrorMsgView.setVisibility(View.INVISIBLE);
    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 false if the cursor is empty, true otherwise
   1056      */
   1057     private boolean initEventCursor() {
   1058         if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
   1059             return false;
   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 true;
   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 displayEventNotFound() {
   1462         mErrorMsgView.setVisibility(View.VISIBLE);
   1463         mScrollView.setVisibility(View.GONE);
   1464         mLoadingMsgView.setVisibility(View.GONE);
   1465     }
   1466 
   1467     private void updateEvent(View view) {
   1468         if (mEventCursor == null || view == null) {
   1469             return;
   1470         }
   1471 
   1472         Context context = view.getContext();
   1473         if (context == null) {
   1474             return;
   1475         }
   1476 
   1477         String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
   1478         if (eventName == null || eventName.length() == 0) {
   1479             eventName = getActivity().getString(R.string.no_title_label);
   1480         }
   1481 
   1482         // 3rd parties might not have specified the start/end time when firing the
   1483         // Events.CONTENT_URI intent.  Update these with values read from the db.
   1484         if (mStartMillis == 0 && mEndMillis == 0) {
   1485             mStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
   1486             mEndMillis = mEventCursor.getLong(EVENT_INDEX_DTEND);
   1487             if (mEndMillis == 0) {
   1488                 String duration = mEventCursor.getString(EVENT_INDEX_DURATION);
   1489                 if (!TextUtils.isEmpty(duration)) {
   1490                     try {
   1491                         Duration d = new Duration();
   1492                         d.parse(duration);
   1493                         long endMillis = mStartMillis + d.getMillis();
   1494                         if (endMillis >= mStartMillis) {
   1495                             mEndMillis = endMillis;
   1496                         } else {
   1497                             Log.d(TAG, "Invalid duration string: " + duration);
   1498                         }
   1499                     } catch (DateException e) {
   1500                         Log.d(TAG, "Error parsing duration string " + duration, e);
   1501                     }
   1502                 }
   1503                 if (mEndMillis == 0) {
   1504                     mEndMillis = mStartMillis;
   1505                 }
   1506             }
   1507         }
   1508 
   1509         mAllDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
   1510         String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
   1511         String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
   1512         String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
   1513         String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
   1514 
   1515         mHeadlines.setBackgroundColor(mCurrentColor);
   1516 
   1517         // What
   1518         if (eventName != null) {
   1519             setTextCommon(view, R.id.title, eventName);
   1520         }
   1521 
   1522         // When
   1523         // Set the date and repeats (if any)
   1524         String localTimezone = Utils.getTimeZone(mActivity, mTZUpdater);
   1525 
   1526         Resources resources = context.getResources();
   1527         String displayedDatetime = Utils.getDisplayedDatetime(mStartMillis, mEndMillis,
   1528                 System.currentTimeMillis(), localTimezone, mAllDay, context);
   1529 
   1530         String displayedTimezone = null;
   1531         if (!mAllDay) {
   1532             displayedTimezone = Utils.getDisplayedTimezone(mStartMillis, localTimezone,
   1533                     eventTimezone);
   1534         }
   1535         // Display the datetime.  Make the timezone (if any) transparent.
   1536         if (displayedTimezone == null) {
   1537             setTextCommon(view, R.id.when_datetime, displayedDatetime);
   1538         } else {
   1539             int timezoneIndex = displayedDatetime.length();
   1540             displayedDatetime += "  " + displayedTimezone;
   1541             SpannableStringBuilder sb = new SpannableStringBuilder(displayedDatetime);
   1542             ForegroundColorSpan transparentColorSpan = new ForegroundColorSpan(
   1543                     resources.getColor(R.color.event_info_headline_transparent_color));
   1544             sb.setSpan(transparentColorSpan, timezoneIndex, displayedDatetime.length(),
   1545                     Spannable.SPAN_INCLUSIVE_INCLUSIVE);
   1546             setTextCommon(view, R.id.when_datetime, sb);
   1547         }
   1548 
   1549         // Display the repeat string (if any)
   1550         String repeatString = null;
   1551         if (!TextUtils.isEmpty(rRule)) {
   1552             EventRecurrence eventRecurrence = new EventRecurrence();
   1553             eventRecurrence.parse(rRule);
   1554             Time date = new Time(localTimezone);
   1555             date.set(mStartMillis);
   1556             if (mAllDay) {
   1557                 date.timezone = Time.TIMEZONE_UTC;
   1558             }
   1559             eventRecurrence.setStartDate(date);
   1560             repeatString = EventRecurrenceFormatter.getRepeatString(mContext, resources,
   1561                     eventRecurrence, true);
   1562         }
   1563         if (repeatString == null) {
   1564             view.findViewById(R.id.when_repeat).setVisibility(View.GONE);
   1565         } else {
   1566             setTextCommon(view, R.id.when_repeat, repeatString);
   1567         }
   1568 
   1569         // Organizer view is setup in the updateCalendar method
   1570 
   1571 
   1572         // Where
   1573         if (location == null || location.trim().length() == 0) {
   1574             setVisibilityCommon(view, R.id.where, View.GONE);
   1575         } else {
   1576             final TextView textView = mWhere;
   1577             if (textView != null) {
   1578                 textView.setAutoLinkMask(0);
   1579                 textView.setText(location.trim());
   1580                 try {
   1581                     textView.setText(Utils.extendedLinkify(textView.getText().toString(), true));
   1582 
   1583                     // Linkify.addLinks() sets the TextView movement method if it finds any links.
   1584                     // We must do the same here, in case linkify by itself did not find any.
   1585                     // (This is cloned from Linkify.addLinkMovementMethod().)
   1586                     MovementMethod mm = textView.getMovementMethod();
   1587                     if ((mm == null) || !(mm instanceof LinkMovementMethod)) {
   1588                         if (textView.getLinksClickable()) {
   1589                             textView.setMovementMethod(LinkMovementMethod.getInstance());
   1590                         }
   1591                     }
   1592                 } catch (Exception ex) {
   1593                     // unexpected
   1594                     Log.e(TAG, "Linkification failed", ex);
   1595                 }
   1596 
   1597                 textView.setOnTouchListener(new OnTouchListener() {
   1598                     @Override
   1599                     public boolean onTouch(View v, MotionEvent event) {
   1600                         try {
   1601                             return v.onTouchEvent(event);
   1602                         } catch (ActivityNotFoundException e) {
   1603                             // ignore
   1604                             return true;
   1605                         }
   1606                     }
   1607                 });
   1608             }
   1609         }
   1610 
   1611         // Description
   1612         if (description != null && description.length() != 0) {
   1613             mDesc.setText(description);
   1614         }
   1615 
   1616         // Launch Custom App
   1617         if (Utils.isJellybeanOrLater()) {
   1618             updateCustomAppButton();
   1619         }
   1620     }
   1621 
   1622     private void updateCustomAppButton() {
   1623         buttonSetup: {
   1624             final Button launchButton = (Button) mView.findViewById(R.id.launch_custom_app_button);
   1625             if (launchButton == null)
   1626                 break buttonSetup;
   1627 
   1628             final String customAppPackage = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_PACKAGE);
   1629             final String customAppUri = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_URI);
   1630 
   1631             if (TextUtils.isEmpty(customAppPackage) || TextUtils.isEmpty(customAppUri))
   1632                 break buttonSetup;
   1633 
   1634             PackageManager pm = mContext.getPackageManager();
   1635             if (pm == null)
   1636                 break buttonSetup;
   1637 
   1638             ApplicationInfo info;
   1639             try {
   1640                 info = pm.getApplicationInfo(customAppPackage, 0);
   1641                 if (info == null)
   1642                     break buttonSetup;
   1643             } catch (NameNotFoundException e) {
   1644                 break buttonSetup;
   1645             }
   1646 
   1647             Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
   1648             final Intent intent = new Intent(CalendarContract.ACTION_HANDLE_CUSTOM_EVENT, uri);
   1649             intent.setPackage(customAppPackage);
   1650             intent.putExtra(CalendarContract.EXTRA_CUSTOM_APP_URI, customAppUri);
   1651             intent.putExtra(EXTRA_EVENT_BEGIN_TIME, mStartMillis);
   1652 
   1653             // See if we have a taker for our intent
   1654             if (pm.resolveActivity(intent, 0) == null)
   1655                 break buttonSetup;
   1656 
   1657             Drawable icon = pm.getApplicationIcon(info);
   1658             if (icon != null) {
   1659 
   1660                 Drawable[] d = launchButton.getCompoundDrawables();
   1661                 icon.setBounds(0, 0, mCustomAppIconSize, mCustomAppIconSize);
   1662                 launchButton.setCompoundDrawables(icon, d[1], d[2], d[3]);
   1663             }
   1664 
   1665             CharSequence label = pm.getApplicationLabel(info);
   1666             if (label != null && label.length() != 0) {
   1667                 launchButton.setText(label);
   1668             } else if (icon == null) {
   1669                 // No icon && no label. Hide button?
   1670                 break buttonSetup;
   1671             }
   1672 
   1673             // Launch custom app
   1674             launchButton.setOnClickListener(new View.OnClickListener() {
   1675                 @Override
   1676                 public void onClick(View v) {
   1677                     try {
   1678                         startActivityForResult(intent, 0);
   1679                     } catch (ActivityNotFoundException e) {
   1680                         // Shouldn't happen as we checked it already
   1681                         setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
   1682                     }
   1683                 }
   1684             });
   1685 
   1686             setVisibilityCommon(mView, R.id.launch_custom_app_container, View.VISIBLE);
   1687             return;
   1688 
   1689         }
   1690 
   1691         setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
   1692         return;
   1693     }
   1694 
   1695     private void sendAccessibilityEvent() {
   1696         AccessibilityManager am =
   1697             (AccessibilityManager) getActivity().getSystemService(Service.ACCESSIBILITY_SERVICE);
   1698         if (!am.isEnabled()) {
   1699             return;
   1700         }
   1701 
   1702         AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
   1703         event.setClassName(EventInfoFragment.class.getName());
   1704         event.setPackageName(getActivity().getPackageName());
   1705         List<CharSequence> text = event.getText();
   1706 
   1707         addFieldToAccessibilityEvent(text, mTitle, null);
   1708         addFieldToAccessibilityEvent(text, mWhenDateTime, null);
   1709         addFieldToAccessibilityEvent(text, mWhere, null);
   1710         addFieldToAccessibilityEvent(text, null, mDesc);
   1711 
   1712         if (mResponseRadioGroup.getVisibility() == View.VISIBLE) {
   1713             int id = mResponseRadioGroup.getCheckedRadioButtonId();
   1714             if (id != View.NO_ID) {
   1715                 text.add(((TextView) getView().findViewById(R.id.response_label)).getText());
   1716                 text.add((((RadioButton) (mResponseRadioGroup.findViewById(id)))
   1717                         .getText() + PERIOD_SPACE));
   1718             }
   1719         }
   1720 
   1721         am.sendAccessibilityEvent(event);
   1722     }
   1723 
   1724     private void addFieldToAccessibilityEvent(List<CharSequence> text, TextView tv,
   1725             ExpandableTextView etv) {
   1726         CharSequence cs;
   1727         if (tv != null) {
   1728             cs = tv.getText();
   1729         } else if (etv != null) {
   1730             cs = etv.getText();
   1731         } else {
   1732             return;
   1733         }
   1734 
   1735         if (!TextUtils.isEmpty(cs)) {
   1736             cs = cs.toString().trim();
   1737             if (cs.length() > 0) {
   1738                 text.add(cs);
   1739                 text.add(PERIOD_SPACE);
   1740             }
   1741         }
   1742     }
   1743 
   1744     private void updateCalendar(View view) {
   1745 
   1746         mCalendarOwnerAccount = "";
   1747         if (mCalendarsCursor != null && mEventCursor != null) {
   1748             mCalendarsCursor.moveToFirst();
   1749             String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
   1750             mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount;
   1751             mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0;
   1752             mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME);
   1753 
   1754             // start visible calendars query
   1755             mHandler.startQuery(TOKEN_QUERY_VISIBLE_CALENDARS, null, Calendars.CONTENT_URI,
   1756                     CALENDARS_PROJECTION, CALENDARS_VISIBLE_WHERE, new String[] {"1"}, null);
   1757 
   1758             mEventOrganizerEmail = mEventCursor.getString(EVENT_INDEX_ORGANIZER);
   1759             mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(mEventOrganizerEmail);
   1760 
   1761             if (!TextUtils.isEmpty(mEventOrganizerEmail) &&
   1762                     !mEventOrganizerEmail.endsWith(Utils.MACHINE_GENERATED_ADDRESS)) {
   1763                 mEventOrganizerDisplayName = mEventOrganizerEmail;
   1764             }
   1765 
   1766             if (!mIsOrganizer && !TextUtils.isEmpty(mEventOrganizerDisplayName)) {
   1767                 setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName);
   1768                 setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE);
   1769             } else {
   1770                 setVisibilityCommon(view, R.id.organizer_container, View.GONE);
   1771             }
   1772             mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
   1773             mCanModifyCalendar = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL)
   1774                     >= Calendars.CAL_ACCESS_CONTRIBUTOR;
   1775             // TODO add "|| guestCanModify" after b/1299071 is fixed
   1776             mCanModifyEvent = mCanModifyCalendar && mIsOrganizer;
   1777             mIsBusyFreeCalendar =
   1778                     mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.CAL_ACCESS_FREEBUSY;
   1779 
   1780             if (!mIsBusyFreeCalendar) {
   1781 
   1782                 View b = mView.findViewById(R.id.edit);
   1783                 b.setEnabled(true);
   1784                 b.setOnClickListener(new OnClickListener() {
   1785                     @Override
   1786                     public void onClick(View v) {
   1787                         doEdit();
   1788                         // For dialogs, just close the fragment
   1789                         // For full screen, close activity on phone, leave it for tablet
   1790                         if (mIsDialog) {
   1791                             EventInfoFragment.this.dismiss();
   1792                         }
   1793                         else if (!mIsTabletConfig){
   1794                             getActivity().finish();
   1795                         }
   1796                     }
   1797                 });
   1798             }
   1799             View button;
   1800             if (mCanModifyCalendar) {
   1801                 button = mView.findViewById(R.id.delete);
   1802                 if (button != null) {
   1803                     button.setEnabled(true);
   1804                     button.setVisibility(View.VISIBLE);
   1805                 }
   1806             }
   1807             if (mCanModifyEvent) {
   1808                 button = mView.findViewById(R.id.edit);
   1809                 if (button != null) {
   1810                     button.setEnabled(true);
   1811                     button.setVisibility(View.VISIBLE);
   1812                 }
   1813             }
   1814             if ((!mIsDialog && !mIsTabletConfig ||
   1815                     mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) && mMenu != null) {
   1816                 mActivity.invalidateOptionsMenu();
   1817             }
   1818         } else {
   1819             setVisibilityCommon(view, R.id.calendar, View.GONE);
   1820             sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS);
   1821         }
   1822     }
   1823 
   1824     /**
   1825      *
   1826      */
   1827     private void updateMenu() {
   1828         if (mMenu == null) {
   1829             return;
   1830         }
   1831         MenuItem delete = mMenu.findItem(R.id.info_action_delete);
   1832         MenuItem edit = mMenu.findItem(R.id.info_action_edit);
   1833         MenuItem changeColor = mMenu.findItem(R.id.info_action_change_color);
   1834         if (delete != null) {
   1835             delete.setVisible(mCanModifyCalendar);
   1836             delete.setEnabled(mCanModifyCalendar);
   1837         }
   1838         if (edit != null) {
   1839             edit.setVisible(mCanModifyEvent);
   1840             edit.setEnabled(mCanModifyEvent);
   1841         }
   1842         if (changeColor != null && mColors != null && mColors.length > 0) {
   1843             changeColor.setVisible(mCanModifyCalendar);
   1844             changeColor.setEnabled(mCanModifyCalendar);
   1845         }
   1846     }
   1847 
   1848     private void updateAttendees(View view) {
   1849         if (mAcceptedAttendees.size() + mDeclinedAttendees.size() +
   1850                 mTentativeAttendees.size() + mNoResponseAttendees.size() > 0) {
   1851             mLongAttendees.clearAttendees();
   1852             (mLongAttendees).addAttendees(mAcceptedAttendees);
   1853             (mLongAttendees).addAttendees(mDeclinedAttendees);
   1854             (mLongAttendees).addAttendees(mTentativeAttendees);
   1855             (mLongAttendees).addAttendees(mNoResponseAttendees);
   1856             mLongAttendees.setEnabled(false);
   1857             mLongAttendees.setVisibility(View.VISIBLE);
   1858         } else {
   1859             mLongAttendees.setVisibility(View.GONE);
   1860         }
   1861 
   1862         if (hasEmailableAttendees()) {
   1863             setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE);
   1864             if (emailAttendeesButton != null) {
   1865                 emailAttendeesButton.setText(R.string.email_guests_label);
   1866             }
   1867         } else if (hasEmailableOrganizer()) {
   1868             setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE);
   1869             if (emailAttendeesButton != null) {
   1870                 emailAttendeesButton.setText(R.string.email_organizer_label);
   1871             }
   1872         } else {
   1873             setVisibilityCommon(mView, R.id.email_attendees_container, View.GONE);
   1874         }
   1875     }
   1876 
   1877     /**
   1878      * Returns true if there is at least 1 attendee that is not the viewer.
   1879      */
   1880     private boolean hasEmailableAttendees() {
   1881         for (Attendee attendee : mAcceptedAttendees) {
   1882             if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
   1883                 return true;
   1884             }
   1885         }
   1886         for (Attendee attendee : mTentativeAttendees) {
   1887             if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
   1888                 return true;
   1889             }
   1890         }
   1891         for (Attendee attendee : mNoResponseAttendees) {
   1892             if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
   1893                 return true;
   1894             }
   1895         }
   1896         for (Attendee attendee : mDeclinedAttendees) {
   1897             if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
   1898                 return true;
   1899             }
   1900         }
   1901         return false;
   1902     }
   1903 
   1904     private boolean hasEmailableOrganizer() {
   1905         return mEventOrganizerEmail != null &&
   1906                 Utils.isEmailableFrom(mEventOrganizerEmail, mSyncAccountName);
   1907     }
   1908 
   1909     public void initReminders(View view, Cursor cursor) {
   1910 
   1911         // Add reminders
   1912         mOriginalReminders.clear();
   1913         mUnsupportedReminders.clear();
   1914         while (cursor.moveToNext()) {
   1915             int minutes = cursor.getInt(EditEventHelper.REMINDERS_INDEX_MINUTES);
   1916             int method = cursor.getInt(EditEventHelper.REMINDERS_INDEX_METHOD);
   1917 
   1918             if (method != Reminders.METHOD_DEFAULT && !mReminderMethodValues.contains(method)) {
   1919                 // Stash unsupported reminder types separately so we don't alter
   1920                 // them in the UI
   1921                 mUnsupportedReminders.add(ReminderEntry.valueOf(minutes, method));
   1922             } else {
   1923                 mOriginalReminders.add(ReminderEntry.valueOf(minutes, method));
   1924             }
   1925         }
   1926         // Sort appropriately for display (by time, then type)
   1927         Collections.sort(mOriginalReminders);
   1928 
   1929         if (mUserModifiedReminders) {
   1930             // If the user has changed the list of reminders don't change what's
   1931             // shown.
   1932             return;
   1933         }
   1934 
   1935         LinearLayout parent = (LinearLayout) mScrollView
   1936                 .findViewById(R.id.reminder_items_container);
   1937         if (parent != null) {
   1938             parent.removeAllViews();
   1939         }
   1940         if (mReminderViews != null) {
   1941             mReminderViews.clear();
   1942         }
   1943 
   1944         if (mHasAlarm) {
   1945             ArrayList<ReminderEntry> reminders;
   1946             // If applicable, use reminders saved in the bundle.
   1947             if (mReminders != null) {
   1948                 reminders = mReminders;
   1949             } else {
   1950                 reminders = mOriginalReminders;
   1951             }
   1952             // Insert any minute values that aren't represented in the minutes list.
   1953             for (ReminderEntry re : reminders) {
   1954                 EventViewUtils.addMinutesToList(
   1955                         mActivity, mReminderMinuteValues, mReminderMinuteLabels, re.getMinutes());
   1956             }
   1957             // Create a UI element for each reminder.  We display all of the reminders we get
   1958             // from the provider, even if the count exceeds the calendar maximum.  (Also, for
   1959             // a new event, we won't have a maxReminders value available.)
   1960             for (ReminderEntry re : reminders) {
   1961                 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
   1962                         mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
   1963                         mReminderMethodLabels, re, Integer.MAX_VALUE, mReminderChangeListener);
   1964             }
   1965             EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
   1966             // TODO show unsupported reminder types in some fashion.
   1967         }
   1968     }
   1969 
   1970     void updateResponse(View view) {
   1971         // we only let the user accept/reject/etc. a meeting if:
   1972         // a) you can edit the event's containing calendar AND
   1973         // b) you're not the organizer and only attendee AND
   1974         // c) organizerCanRespond is enabled for the calendar
   1975         // (if the attendee data has been hidden, the visible number of attendees
   1976         // will be 1 -- the calendar owner's).
   1977         // (there are more cases involved to be 100% accurate, such as
   1978         // paying attention to whether or not an attendee status was
   1979         // included in the feed, but we're currently omitting those corner cases
   1980         // for simplicity).
   1981 
   1982         // TODO Switch to EditEventHelper.canRespond when this class uses CalendarEventModel.
   1983         if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) ||
   1984                 (mIsOrganizer && !mOwnerCanRespond)) {
   1985             setVisibilityCommon(view, R.id.response_container, View.GONE);
   1986             return;
   1987         }
   1988 
   1989         setVisibilityCommon(view, R.id.response_container, View.VISIBLE);
   1990 
   1991 
   1992         int response;
   1993         if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
   1994             response = mTentativeUserSetResponse;
   1995         } else if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
   1996             response = mUserSetResponse;
   1997         } else if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) {
   1998             response = mAttendeeResponseFromIntent;
   1999         } else {
   2000             response = mOriginalAttendeeResponse;
   2001         }
   2002 
   2003         int buttonToCheck = findButtonIdForResponse(response);
   2004         mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons
   2005         mResponseRadioGroup.setOnCheckedChangeListener(this);
   2006     }
   2007 
   2008     private void setTextCommon(View view, int id, CharSequence text) {
   2009         TextView textView = (TextView) view.findViewById(id);
   2010         if (textView == null)
   2011             return;
   2012         textView.setText(text);
   2013     }
   2014 
   2015     private void setVisibilityCommon(View view, int id, int visibility) {
   2016         View v = view.findViewById(id);
   2017         if (v != null) {
   2018             v.setVisibility(visibility);
   2019         }
   2020         return;
   2021     }
   2022 
   2023     /**
   2024      * Taken from com.google.android.gm.HtmlConversationActivity
   2025      *
   2026      * Send the intent that shows the Contact info corresponding to the email address.
   2027      */
   2028     public void showContactInfo(Attendee attendee, Rect rect) {
   2029         // First perform lookup query to find existing contact
   2030         final ContentResolver resolver = getActivity().getContentResolver();
   2031         final String address = attendee.mEmail;
   2032         final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI,
   2033                 Uri.encode(address));
   2034         final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri);
   2035 
   2036         if (lookupUri != null) {
   2037             // Found matching contact, trigger QuickContact
   2038             QuickContact.showQuickContact(getActivity(), rect, lookupUri,
   2039                     QuickContact.MODE_MEDIUM, null);
   2040         } else {
   2041             // No matching contact, ask user to create one
   2042             final Uri mailUri = Uri.fromParts("mailto", address, null);
   2043             final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri);
   2044 
   2045             // Pass along full E-mail string for possible create dialog
   2046             Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null);
   2047             intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString());
   2048 
   2049             // Only provide personal name hint if we have one
   2050             final String senderPersonal = attendee.mName;
   2051             if (!TextUtils.isEmpty(senderPersonal)) {
   2052                 intent.putExtra(Intents.Insert.NAME, senderPersonal);
   2053             }
   2054 
   2055             startActivity(intent);
   2056         }
   2057     }
   2058 
   2059     @Override
   2060     public void onPause() {
   2061         mIsPaused = true;
   2062         mHandler.removeCallbacks(onDeleteRunnable);
   2063         super.onPause();
   2064         // Remove event deletion alert box since it is being rebuild in the OnResume
   2065         // This is done to get the same behavior on OnResume since the AlertDialog is gone on
   2066         // rotation but not if you press the HOME key
   2067         if (mDeleteDialogVisible && mDeleteHelper != null) {
   2068             mDeleteHelper.dismissAlertDialog();
   2069             mDeleteHelper = null;
   2070         }
   2071         if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE
   2072                 && mEditResponseHelper != null) {
   2073             mEditResponseHelper.dismissAlertDialog();
   2074         }
   2075     }
   2076 
   2077     @Override
   2078     public void onResume() {
   2079         super.onResume();
   2080         if (mIsDialog) {
   2081             setDialogSize(getActivity().getResources());
   2082             applyDialogParams();
   2083         }
   2084         mIsPaused = false;
   2085         if (mDismissOnResume) {
   2086             mHandler.post(onDeleteRunnable);
   2087         }
   2088         // Display the "delete confirmation" or "edit response helper" dialog if needed
   2089         if (mDeleteDialogVisible) {
   2090             mDeleteHelper = new DeleteEventHelper(
   2091                     mContext, mActivity,
   2092                     !mIsDialog && !mIsTabletConfig /* exitWhenDone */);
   2093             mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener());
   2094             mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
   2095         } else if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
   2096             int buttonId = findButtonIdForResponse(mTentativeUserSetResponse);
   2097             mResponseRadioGroup.check(buttonId);
   2098             mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents());
   2099         }
   2100     }
   2101 
   2102     @Override
   2103     public void eventsChanged() {
   2104     }
   2105 
   2106     @Override
   2107     public long getSupportedEventTypes() {
   2108         return EventType.EVENTS_CHANGED;
   2109     }
   2110 
   2111     @Override
   2112     public void handleEvent(EventInfo event) {
   2113         reloadEvents();
   2114     }
   2115 
   2116     public void reloadEvents() {
   2117         if (mHandler != null) {
   2118             mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
   2119                     null, null, null);
   2120         }
   2121     }
   2122 
   2123     @Override
   2124     public void onClick(View view) {
   2125 
   2126         // This must be a click on one of the "remove reminder" buttons
   2127         LinearLayout reminderItem = (LinearLayout) view.getParent();
   2128         LinearLayout parent = (LinearLayout) reminderItem.getParent();
   2129         parent.removeView(reminderItem);
   2130         mReminderViews.remove(reminderItem);
   2131         mUserModifiedReminders = true;
   2132         EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
   2133     }
   2134 
   2135 
   2136     /**
   2137      * Add a new reminder when the user hits the "add reminder" button.  We use the default
   2138      * reminder time and method.
   2139      */
   2140     private void addReminder() {
   2141         // TODO: when adding a new reminder, make it different from the
   2142         // last one in the list (if any).
   2143         if (mDefaultReminderMinutes == GeneralPreferences.NO_REMINDER) {
   2144             EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
   2145                     mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
   2146                     mReminderMethodLabels,
   2147                     ReminderEntry.valueOf(GeneralPreferences.REMINDER_DEFAULT_TIME), mMaxReminders,
   2148                     mReminderChangeListener);
   2149         } else {
   2150             EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
   2151                     mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
   2152                     mReminderMethodLabels, ReminderEntry.valueOf(mDefaultReminderMinutes),
   2153                     mMaxReminders, mReminderChangeListener);
   2154         }
   2155 
   2156         EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
   2157     }
   2158 
   2159     synchronized private void prepareReminders() {
   2160         // Nothing to do if we've already built these lists _and_ we aren't
   2161         // removing not allowed methods
   2162         if (mReminderMinuteValues != null && mReminderMinuteLabels != null
   2163                 && mReminderMethodValues != null && mReminderMethodLabels != null
   2164                 && mCalendarAllowedReminders == null) {
   2165             return;
   2166         }
   2167         // Load the labels and corresponding numeric values for the minutes and methods lists
   2168         // from the assets.  If we're switching calendars, we need to clear and re-populate the
   2169         // lists (which may have elements added and removed based on calendar properties).  This
   2170         // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a
   2171         // new event that aren't in the default set.
   2172         Resources r = mActivity.getResources();
   2173         mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values);
   2174         mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels);
   2175         mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values);
   2176         mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels);
   2177 
   2178         // Remove any reminder methods that aren't allowed for this calendar.  If this is
   2179         // a new event, mCalendarAllowedReminders may not be set the first time we're called.
   2180         if (mCalendarAllowedReminders != null) {
   2181             EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels,
   2182                     mCalendarAllowedReminders);
   2183         }
   2184         if (mView != null) {
   2185             mView.invalidate();
   2186         }
   2187     }
   2188 
   2189 
   2190     private boolean saveReminders() {
   2191         ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3);
   2192 
   2193         // Read reminders from UI
   2194         mReminders = EventViewUtils.reminderItemsToReminders(mReminderViews,
   2195                 mReminderMinuteValues, mReminderMethodValues);
   2196         mOriginalReminders.addAll(mUnsupportedReminders);
   2197         Collections.sort(mOriginalReminders);
   2198         mReminders.addAll(mUnsupportedReminders);
   2199         Collections.sort(mReminders);
   2200 
   2201         // Check if there are any changes in the reminder
   2202         boolean changed = EditEventHelper.saveReminders(ops, mEventId, mReminders,
   2203                 mOriginalReminders, false /* no force save */);
   2204 
   2205         if (!changed) {
   2206             return false;
   2207         }
   2208 
   2209         // save new reminders
   2210         AsyncQueryService service = new AsyncQueryService(getActivity());
   2211         service.startBatch(0, null, Calendars.CONTENT_URI.getAuthority(), ops, 0);
   2212         mOriginalReminders = mReminders;
   2213         // Update the "hasAlarm" field for the event
   2214         Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
   2215         int len = mReminders.size();
   2216         boolean hasAlarm = len > 0;
   2217         if (hasAlarm != mHasAlarm) {
   2218             ContentValues values = new ContentValues();
   2219             values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0);
   2220             service.startUpdate(0, null, uri, values, null, null, 0);
   2221         }
   2222         return true;
   2223     }
   2224 
   2225     /**
   2226      * Email all the attendees of the event, except for the viewer (so as to not email
   2227      * himself) and resources like conference rooms.
   2228      */
   2229     private void emailAttendees() {
   2230         Intent i = new Intent(getActivity(), QuickResponseActivity.class);
   2231         i.putExtra(QuickResponseActivity.EXTRA_EVENT_ID, mEventId);
   2232         i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   2233         startActivity(i);
   2234     }
   2235 
   2236     /**
   2237      * Loads an integer array asset into a list.
   2238      */
   2239     private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) {
   2240         int[] vals = r.getIntArray(resNum);
   2241         int size = vals.length;
   2242         ArrayList<Integer> list = new ArrayList<Integer>(size);
   2243 
   2244         for (int i = 0; i < size; i++) {
   2245             list.add(vals[i]);
   2246         }
   2247 
   2248         return list;
   2249     }
   2250     /**
   2251      * Loads a String array asset into a list.
   2252      */
   2253     private static ArrayList<String> loadStringArray(Resources r, int resNum) {
   2254         String[] labels = r.getStringArray(resNum);
   2255         ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels));
   2256         return list;
   2257     }
   2258 
   2259     @Override
   2260     public void onDeleteStarted() {
   2261         mEventDeletionStarted = true;
   2262     }
   2263 
   2264     private Dialog.OnDismissListener createDeleteOnDismissListener() {
   2265         return new Dialog.OnDismissListener() {
   2266                     @Override
   2267                     public void onDismiss(DialogInterface dialog) {
   2268                         // Since OnPause will force the dialog to dismiss , do
   2269                         // not change the dialog status
   2270                         if (!mIsPaused) {
   2271                             mDeleteDialogVisible = false;
   2272                         }
   2273                     }
   2274                 };
   2275     }
   2276 
   2277     public long getEventId() {
   2278         return mEventId;
   2279     }
   2280 
   2281     public long getStartMillis() {
   2282         return mStartMillis;
   2283     }
   2284     public long getEndMillis() {
   2285         return mEndMillis;
   2286     }
   2287     private void setDialogSize(Resources r) {
   2288         mDialogWidth = (int)r.getDimension(R.dimen.event_info_dialog_width);
   2289         mDialogHeight = (int)r.getDimension(R.dimen.event_info_dialog_height);
   2290     }
   2291 
   2292     @Override
   2293     public void onColorSelected(int color) {
   2294         mCurrentColor = color;
   2295         mCurrentColorKey = mDisplayColorKeyMap.get(color);
   2296         mHeadlines.setBackgroundColor(color);
   2297     }
   2298 }
   2299