blob: 721fb9c5e54a1d5adcb8c26d686af0000bb8ec4c [file] [log] [blame]
Mason Tang2bebde72010-08-04 17:20:24 -07001/*
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
17package com.android.calendar;
18
Michael Chan2c8485c2012-05-09 10:23:45 -070019import static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY;
20import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
21import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
22import static com.android.calendar.CalendarController.EVENT_EDIT_ON_LAUNCH;
Mason Tang2bebde72010-08-04 17:20:24 -070023
Isaac Katzenelson8f4d9692012-02-23 17:34:07 -080024import android.animation.Animator;
25import android.animation.AnimatorListenerAdapter;
26import android.animation.ObjectAnimator;
Mason Tang2bebde72010-08-04 17:20:24 -070027import android.app.Activity;
Michael Chan71d08832010-08-25 18:28:59 -070028import android.app.Dialog;
29import android.app.DialogFragment;
Andy McFaddenef31a982011-06-09 07:38:56 -070030import android.app.Service;
Mason Tang2bebde72010-08-04 17:20:24 -070031import android.content.ActivityNotFoundException;
Erik364309f2010-12-23 14:26:26 -080032import android.content.ContentProviderOperation;
Mason Tang2bebde72010-08-04 17:20:24 -070033import android.content.ContentResolver;
34import android.content.ContentUris;
35import android.content.ContentValues;
36import android.content.Context;
Isaac Katzenelsonb0a313a2011-10-24 11:41:56 -070037import android.content.DialogInterface;
Mason Tang2bebde72010-08-04 17:20:24 -070038import android.content.Intent;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -070039import android.content.SharedPreferences;
Michael Chan2c8485c2012-05-09 10:23:45 -070040import android.content.pm.ApplicationInfo;
41import android.content.pm.PackageManager;
42import android.content.pm.PackageManager.NameNotFoundException;
Mason Tang2bebde72010-08-04 17:20:24 -070043import android.content.res.Resources;
44import android.database.Cursor;
Mason Tang2bebde72010-08-04 17:20:24 -070045import android.graphics.Rect;
Michael Chan71b221a2010-10-25 17:55:44 -070046import android.graphics.Typeface;
Michael Chan2c8485c2012-05-09 10:23:45 -070047import android.graphics.drawable.Drawable;
Mason Tang2bebde72010-08-04 17:20:24 -070048import android.net.Uri;
49import android.os.Bundle;
RoboErika7c03902011-06-14 11:06:44 -070050import android.provider.CalendarContract;
51import android.provider.CalendarContract.Attendees;
52import android.provider.CalendarContract.Calendars;
53import android.provider.CalendarContract.Events;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -070054import android.provider.CalendarContract.Reminders;
Erikcb811892010-09-28 13:44:19 -070055import android.provider.ContactsContract;
Mason Tang2bebde72010-08-04 17:20:24 -070056import android.provider.ContactsContract.CommonDataKinds;
Mason Tang2bebde72010-08-04 17:20:24 -070057import android.provider.ContactsContract.Intents;
Mason Tang2bebde72010-08-04 17:20:24 -070058import android.provider.ContactsContract.QuickContact;
Michael Chan8ce3b7d2010-09-01 15:41:01 -070059import android.text.Spannable;
Andy McFadden73b07652011-08-23 15:45:09 -070060import android.text.SpannableString;
Michael Chan8ce3b7d2010-09-01 15:41:01 -070061import android.text.SpannableStringBuilder;
Andy McFadden73b07652011-08-23 15:45:09 -070062import android.text.Spanned;
Mason Tang2bebde72010-08-04 17:20:24 -070063import android.text.TextUtils;
Mason Tang2bebde72010-08-04 17:20:24 -070064import android.text.format.Time;
Andy McFadden73b07652011-08-23 15:45:09 -070065import android.text.method.LinkMovementMethod;
66import android.text.method.MovementMethod;
Michael Chan8ce3b7d2010-09-01 15:41:01 -070067import android.text.style.ForegroundColorSpan;
Michael Chan71b221a2010-10-25 17:55:44 -070068import android.text.style.StyleSpan;
Andy McFadden73b07652011-08-23 15:45:09 -070069import android.text.style.URLSpan;
Mason Tang2bebde72010-08-04 17:20:24 -070070import android.text.util.Linkify;
71import android.text.util.Rfc822Token;
72import android.util.Log;
Michael Chan71d08832010-08-25 18:28:59 -070073import android.view.Gravity;
Mason Tang2bebde72010-08-04 17:20:24 -070074import android.view.LayoutInflater;
Isaac Katzenelson60f01c22011-06-03 15:42:01 -070075import android.view.Menu;
76import android.view.MenuInflater;
77import android.view.MenuItem;
Mason Tang2bebde72010-08-04 17:20:24 -070078import android.view.MotionEvent;
79import android.view.View;
Erikcb811892010-09-28 13:44:19 -070080import android.view.View.OnClickListener;
81import android.view.View.OnTouchListener;
Mason Tang2bebde72010-08-04 17:20:24 -070082import android.view.ViewGroup;
Michael Chan71d08832010-08-25 18:28:59 -070083import android.view.Window;
84import android.view.WindowManager;
Erik259b8f92011-01-12 14:01:12 -080085import android.view.accessibility.AccessibilityEvent;
86import android.view.accessibility.AccessibilityManager;
Mason Tang2bebde72010-08-04 17:20:24 -070087import android.widget.AdapterView;
RoboErik8b5743c2011-09-27 16:07:09 -070088import android.widget.AdapterView.OnItemSelectedListener;
Michael Chan8ce3b7d2010-09-01 15:41:01 -070089import android.widget.Button;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -070090import android.widget.LinearLayout;
Erik259b8f92011-01-12 14:01:12 -080091import android.widget.RadioButton;
Michael Chanf92f46b2010-10-27 18:59:05 -070092import android.widget.RadioGroup;
93import android.widget.RadioGroup.OnCheckedChangeListener;
RoboErik8b5743c2011-09-27 16:07:09 -070094import android.widget.ScrollView;
Mason Tang2bebde72010-08-04 17:20:24 -070095import android.widget.TextView;
96import android.widget.Toast;
97
Michael Chan2c8485c2012-05-09 10:23:45 -070098import com.android.calendar.CalendarController.EventInfo;
99import com.android.calendar.CalendarController.EventType;
100import com.android.calendar.CalendarEventModel.Attendee;
101import com.android.calendar.CalendarEventModel.ReminderEntry;
102import com.android.calendar.event.AttendeesView;
103import com.android.calendar.event.EditEventActivity;
104import com.android.calendar.event.EditEventHelper;
105import com.android.calendar.event.EventViewUtils;
106import com.android.calendarcommon.EventRecurrence;
107
Mason Tang2bebde72010-08-04 17:20:24 -0700108import java.util.ArrayList;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700109import java.util.Arrays;
110import java.util.Collections;
Erik259b8f92011-01-12 14:01:12 -0800111import java.util.List;
RoboErik8b5743c2011-09-27 16:07:09 -0700112import java.util.regex.Pattern;
Isaac Katzenelsonf6f79772011-06-02 16:29:18 -0700113
Michael Chanf92f46b2010-10-27 18:59:05 -0700114public class EventInfoFragment extends DialogFragment implements OnCheckedChangeListener,
Isaac Katzenelsonb33cdf42011-10-14 18:15:18 -0700115 CalendarController.EventHandler, OnClickListener, DeleteEventHelper.DeleteNotifyListener {
Michael Chan83efa2d2012-04-24 17:42:21 -0700116
Erikd845fbe2010-08-12 11:20:01 -0700117 public static final boolean DEBUG = false;
Mason Tang2bebde72010-08-04 17:20:24 -0700118
Michael Chan2c7c8512010-12-10 14:12:57 -0800119 public static final String TAG = "EventInfoFragment";
Mason Tang2bebde72010-08-04 17:20:24 -0700120
Isaac Katzenelson60f01c22011-06-03 15:42:01 -0700121 protected static final String BUNDLE_KEY_EVENT_ID = "key_event_id";
122 protected static final String BUNDLE_KEY_START_MILLIS = "key_start_millis";
123 protected static final String BUNDLE_KEY_END_MILLIS = "key_end_millis";
124 protected static final String BUNDLE_KEY_IS_DIALOG = "key_fragment_is_dialog";
Isaac Katzenelsonb0a313a2011-10-24 11:41:56 -0700125 protected static final String BUNDLE_KEY_DELETE_DIALOG_VISIBLE = "key_delete_dialog_visible";
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700126 protected static final String BUNDLE_KEY_WINDOW_STYLE = "key_window_style";
Isaac Katzenelson60f01c22011-06-03 15:42:01 -0700127 protected static final String BUNDLE_KEY_ATTENDEE_RESPONSE = "key_attendee_response";
Michael Chan71d08832010-08-25 18:28:59 -0700128
Erik259b8f92011-01-12 14:01:12 -0800129 private static final String PERIOD_SPACE = ". ";
130
Mason Tang2bebde72010-08-04 17:20:24 -0700131 /**
132 * These are the corresponding indices into the array of strings
133 * "R.array.change_response_labels" in the resource file.
134 */
135 static final int UPDATE_SINGLE = 0;
136 static final int UPDATE_ALL = 1;
137
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700138 // Style of view
139 public static final int FULL_WINDOW_STYLE = 0;
140 public static final int DIALOG_WINDOW_STYLE = 1;
141
142 private int mWindowStyle = DIALOG_WINDOW_STYLE;
143
Mason Tang2bebde72010-08-04 17:20:24 -0700144 // Query tokens for QueryHandler
Erik259b8f92011-01-12 14:01:12 -0800145 private static final int TOKEN_QUERY_EVENT = 1 << 0;
146 private static final int TOKEN_QUERY_CALENDARS = 1 << 1;
147 private static final int TOKEN_QUERY_ATTENDEES = 1 << 2;
148 private static final int TOKEN_QUERY_DUPLICATE_CALENDARS = 1 << 3;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700149 private static final int TOKEN_QUERY_REMINDERS = 1 << 4;
Erik259b8f92011-01-12 14:01:12 -0800150 private static final int TOKEN_QUERY_ALL = TOKEN_QUERY_DUPLICATE_CALENDARS
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700151 | TOKEN_QUERY_ATTENDEES | TOKEN_QUERY_CALENDARS | TOKEN_QUERY_EVENT
152 | TOKEN_QUERY_REMINDERS;
Erik259b8f92011-01-12 14:01:12 -0800153 private int mCurrentQuery = 0;
Mason Tang2bebde72010-08-04 17:20:24 -0700154
155 private static final String[] EVENT_PROJECTION = new String[] {
156 Events._ID, // 0 do not remove; used in DeleteEventHelper
157 Events.TITLE, // 1 do not remove; used in DeleteEventHelper
158 Events.RRULE, // 2 do not remove; used in DeleteEventHelper
159 Events.ALL_DAY, // 3 do not remove; used in DeleteEventHelper
160 Events.CALENDAR_ID, // 4 do not remove; used in DeleteEventHelper
161 Events.DTSTART, // 5 do not remove; used in DeleteEventHelper
162 Events._SYNC_ID, // 6 do not remove; used in DeleteEventHelper
163 Events.EVENT_TIMEZONE, // 7 do not remove; used in DeleteEventHelper
164 Events.DESCRIPTION, // 8
165 Events.EVENT_LOCATION, // 9
RoboErikef2add92011-06-09 14:49:53 -0700166 Calendars.CALENDAR_ACCESS_LEVEL, // 10
Alon Alberta651f042012-03-01 15:25:53 -0800167 Events.DISPLAY_COLOR, // 11
Michael Chan71b221a2010-10-25 17:55:44 -0700168 Events.HAS_ATTENDEE_DATA, // 12
169 Events.ORGANIZER, // 13
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700170 Events.HAS_ALARM, // 14
171 Calendars.MAX_REMINDERS, //15
172 Calendars.ALLOWED_REMINDERS, // 16
Michael Chan2c8485c2012-05-09 10:23:45 -0700173 Events.CUSTOM_APP_PACKAGE, // 17
174 Events.CUSTOM_APP_URI, // 18
175 Events.ORIGINAL_SYNC_ID, // 19 do not remove; used in DeleteEventHelper
Mason Tang2bebde72010-08-04 17:20:24 -0700176 };
177 private static final int EVENT_INDEX_ID = 0;
178 private static final int EVENT_INDEX_TITLE = 1;
179 private static final int EVENT_INDEX_RRULE = 2;
180 private static final int EVENT_INDEX_ALL_DAY = 3;
181 private static final int EVENT_INDEX_CALENDAR_ID = 4;
182 private static final int EVENT_INDEX_SYNC_ID = 6;
183 private static final int EVENT_INDEX_EVENT_TIMEZONE = 7;
184 private static final int EVENT_INDEX_DESCRIPTION = 8;
185 private static final int EVENT_INDEX_EVENT_LOCATION = 9;
Michael Chan71b221a2010-10-25 17:55:44 -0700186 private static final int EVENT_INDEX_ACCESS_LEVEL = 10;
187 private static final int EVENT_INDEX_COLOR = 11;
188 private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 12;
189 private static final int EVENT_INDEX_ORGANIZER = 13;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700190 private static final int EVENT_INDEX_HAS_ALARM = 14;
191 private static final int EVENT_INDEX_MAX_REMINDERS = 15;
192 private static final int EVENT_INDEX_ALLOWED_REMINDERS = 16;
Michael Chan2c8485c2012-05-09 10:23:45 -0700193 private static final int EVENT_INDEX_CUSTOM_APP_PACKAGE = 17;
194 private static final int EVENT_INDEX_CUSTOM_APP_URI = 18;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700195
Mason Tang2bebde72010-08-04 17:20:24 -0700196
197 private static final String[] ATTENDEES_PROJECTION = new String[] {
198 Attendees._ID, // 0
199 Attendees.ATTENDEE_NAME, // 1
200 Attendees.ATTENDEE_EMAIL, // 2
201 Attendees.ATTENDEE_RELATIONSHIP, // 3
202 Attendees.ATTENDEE_STATUS, // 4
Sara Tingddbc0022012-04-26 17:08:46 -0700203 Attendees.ATTENDEE_IDENTITY, // 5
204 Attendees.ATTENDEE_ID_NAMESPACE // 6
Mason Tang2bebde72010-08-04 17:20:24 -0700205 };
206 private static final int ATTENDEES_INDEX_ID = 0;
207 private static final int ATTENDEES_INDEX_NAME = 1;
208 private static final int ATTENDEES_INDEX_EMAIL = 2;
209 private static final int ATTENDEES_INDEX_RELATIONSHIP = 3;
210 private static final int ATTENDEES_INDEX_STATUS = 4;
Sara Tingddbc0022012-04-26 17:08:46 -0700211 private static final int ATTENDEES_INDEX_IDENTITY = 5;
212 private static final int ATTENDEES_INDEX_ID_NAMESPACE = 6;
Mason Tang2bebde72010-08-04 17:20:24 -0700213
214 private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
215
216 private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, "
217 + Attendees.ATTENDEE_EMAIL + " ASC";
218
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700219 private static final String[] REMINDERS_PROJECTION = new String[] {
220 Reminders._ID, // 0
221 Reminders.MINUTES, // 1
222 Reminders.METHOD // 2
223 };
224 private static final int REMINDERS_INDEX_ID = 0;
225 private static final int REMINDERS_MINUTES_ID = 1;
226 private static final int REMINDERS_METHOD_ID = 2;
227
228 private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=?";
229
Mason Tang2bebde72010-08-04 17:20:24 -0700230 static final String[] CALENDARS_PROJECTION = new String[] {
231 Calendars._ID, // 0
RoboErikef2add92011-06-09 14:49:53 -0700232 Calendars.CALENDAR_DISPLAY_NAME, // 1
Mason Tang2bebde72010-08-04 17:20:24 -0700233 Calendars.OWNER_ACCOUNT, // 2
Sara Ting42896f72012-03-15 15:24:36 -0700234 Calendars.CAN_ORGANIZER_RESPOND, // 3
235 Calendars.ACCOUNT_NAME // 4
Mason Tang2bebde72010-08-04 17:20:24 -0700236 };
237 static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
238 static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
239 static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3;
Sara Ting42896f72012-03-15 15:24:36 -0700240 static final int CALENDARS_INDEX_ACCOUNT_NAME = 4;
Mason Tang2bebde72010-08-04 17:20:24 -0700241
242 static final String CALENDARS_WHERE = Calendars._ID + "=?";
RoboErikef2add92011-06-09 14:49:53 -0700243 static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.CALENDAR_DISPLAY_NAME + "=?";
Mason Tang2bebde72010-08-04 17:20:24 -0700244
Andy McFaddenfd8ce5e2011-11-15 15:44:57 -0800245 private static final String NANP_ALLOWED_SYMBOLS = "()+-*#.";
246 private static final int NANP_MIN_DIGITS = 7;
247 private static final int NANP_MAX_DIGITS = 11;
248
249
Mason Tang2bebde72010-08-04 17:20:24 -0700250 private View mView;
Mason Tang2bebde72010-08-04 17:20:24 -0700251
252 private Uri mUri;
253 private long mEventId;
254 private Cursor mEventCursor;
255 private Cursor mAttendeesCursor;
256 private Cursor mCalendarsCursor;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700257 private Cursor mRemindersCursor;
258
Michael Chan0a01fd32011-01-23 15:13:28 -0800259 private static float mScale = 0; // Used for supporting different screen densities
Mason Tang2bebde72010-08-04 17:20:24 -0700260
Michael Chan2c8485c2012-05-09 10:23:45 -0700261 private static int mCustomAppIconSize = 32;
262
Mason Tang2bebde72010-08-04 17:20:24 -0700263 private long mStartMillis;
264 private long mEndMillis;
Michael Chan8b1119b2012-01-06 16:04:55 -0800265 private boolean mAllDay;
Mason Tang2bebde72010-08-04 17:20:24 -0700266
267 private boolean mHasAttendeeData;
Michael Chan83efa2d2012-04-24 17:42:21 -0700268 private String mEventOrganizerEmail;
269 private String mEventOrganizerDisplayName = "";
Mason Tang2bebde72010-08-04 17:20:24 -0700270 private boolean mIsOrganizer;
Erikd845fbe2010-08-12 11:20:01 -0700271 private long mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE;
Michael Chan352e1a22010-09-28 05:15:08 -0700272 private boolean mOwnerCanRespond;
Sara Ting42896f72012-03-15 15:24:36 -0700273 private String mSyncAccountName;
Mason Tang2bebde72010-08-04 17:20:24 -0700274 private String mCalendarOwnerAccount;
275 private boolean mCanModifyCalendar;
RoboErik65eeb792011-08-08 12:54:00 -0700276 private boolean mCanModifyEvent;
Mason Tang2bebde72010-08-04 17:20:24 -0700277 private boolean mIsBusyFreeCalendar;
Mason Tang2bebde72010-08-04 17:20:24 -0700278 private int mNumOfAttendees;
Mason Tang2bebde72010-08-04 17:20:24 -0700279
Mason Tang2bebde72010-08-04 17:20:24 -0700280 private EditResponseHelper mEditResponseHelper;
Isaac Katzenelsonb0a313a2011-10-24 11:41:56 -0700281 private boolean mDeleteDialogVisible = false;
282 private DeleteEventHelper mDeleteHelper;
Mason Tang2bebde72010-08-04 17:20:24 -0700283
Mason Tang2bebde72010-08-04 17:20:24 -0700284 private int mOriginalAttendeeResponse;
Isaac Katzenelson6bcafcf2012-05-02 18:25:36 -0700285 private int mAttendeeResponseFromIntent = Attendees.ATTENDEE_STATUS_NONE;
286 private int mUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
Mason Tang2bebde72010-08-04 17:20:24 -0700287 private boolean mIsRepeating;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700288 private boolean mHasAlarm;
289 private int mMaxReminders;
290 private String mCalendarAllowedReminders;
Isaac Katzenelsonb33cdf42011-10-14 18:15:18 -0700291 // Used to prevent saving changes in event if it is being deleted.
292 private boolean mEventDeletionStarted = false;
Mason Tang2bebde72010-08-04 17:20:24 -0700293
Erik259b8f92011-01-12 14:01:12 -0800294 private TextView mTitle;
Sara Ting75f53662012-04-09 15:37:10 -0700295 private TextView mWhenDateTime;
Erik259b8f92011-01-12 14:01:12 -0800296 private TextView mWhere;
Michael Chan99704a22011-11-11 10:24:37 -0800297 private ExpandableTextView mDesc;
Isaac Katzenelsonc0624ee2011-06-03 09:24:27 -0700298 private AttendeesView mLongAttendees;
RoboErikd8353fb2011-07-21 11:28:11 -0700299 private Menu mMenu = null;
Isaac Katzenelsonfae6d5c2011-06-16 15:43:41 -0700300 private View mHeadlines;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700301 private ScrollView mScrollView;
Isaac Katzenelson8f4d9692012-02-23 17:34:07 -0800302 private View mLoadingMsgView;
303 private ObjectAnimator mAnimateAlpha;
Isaac Katzenelson263b0292012-03-14 14:55:50 -0700304 private long mLoadingMsgStartTime;
Isaac Katzenelson8f4d9692012-02-23 17:34:07 -0800305 private static final int FADE_IN_TIME = 300; // in milliseconds
Isaac Katzenelson263b0292012-03-14 14:55:50 -0700306 private static final int LOADING_MSG_DELAY = 600; // in milliseconds
307 private static final int LOADING_MSG_MIN_DISPLAY_TIME = 600;
Isaac Katzenelson80d640f2012-04-20 09:21:28 -0700308 private boolean mNoCrossFade = false; // Used to prevent repeated cross-fade
Isaac Katzenelson8f4d9692012-02-23 17:34:07 -0800309
Erik259b8f92011-01-12 14:01:12 -0800310
Andy McFadden73b07652011-08-23 15:45:09 -0700311 private static final Pattern mWildcardPattern = Pattern.compile("^.*$");
Mason Tang2bebde72010-08-04 17:20:24 -0700312
313 ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>();
314 ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>();
315 ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>();
316 ArrayList<Attendee> mNoResponseAttendees = new ArrayList<Attendee>();
Sara Tingcb5f5682012-04-04 22:52:20 -0700317 ArrayList<String> mToEmails = new ArrayList<String>();
318 ArrayList<String> mCcEmails = new ArrayList<String>();
Mason Tang2bebde72010-08-04 17:20:24 -0700319 private int mColor;
320
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700321
322 private int mDefaultReminderMinutes;
Isaac Katzenelsonac4350b2012-04-18 15:33:26 -0700323 private final ArrayList<LinearLayout> mReminderViews = new ArrayList<LinearLayout>(0);
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700324 public ArrayList<ReminderEntry> mReminders;
RoboErik7f078b72011-09-06 10:25:31 -0700325 public ArrayList<ReminderEntry> mOriginalReminders = new ArrayList<ReminderEntry>();
326 public ArrayList<ReminderEntry> mUnsupportedReminders = new ArrayList<ReminderEntry>();
RoboErik8b5743c2011-09-27 16:07:09 -0700327 private boolean mUserModifiedReminders = false;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700328
329 /**
330 * Contents of the "minutes" spinner. This has default values from the XML file, augmented
331 * with any additional values that were already associated with the event.
332 */
333 private ArrayList<Integer> mReminderMinuteValues;
334 private ArrayList<String> mReminderMinuteLabels;
335
336 /**
337 * Contents of the "methods" spinner. The "values" list specifies the method constant
338 * (e.g. {@link Reminders#METHOD_ALERT}) associated with the labels. Any methods that
339 * aren't allowed by the Calendar will be removed.
340 */
341 private ArrayList<Integer> mReminderMethodValues;
342 private ArrayList<String> mReminderMethodLabels;
343
Mason Tang2bebde72010-08-04 17:20:24 -0700344 private QueryHandler mHandler;
345
Isaac Katzenelsonb33cdf42011-10-14 18:15:18 -0700346
Isaac Katzenelsonac4350b2012-04-18 15:33:26 -0700347 private final Runnable mTZUpdater = new Runnable() {
Erikcb811892010-09-28 13:44:19 -0700348 @Override
349 public void run() {
350 updateEvent(mView);
351 }
352 };
353
Isaac Katzenelsonac4350b2012-04-18 15:33:26 -0700354 private final Runnable mLoadingMsgAlphaUpdater = new Runnable() {
Isaac Katzenelson263b0292012-03-14 14:55:50 -0700355 @Override
356 public void run() {
357 // Since this is run after a delay, make sure to only show the message
358 // if the event's data is not shown yet.
359 if (!mAnimateAlpha.isRunning() && mScrollView.getAlpha() == 0) {
360 mLoadingMsgStartTime = System.currentTimeMillis();
361 mLoadingMsgView.setAlpha(1);
362 }
363 }
364 };
365
RoboErik8b5743c2011-09-27 16:07:09 -0700366 private OnItemSelectedListener mReminderChangeListener;
367
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700368 private static int mDialogWidth = 500;
369 private static int mDialogHeight = 600;
Isaac Katzenelson0aa0c612011-10-03 16:46:10 -0700370 private static int DIALOG_TOP_MARGIN = 8;
Michael Chan71d08832010-08-25 18:28:59 -0700371 private boolean mIsDialog = false;
RoboErik7c6236d2011-02-24 16:41:18 -0800372 private boolean mIsPaused = true;
373 private boolean mDismissOnResume = false;
Michael Chan71d08832010-08-25 18:28:59 -0700374 private int mX = -1;
375 private int mY = -1;
Isaac Katzenelson0aa0c612011-10-03 16:46:10 -0700376 private int mMinTop; // Dialog cannot be above this location
Isaac Katzenelson10b60212011-06-28 17:59:14 -0700377 private boolean mIsTabletConfig;
378 private Activity mActivity;
Michael Chanbe2a4322011-08-11 13:39:14 -0700379 private Context mContext;
Michael Chan71d08832010-08-25 18:28:59 -0700380
Mason Tang2bebde72010-08-04 17:20:24 -0700381 private class QueryHandler extends AsyncQueryService {
382 public QueryHandler(Context context) {
383 super(context);
384 }
385
386 @Override
387 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
Mason Tang4003d1c2010-08-17 13:50:45 -0700388 // if the activity is finishing, then close the cursor and return
389 final Activity activity = getActivity();
390 if (activity == null || activity.isFinishing()) {
391 cursor.close();
392 return;
393 }
394
Mason Tang2bebde72010-08-04 17:20:24 -0700395 switch (token) {
396 case TOKEN_QUERY_EVENT:
397 mEventCursor = Utils.matrixCursorFromCursor(cursor);
398 if (initEventCursor()) {
399 // The cursor is empty. This can happen if the event was
400 // deleted.
401 // FRAG_TODO we should no longer rely on Activity.finish()
Mason Tang1df2ca62010-08-18 16:10:25 -0700402 activity.finish();
Mason Tang2bebde72010-08-04 17:20:24 -0700403 return;
404 }
405 updateEvent(mView);
RoboErike35a0322011-09-01 16:21:49 -0700406 prepareReminders();
Mason Tang2bebde72010-08-04 17:20:24 -0700407
408 // start calendar query
409 Uri uri = Calendars.CONTENT_URI;
410 String[] args = new String[] {
411 Long.toString(mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID))};
412 startQuery(TOKEN_QUERY_CALENDARS, null, uri, CALENDARS_PROJECTION,
413 CALENDARS_WHERE, args, null);
414 break;
415 case TOKEN_QUERY_CALENDARS:
416 mCalendarsCursor = Utils.matrixCursorFromCursor(cursor);
417 updateCalendar(mView);
Mason Tang4003d1c2010-08-17 13:50:45 -0700418 // FRAG_TODO fragments shouldn't set the title anymore
Mason Tang2bebde72010-08-04 17:20:24 -0700419 updateTitle();
420
Michael Chan352e1a22010-09-28 05:15:08 -0700421 if (!mIsBusyFreeCalendar) {
Michael Chan352e1a22010-09-28 05:15:08 -0700422 args = new String[] { Long.toString(mEventId) };
Mason Tang2bebde72010-08-04 17:20:24 -0700423
Michael Chan352e1a22010-09-28 05:15:08 -0700424 // start attendees query
425 uri = Attendees.CONTENT_URI;
426 startQuery(TOKEN_QUERY_ATTENDEES, null, uri, ATTENDEES_PROJECTION,
427 ATTENDEES_WHERE, args, ATTENDEES_SORT_ORDER);
Erik259b8f92011-01-12 14:01:12 -0800428 } else {
429 sendAccessibilityEventIfQueryDone(TOKEN_QUERY_ATTENDEES);
Mason Tang2bebde72010-08-04 17:20:24 -0700430 }
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700431 if (mHasAlarm) {
432 // start reminders query
433 args = new String[] { Long.toString(mEventId) };
434 uri = Reminders.CONTENT_URI;
435 startQuery(TOKEN_QUERY_REMINDERS, null, uri,
436 REMINDERS_PROJECTION, REMINDERS_WHERE, args, null);
437 } else {
438 sendAccessibilityEventIfQueryDone(TOKEN_QUERY_REMINDERS);
439 }
Mason Tang2bebde72010-08-04 17:20:24 -0700440 break;
441 case TOKEN_QUERY_ATTENDEES:
442 mAttendeesCursor = Utils.matrixCursorFromCursor(cursor);
443 initAttendeesCursor(mView);
444 updateResponse(mView);
445 break;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700446 case TOKEN_QUERY_REMINDERS:
447 mRemindersCursor = Utils.matrixCursorFromCursor(cursor);
448 initReminders(mView, mRemindersCursor);
449 break;
Mason Tang2bebde72010-08-04 17:20:24 -0700450 case TOKEN_QUERY_DUPLICATE_CALENDARS:
Michael Chan71b221a2010-10-25 17:55:44 -0700451 Resources res = activity.getResources();
452 SpannableStringBuilder sb = new SpannableStringBuilder();
453
454 // Label
455 String label = res.getString(R.string.view_event_calendar_label);
456 sb.append(label).append(" ");
457 sb.setSpan(new StyleSpan(Typeface.BOLD), 0, label.length(),
458 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
459
460 // Calendar display name
Mason Tang2bebde72010-08-04 17:20:24 -0700461 String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
Michael Chan71b221a2010-10-25 17:55:44 -0700462 sb.append(calendarName);
463
464 // Show email account if display name is not unique and
465 // display name != email
466 String email = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
467 if (cursor.getCount() > 1 && !calendarName.equalsIgnoreCase(email)) {
468 sb.append(" (").append(email).append(")");
469 }
470
Mason Tang2bebde72010-08-04 17:20:24 -0700471 break;
472 }
473 cursor.close();
Erik259b8f92011-01-12 14:01:12 -0800474 sendAccessibilityEventIfQueryDone(token);
Isaac Katzenelson263b0292012-03-14 14:55:50 -0700475 // All queries are done, show the view
476 if (mCurrentQuery == TOKEN_QUERY_ALL) {
477 if (mLoadingMsgView.getAlpha() == 1) {
478 // Loading message is showing, let it stay a bit more (to prevent
479 // flashing) by adding a start delay to the event animation
480 long timeDiff = LOADING_MSG_MIN_DISPLAY_TIME - (System.currentTimeMillis() -
481 mLoadingMsgStartTime);
482 if (timeDiff > 0) {
483 mAnimateAlpha.setStartDelay(timeDiff);
484 }
485 }
Isaac Katzenelson80d640f2012-04-20 09:21:28 -0700486 if (!mAnimateAlpha.isRunning() &&!mAnimateAlpha.isStarted() && !mNoCrossFade) {
487 mAnimateAlpha.start();
488 } else {
489 mScrollView.setAlpha(1);
490 mLoadingMsgView.setVisibility(View.GONE);
491 }
Isaac Katzenelson263b0292012-03-14 14:55:50 -0700492 }
Mason Tang2bebde72010-08-04 17:20:24 -0700493 }
Mason Tang2bebde72010-08-04 17:20:24 -0700494 }
495
Erik259b8f92011-01-12 14:01:12 -0800496 private void sendAccessibilityEventIfQueryDone(int token) {
497 mCurrentQuery |= token;
498 if (mCurrentQuery == TOKEN_QUERY_ALL) {
499 sendAccessibilityEvent();
500 }
501 }
502
Michael Chan0a01fd32011-01-23 15:13:28 -0800503 public EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis,
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700504 int attendeeResponse, boolean isDialog, int windowStyle) {
505
Michael Chan2c8485c2012-05-09 10:23:45 -0700506 Resources r = context.getResources();
507 if (mScale == 0) {
508 mScale = context.getResources().getDisplayMetrics().density;
509 if (mScale != 1) {
510 mCustomAppIconSize *= mScale;
511 if (isDialog) {
Isaac Katzenelson65aba6a2012-04-27 15:57:04 -0700512 mDialogWidth = r.getInteger(R.integer.event_info_dialog_width);
513 mDialogHeight = r.getInteger(R.integer.event_info_dialog_height);
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700514 mDialogWidth *= mScale;
515 mDialogHeight *= mScale;
516 DIALOG_TOP_MARGIN *= mScale;
517 }
Michael Chan0a01fd32011-01-23 15:13:28 -0800518 }
519 }
Isaac Katzenelson60f01c22011-06-03 15:42:01 -0700520 mIsDialog = isDialog;
Isaac Katzenelsonc0624ee2011-06-03 09:24:27 -0700521
Michael Chan2fc6b792011-01-26 17:12:44 -0800522 setStyle(DialogFragment.STYLE_NO_TITLE, 0);
Mason Tang2bebde72010-08-04 17:20:24 -0700523 mUri = uri;
524 mStartMillis = startMillis;
525 mEndMillis = endMillis;
526 mAttendeeResponseFromIntent = attendeeResponse;
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700527 mWindowStyle = windowStyle;
Mason Tang2bebde72010-08-04 17:20:24 -0700528 }
529
Andy McFaddenef31a982011-06-09 07:38:56 -0700530 // This is currently required by the fragment manager.
Isaac Katzenelson60f01c22011-06-03 15:42:01 -0700531 public EventInfoFragment() {
532 }
533
534
535
Michael Chan0a01fd32011-01-23 15:13:28 -0800536 public EventInfoFragment(Context context, long eventId, long startMillis, long endMillis,
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700537 int attendeeResponse, boolean isDialog, int windowStyle) {
Michael Chan0a01fd32011-01-23 15:13:28 -0800538 this(context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis,
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700539 endMillis, attendeeResponse, isDialog, windowStyle);
Mason Tang4003d1c2010-08-17 13:50:45 -0700540 mEventId = eventId;
541 }
542
Michael Chan71d08832010-08-25 18:28:59 -0700543 @Override
544 public void onActivityCreated(Bundle savedInstanceState) {
545 super.onActivityCreated(savedInstanceState);
546
RoboErik8b5743c2011-09-27 16:07:09 -0700547 mReminderChangeListener = new OnItemSelectedListener() {
548 @Override
549 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
550 Integer prevValue = (Integer) parent.getTag();
551 if (prevValue == null || prevValue != position) {
552 parent.setTag(position);
553 mUserModifiedReminders = true;
554 }
555 }
556
557 @Override
558 public void onNothingSelected(AdapterView<?> parent) {
559 // do nothing
560 }
561
562 };
563
Michael Chan71d08832010-08-25 18:28:59 -0700564 if (savedInstanceState != null) {
565 mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false);
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700566 mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE,
567 DIALOG_WINDOW_STYLE);
Michael Chan71d08832010-08-25 18:28:59 -0700568 }
569
570 if (mIsDialog) {
571 applyDialogParams();
572 }
Michael Chan825b8ca2011-09-07 10:52:18 -0700573 mContext = getActivity();
Michael Chan71d08832010-08-25 18:28:59 -0700574 }
575
576 private void applyDialogParams() {
577 Dialog dialog = getDialog();
578 dialog.setCanceledOnTouchOutside(true);
579
580 Window window = dialog.getWindow();
581 window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
582
583 WindowManager.LayoutParams a = window.getAttributes();
584 a.dimAmount = .4f;
585
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700586 a.width = mDialogWidth;
587 a.height = mDialogHeight;
Michael Chan71d08832010-08-25 18:28:59 -0700588
Michael Chan71d08832010-08-25 18:28:59 -0700589
Isaac Katzenelson0b1bd102011-04-07 14:26:29 -0700590 // On tablets , do smart positioning of dialog
591 // On phones , use the whole screen
592
Isaac Katzenelson60f01c22011-06-03 15:42:01 -0700593 if (mX != -1 || mY != -1) {
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700594 a.x = mX - mDialogWidth / 2;
595 a.y = mY - mDialogHeight / 2;
Isaac Katzenelson0aa0c612011-10-03 16:46:10 -0700596 if (a.y < mMinTop) {
597 a.y = mMinTop + DIALOG_TOP_MARGIN;
Isaac Katzenelson0b1bd102011-04-07 14:26:29 -0700598 }
Isaac Katzenelson60f01c22011-06-03 15:42:01 -0700599 a.gravity = Gravity.LEFT | Gravity.TOP;
Isaac Katzenelson0b1bd102011-04-07 14:26:29 -0700600 }
Michael Chan71d08832010-08-25 18:28:59 -0700601 window.setAttributes(a);
602 }
603
Isaac Katzenelson0aa0c612011-10-03 16:46:10 -0700604 public void setDialogParams(int x, int y, int minTop) {
Michael Chan71d08832010-08-25 18:28:59 -0700605 mX = x;
606 mY = y;
Isaac Katzenelson0aa0c612011-10-03 16:46:10 -0700607 mMinTop = minTop;
Michael Chan71d08832010-08-25 18:28:59 -0700608 }
609
Michael Chanf92f46b2010-10-27 18:59:05 -0700610 // Implements OnCheckedChangeListener
611 @Override
612 public void onCheckedChanged(RadioGroup group, int checkedId) {
Mason Tang2bebde72010-08-04 17:20:24 -0700613 // If this is not a repeating event, then don't display the dialog
614 // asking which events to change.
RoboErik5afc0832011-09-27 14:14:09 -0700615 mUserSetResponse = getResponseFromButtonId(checkedId);
Mason Tang2bebde72010-08-04 17:20:24 -0700616 if (!mIsRepeating) {
617 return;
618 }
619
620 // If the selection is the same as the original, then don't display the
621 // dialog asking which events to change.
Michael Chanf92f46b2010-10-27 18:59:05 -0700622 if (checkedId == findButtonIdForResponse(mOriginalAttendeeResponse)) {
Mason Tang2bebde72010-08-04 17:20:24 -0700623 return;
624 }
625
626 // This is a repeating event. We need to ask the user if they mean to
627 // change just this one instance or all instances.
628 mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents());
629 }
630
631 public void onNothingSelected(AdapterView<?> parent) {
632 }
633
634 @Override
635 public void onAttach(Activity activity) {
636 super.onAttach(activity);
Isaac Katzenelson10b60212011-06-28 17:59:14 -0700637 mActivity = activity;
Mason Tang2bebde72010-08-04 17:20:24 -0700638 mEditResponseHelper = new EditResponseHelper(activity);
RoboErik86550ce2011-08-22 16:47:51 -0700639
640 if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) {
641 mEditResponseHelper.setWhichEvents(UPDATE_ALL);
642 }
Mason Tang2bebde72010-08-04 17:20:24 -0700643 mHandler = new QueryHandler(activity);
Isaac Katzenelson60f01c22011-06-03 15:42:01 -0700644 if (!mIsDialog) {
645 setHasOptionsMenu(true);
646 }
Mason Tang2bebde72010-08-04 17:20:24 -0700647 }
648
649 @Override
650 public View onCreateView(LayoutInflater inflater, ViewGroup container,
651 Bundle savedInstanceState) {
Isaac Katzenelson320c1b12011-11-07 15:31:45 -0800652
653 if (savedInstanceState != null) {
654 mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false);
655 mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE,
656 DIALOG_WINDOW_STYLE);
657 mDeleteDialogVisible =
658 savedInstanceState.getBoolean(BUNDLE_KEY_DELETE_DIALOG_VISIBLE,false);
659
660 }
661
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700662 if (mWindowStyle == DIALOG_WINDOW_STYLE) {
Michael Chancba7f1f2011-11-07 17:05:59 -0800663 mView = inflater.inflate(R.layout.event_info_dialog, container, false);
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700664 } else {
665 mView = inflater.inflate(R.layout.event_info, container, false);
666 }
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700667 mScrollView = (ScrollView) mView.findViewById(R.id.event_info_scroll_view);
Isaac Katzenelson8f4d9692012-02-23 17:34:07 -0800668 mLoadingMsgView = mView.findViewById(R.id.event_info_loading_msg);
Erik259b8f92011-01-12 14:01:12 -0800669 mTitle = (TextView) mView.findViewById(R.id.title);
Sara Ting75f53662012-04-09 15:37:10 -0700670 mWhenDateTime = (TextView) mView.findViewById(R.id.when_datetime);
Erik259b8f92011-01-12 14:01:12 -0800671 mWhere = (TextView) mView.findViewById(R.id.where);
Michael Chan99704a22011-11-11 10:24:37 -0800672 mDesc = (ExpandableTextView) mView.findViewById(R.id.description);
Isaac Katzenelsonfae6d5c2011-06-16 15:43:41 -0700673 mHeadlines = mView.findViewById(R.id.event_info_headline);
Isaac Katzenelsonc0624ee2011-06-03 09:24:27 -0700674 mLongAttendees = (AttendeesView)mView.findViewById(R.id.long_attendee_list);
Isaac Katzenelson10b60212011-06-28 17:59:14 -0700675 mIsTabletConfig = Utils.getConfigBool(mActivity, R.bool.tablet_config);
Mason Tang2bebde72010-08-04 17:20:24 -0700676
Mason Tang4003d1c2010-08-17 13:50:45 -0700677 if (mUri == null) {
678 // restore event ID from bundle
679 mEventId = savedInstanceState.getLong(BUNDLE_KEY_EVENT_ID);
680 mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
681 mStartMillis = savedInstanceState.getLong(BUNDLE_KEY_START_MILLIS);
682 mEndMillis = savedInstanceState.getLong(BUNDLE_KEY_END_MILLIS);
683 }
684
Isaac Katzenelson8f4d9692012-02-23 17:34:07 -0800685 mAnimateAlpha = ObjectAnimator.ofFloat(mScrollView, "Alpha", 0, 1);
686 mAnimateAlpha.setDuration(FADE_IN_TIME);
687 mAnimateAlpha.addListener(new AnimatorListenerAdapter() {
688 int defLayerType;
689
690 @Override
691 public void onAnimationStart(Animator animation) {
692 // Use hardware layer for better performance during animation
693 defLayerType = mScrollView.getLayerType();
694 mScrollView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
695 // Ensure that the loading message is gone before showing the
696 // event info
Isaac Katzenelson263b0292012-03-14 14:55:50 -0700697 mLoadingMsgView.removeCallbacks(mLoadingMsgAlphaUpdater);
Isaac Katzenelson8f4d9692012-02-23 17:34:07 -0800698 mLoadingMsgView.setVisibility(View.GONE);
699 }
700
701 @Override
702 public void onAnimationCancel(Animator animation) {
703 mScrollView.setLayerType(defLayerType, null);
704 }
705
706 @Override
707 public void onAnimationEnd(Animator animation) {
708 mScrollView.setLayerType(defLayerType, null);
Isaac Katzenelson80d640f2012-04-20 09:21:28 -0700709 // Do not cross fade after the first time
710 mNoCrossFade = true;
Isaac Katzenelson8f4d9692012-02-23 17:34:07 -0800711 }
712 });
713
Isaac Katzenelson8f4d9692012-02-23 17:34:07 -0800714 mLoadingMsgView.setAlpha(0);
715 mScrollView.setAlpha(0);
Isaac Katzenelson263b0292012-03-14 14:55:50 -0700716 mLoadingMsgView.postDelayed(mLoadingMsgAlphaUpdater, LOADING_MSG_DELAY);
Isaac Katzenelson8f4d9692012-02-23 17:34:07 -0800717
Mason Tang2bebde72010-08-04 17:20:24 -0700718 // start loading the data
Isaac Katzenelson8f4d9692012-02-23 17:34:07 -0800719
Mason Tang2bebde72010-08-04 17:20:24 -0700720 mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
721 null, null, null);
722
Erik89ad0822011-01-18 15:55:17 -0800723 Button b = (Button) mView.findViewById(R.id.delete);
Michael Chan8ce3b7d2010-09-01 15:41:01 -0700724 b.setOnClickListener(new OnClickListener() {
725 @Override
726 public void onClick(View v) {
RoboErik69be8552011-02-14 16:51:59 -0800727 if (!mCanModifyCalendar) {
728 return;
729 }
Isaac Katzenelsonb0a313a2011-10-24 11:41:56 -0700730 mDeleteHelper = new DeleteEventHelper(
Michael Chanbe2a4322011-08-11 13:39:14 -0700731 mContext, mActivity,
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700732 !mIsDialog && !mIsTabletConfig /* exitWhenDone */);
Isaac Katzenelsonb0a313a2011-10-24 11:41:56 -0700733 mDeleteHelper.setDeleteNotificationListener(EventInfoFragment.this);
734 mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener());
735 mDeleteDialogVisible = true;
736 mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
Michael Chan8ce3b7d2010-09-01 15:41:01 -0700737 }});
738
Isaac Katzenelson10b60212011-06-28 17:59:14 -0700739 // Hide Edit/Delete buttons if in full screen mode on a phone
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700740 if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) {
Isaac Katzenelson60f01c22011-06-03 15:42:01 -0700741 mView.findViewById(R.id.event_info_buttons_container).setVisibility(View.GONE);
742 }
743
Sara Tingcb5f5682012-04-04 22:52:20 -0700744 // Create a listener for the email guests button
745 View emailAttendeesButton = mView.findViewById(R.id.email_attendees_button);
746 if (emailAttendeesButton != null) {
747 emailAttendeesButton.setOnClickListener(new View.OnClickListener() {
748 @Override
749 public void onClick(View v) {
750 emailAttendees();
751 }
752 });
753 }
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700754
Sara Tingcb5f5682012-04-04 22:52:20 -0700755 // Create a listener for the add reminder button
Michael Chanb20942b2011-09-08 00:52:18 -0700756 View reminderAddButton = mView.findViewById(R.id.reminder_add);
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700757 View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
758 @Override
759 public void onClick(View v) {
760 addReminder();
RoboErik8b5743c2011-09-27 16:07:09 -0700761 mUserModifiedReminders = true;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700762 }
763 };
764 reminderAddButton.setOnClickListener(addReminderOnClickListener);
765
766 // Set reminders variables
767
768 SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity);
769 String defaultReminderString = prefs.getString(
770 GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING);
771 mDefaultReminderMinutes = Integer.parseInt(defaultReminderString);
772 prepareReminders();
773
Mason Tang2bebde72010-08-04 17:20:24 -0700774 return mView;
775 }
776
Isaac Katzenelsonac4350b2012-04-18 15:33:26 -0700777 private final Runnable onDeleteRunnable = new Runnable() {
Erik89ad0822011-01-18 15:55:17 -0800778 @Override
779 public void run() {
RoboErik7c6236d2011-02-24 16:41:18 -0800780 if (EventInfoFragment.this.mIsPaused) {
781 mDismissOnResume = true;
782 return;
783 }
Erik89ad0822011-01-18 15:55:17 -0800784 if (EventInfoFragment.this.isVisible()) {
785 EventInfoFragment.this.dismiss();
786 }
787 }
788 };
789
Mason Tang2bebde72010-08-04 17:20:24 -0700790 private void updateTitle() {
791 Resources res = getActivity().getResources();
792 if (mCanModifyCalendar && !mIsOrganizer) {
793 getActivity().setTitle(res.getString(R.string.event_info_title_invite));
794 } else {
795 getActivity().setTitle(res.getString(R.string.event_info_title));
796 }
797 }
798
799 /**
800 * Initializes the event cursor, which is expected to point to the first
801 * (and only) result from a query.
802 * @return true if the cursor is empty.
803 */
804 private boolean initEventCursor() {
805 if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
806 return true;
807 }
808 mEventCursor.moveToFirst();
809 mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
810 String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
Michael Chan66b20672010-12-20 13:50:28 -0800811 mIsRepeating = !TextUtils.isEmpty(rRule);
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -0700812 mHasAlarm = (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) == 1)?true:false;
813 mMaxReminders = mEventCursor.getInt(EVENT_INDEX_MAX_REMINDERS);
814 mCalendarAllowedReminders = mEventCursor.getString(EVENT_INDEX_ALLOWED_REMINDERS);
Mason Tang2bebde72010-08-04 17:20:24 -0700815 return false;
816 }
817
Mason Tang2bebde72010-08-04 17:20:24 -0700818 @SuppressWarnings("fallthrough")
819 private void initAttendeesCursor(View view) {
Isaac Katzenelson6bcafcf2012-05-02 18:25:36 -0700820 mOriginalAttendeeResponse = Attendees.ATTENDEE_STATUS_NONE;
Erikd845fbe2010-08-12 11:20:01 -0700821 mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE;
Mason Tang2bebde72010-08-04 17:20:24 -0700822 mNumOfAttendees = 0;
823 if (mAttendeesCursor != null) {
824 mNumOfAttendees = mAttendeesCursor.getCount();
825 if (mAttendeesCursor.moveToFirst()) {
826 mAcceptedAttendees.clear();
827 mDeclinedAttendees.clear();
828 mTentativeAttendees.clear();
829 mNoResponseAttendees.clear();
830
831 do {
832 int status = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
833 String name = mAttendeesCursor.getString(ATTENDEES_INDEX_NAME);
834 String email = mAttendeesCursor.getString(ATTENDEES_INDEX_EMAIL);
835
Michael Chan83efa2d2012-04-24 17:42:21 -0700836 if (mAttendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP) ==
837 Attendees.RELATIONSHIP_ORGANIZER) {
838
839 // Overwrites the one from Event table if available
840 if (!TextUtils.isEmpty(name)) {
841 mEventOrganizerDisplayName = name;
842 if (!mIsOrganizer) {
843 setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE);
844 setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName);
845 }
846 }
847 }
848
Erikd845fbe2010-08-12 11:20:01 -0700849 if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE &&
Mason Tang2bebde72010-08-04 17:20:24 -0700850 mCalendarOwnerAccount.equalsIgnoreCase(email)) {
851 mCalendarOwnerAttendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID);
852 mOriginalAttendeeResponse = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
853 } else {
Sara Tingddbc0022012-04-26 17:08:46 -0700854 String identity = mAttendeesCursor.getString(ATTENDEES_INDEX_IDENTITY);
855 String idNamespace = mAttendeesCursor.getString(
856 ATTENDEES_INDEX_ID_NAMESPACE);
857
Mason Tang2bebde72010-08-04 17:20:24 -0700858 // Don't show your own status in the list because:
859 // 1) it doesn't make sense for event without other guests.
860 // 2) there's a spinner for that for events with guests.
861 switch(status) {
862 case Attendees.ATTENDEE_STATUS_ACCEPTED:
Isaac Katzenelsonfe25c502011-06-02 13:31:59 -0700863 mAcceptedAttendees.add(new Attendee(name, email,
Sara Tingddbc0022012-04-26 17:08:46 -0700864 Attendees.ATTENDEE_STATUS_ACCEPTED, identity,
865 idNamespace));
Mason Tang2bebde72010-08-04 17:20:24 -0700866 break;
867 case Attendees.ATTENDEE_STATUS_DECLINED:
Isaac Katzenelsonfe25c502011-06-02 13:31:59 -0700868 mDeclinedAttendees.add(new Attendee(name, email,
Sara Tingddbc0022012-04-26 17:08:46 -0700869 Attendees.ATTENDEE_STATUS_DECLINED, identity,
870 idNamespace));
Mason Tang2bebde72010-08-04 17:20:24 -0700871 break;
Michael Chan8ce3b7d2010-09-01 15:41:01 -0700872 case Attendees.ATTENDEE_STATUS_TENTATIVE:
Isaac Katzenelsonfe25c502011-06-02 13:31:59 -0700873 mTentativeAttendees.add(new Attendee(name, email,
Sara Tingddbc0022012-04-26 17:08:46 -0700874 Attendees.ATTENDEE_STATUS_TENTATIVE, identity,
875 idNamespace));
Michael Chan8ce3b7d2010-09-01 15:41:01 -0700876 break;
877 default:
Isaac Katzenelsonfe25c502011-06-02 13:31:59 -0700878 mNoResponseAttendees.add(new Attendee(name, email,
Sara Tingddbc0022012-04-26 17:08:46 -0700879 Attendees.ATTENDEE_STATUS_NONE, identity,
880 idNamespace));
Mason Tang2bebde72010-08-04 17:20:24 -0700881 }
882 }
883 } while (mAttendeesCursor.moveToNext());
884 mAttendeesCursor.moveToFirst();
885
886 updateAttendees(view);
887 }
888 }
Mason Tang2bebde72010-08-04 17:20:24 -0700889 }
890
891 @Override
Mason Tang4003d1c2010-08-17 13:50:45 -0700892 public void onSaveInstanceState(Bundle outState) {
893 super.onSaveInstanceState(outState);
894 outState.putLong(BUNDLE_KEY_EVENT_ID, mEventId);
895 outState.putLong(BUNDLE_KEY_START_MILLIS, mStartMillis);
896 outState.putLong(BUNDLE_KEY_END_MILLIS, mEndMillis);
Michael Chan71d08832010-08-25 18:28:59 -0700897 outState.putBoolean(BUNDLE_KEY_IS_DIALOG, mIsDialog);
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700898 outState.putInt(BUNDLE_KEY_WINDOW_STYLE, mWindowStyle);
Isaac Katzenelsonb0a313a2011-10-24 11:41:56 -0700899 outState.putBoolean(BUNDLE_KEY_DELETE_DIALOG_VISIBLE, mDeleteDialogVisible);
Isaac Katzenelson60f01c22011-06-03 15:42:01 -0700900 outState.putInt(BUNDLE_KEY_ATTENDEE_RESPONSE, mAttendeeResponseFromIntent);
901 }
902
903
904 @Override
905 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
906 super.onCreateOptionsMenu(menu, inflater);
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700907 // Show edit/delete buttons only in non-dialog configuration
908 if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) {
Isaac Katzenelson60f01c22011-06-03 15:42:01 -0700909 inflater.inflate(R.menu.event_info_title_bar, menu);
RoboErikd8353fb2011-07-21 11:28:11 -0700910 mMenu = menu;
911 updateMenu();
Isaac Katzenelson60f01c22011-06-03 15:42:01 -0700912 }
Mason Tang4003d1c2010-08-17 13:50:45 -0700913 }
914
RoboErikd8353fb2011-07-21 11:28:11 -0700915 @Override
916 public boolean onOptionsItemSelected(MenuItem item) {
917
Isaac Katzenelson7ef29812011-10-25 18:00:50 -0700918 // If we're a dialog we don't want to handle menu buttons
919 if (mIsDialog) {
RoboErikd8353fb2011-07-21 11:28:11 -0700920 return false;
921 }
922 // Handles option menu selections:
923 // Home button - close event info activity and start the main calendar
924 // one
925 // Edit button - start the event edit activity and close the info
926 // activity
927 // Delete button - start a delete query that calls a runnable that close
928 // the info activity
929
930 switch (item.getItemId()) {
931 case android.R.id.home:
RoboErik15b4dcc2011-09-19 16:21:27 -0700932 Utils.returnToCalendarHome(mContext);
RoboErikd8353fb2011-07-21 11:28:11 -0700933 mActivity.finish();
934 return true;
935 case R.id.info_action_edit:
936 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
937 Intent intent = new Intent(Intent.ACTION_EDIT, uri);
938 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, mStartMillis);
939 intent.putExtra(EXTRA_EVENT_END_TIME, mEndMillis);
Michael Chan8b1119b2012-01-06 16:04:55 -0800940 intent.putExtra(EXTRA_EVENT_ALL_DAY, mAllDay);
RoboErikd8353fb2011-07-21 11:28:11 -0700941 intent.setClass(mActivity, EditEventActivity.class);
942 intent.putExtra(EVENT_EDIT_ON_LAUNCH, true);
943 startActivity(intent);
944 mActivity.finish();
945 break;
946 case R.id.info_action_delete:
Isaac Katzenelsonb0a313a2011-10-24 11:41:56 -0700947 mDeleteHelper =
RoboErikd8353fb2011-07-21 11:28:11 -0700948 new DeleteEventHelper(mActivity, mActivity, true /* exitWhenDone */);
Isaac Katzenelsonb0a313a2011-10-24 11:41:56 -0700949 mDeleteHelper.setDeleteNotificationListener(EventInfoFragment.this);
950 mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener());
951 mDeleteDialogVisible = true;
952 mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
RoboErikd8353fb2011-07-21 11:28:11 -0700953 break;
954 default:
955 break;
956 }
957 return super.onOptionsItemSelected(item);
958 }
Mason Tang4003d1c2010-08-17 13:50:45 -0700959
960 @Override
961 public void onDestroyView() {
Isaac Katzenelsonac4350b2012-04-18 15:33:26 -0700962
Isaac Katzenelsonb33cdf42011-10-14 18:15:18 -0700963 if (!mEventDeletionStarted) {
Isaac Katzenelsonac4350b2012-04-18 15:33:26 -0700964 boolean responseSaved = saveResponse();
965 if (saveReminders() || responseSaved) {
Isaac Katzenelsonb33cdf42011-10-14 18:15:18 -0700966 Toast.makeText(getActivity(), R.string.saving_event, Toast.LENGTH_SHORT).show();
967 }
Mason Tang2bebde72010-08-04 17:20:24 -0700968 }
Mason Tang4003d1c2010-08-17 13:50:45 -0700969 super.onDestroyView();
Mason Tang2bebde72010-08-04 17:20:24 -0700970 }
971
972 @Override
973 public void onDestroy() {
Mason Tang2bebde72010-08-04 17:20:24 -0700974 if (mEventCursor != null) {
975 mEventCursor.close();
976 }
977 if (mCalendarsCursor != null) {
978 mCalendarsCursor.close();
979 }
980 if (mAttendeesCursor != null) {
981 mAttendeesCursor.close();
982 }
Michael Chan71d08832010-08-25 18:28:59 -0700983 super.onDestroy();
Mason Tang2bebde72010-08-04 17:20:24 -0700984 }
985
Mason Tang2bebde72010-08-04 17:20:24 -0700986 /**
987 * Asynchronously saves the response to an invitation if the user changed
988 * the response. Returns true if the database will be updated.
989 *
Mason Tang2bebde72010-08-04 17:20:24 -0700990 * @return true if the database will be changed
991 */
992 private boolean saveResponse() {
993 if (mAttendeesCursor == null || mEventCursor == null) {
994 return false;
995 }
Michael Chanf92f46b2010-10-27 18:59:05 -0700996
997 RadioGroup radioGroup = (RadioGroup) getView().findViewById(R.id.response_value);
998 int status = getResponseFromButtonId(radioGroup.getCheckedRadioButtonId());
999 if (status == Attendees.ATTENDEE_STATUS_NONE) {
Mason Tang2bebde72010-08-04 17:20:24 -07001000 return false;
1001 }
1002
Mason Tang2bebde72010-08-04 17:20:24 -07001003 // If the status has not changed, then don't update the database
1004 if (status == mOriginalAttendeeResponse) {
1005 return false;
1006 }
1007
1008 // If we never got an owner attendee id we can't set the status
Erikd845fbe2010-08-12 11:20:01 -07001009 if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE) {
Mason Tang2bebde72010-08-04 17:20:24 -07001010 return false;
1011 }
1012
1013 if (!mIsRepeating) {
1014 // This is a non-repeating event
1015 updateResponse(mEventId, mCalendarOwnerAttendeeId, status);
1016 return true;
1017 }
1018
1019 // This is a repeating event
1020 int whichEvents = mEditResponseHelper.getWhichEvents();
1021 switch (whichEvents) {
1022 case -1:
1023 return false;
1024 case UPDATE_SINGLE:
Andy McFaddenc928e3d2011-06-13 09:53:20 -07001025 createExceptionResponse(mEventId, status);
Mason Tang2bebde72010-08-04 17:20:24 -07001026 return true;
1027 case UPDATE_ALL:
1028 updateResponse(mEventId, mCalendarOwnerAttendeeId, status);
1029 return true;
1030 default:
1031 Log.e(TAG, "Unexpected choice for updating invitation response");
1032 break;
1033 }
1034 return false;
1035 }
1036
1037 private void updateResponse(long eventId, long attendeeId, int status) {
1038 // Update the attendee status in the attendees table. the provider
1039 // takes care of updating the self attendance status.
1040 ContentValues values = new ContentValues();
1041
1042 if (!TextUtils.isEmpty(mCalendarOwnerAccount)) {
1043 values.put(Attendees.ATTENDEE_EMAIL, mCalendarOwnerAccount);
1044 }
1045 values.put(Attendees.ATTENDEE_STATUS, status);
1046 values.put(Attendees.EVENT_ID, eventId);
1047
1048 Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, attendeeId);
1049
1050 mHandler.startUpdate(mHandler.getNextToken(), null, uri, values,
1051 null, null, Utils.UNDO_DELAY);
1052 }
1053
Andy McFaddenc928e3d2011-06-13 09:53:20 -07001054 /**
1055 * Creates an exception to a recurring event. The only change we're making is to the
1056 * "self attendee status" value. The provider will take care of updating the corresponding
1057 * Attendees.attendeeStatus entry.
1058 *
1059 * @param eventId The recurring event.
1060 * @param status The new value for selfAttendeeStatus.
1061 */
1062 private void createExceptionResponse(long eventId, int status) {
Mason Tang2bebde72010-08-04 17:20:24 -07001063 ContentValues values = new ContentValues();
Mason Tang2bebde72010-08-04 17:20:24 -07001064 values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
Mason Tang2bebde72010-08-04 17:20:24 -07001065 values.put(Events.SELF_ATTENDEE_STATUS, status);
Andy McFaddenc928e3d2011-06-13 09:53:20 -07001066 values.put(Events.STATUS, Events.STATUS_CONFIRMED);
Mason Tang2bebde72010-08-04 17:20:24 -07001067
Erik364309f2010-12-23 14:26:26 -08001068 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
Andy McFadden79b2c8c2011-06-30 15:26:05 -07001069 Uri exceptionUri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
Andy McFaddenc928e3d2011-06-13 09:53:20 -07001070 String.valueOf(eventId));
1071 ops.add(ContentProviderOperation.newInsert(exceptionUri).withValues(values).build());
Erik364309f2010-12-23 14:26:26 -08001072
Andy McFaddenc928e3d2011-06-13 09:53:20 -07001073 mHandler.startBatch(mHandler.getNextToken(), null, CalendarContract.AUTHORITY, ops,
1074 Utils.UNDO_DELAY);
1075 }
Mason Tang2bebde72010-08-04 17:20:24 -07001076
Michael Chanf92f46b2010-10-27 18:59:05 -07001077 public static int getResponseFromButtonId(int buttonId) {
1078 int response;
1079 switch (buttonId) {
1080 case R.id.response_yes:
1081 response = Attendees.ATTENDEE_STATUS_ACCEPTED;
1082 break;
1083 case R.id.response_maybe:
1084 response = Attendees.ATTENDEE_STATUS_TENTATIVE;
1085 break;
1086 case R.id.response_no:
1087 response = Attendees.ATTENDEE_STATUS_DECLINED;
1088 break;
1089 default:
1090 response = Attendees.ATTENDEE_STATUS_NONE;
Mason Tang2bebde72010-08-04 17:20:24 -07001091 }
Michael Chanf92f46b2010-10-27 18:59:05 -07001092 return response;
1093 }
1094
1095 public static int findButtonIdForResponse(int response) {
1096 int buttonId;
1097 switch (response) {
1098 case Attendees.ATTENDEE_STATUS_ACCEPTED:
1099 buttonId = R.id.response_yes;
1100 break;
1101 case Attendees.ATTENDEE_STATUS_TENTATIVE:
1102 buttonId = R.id.response_maybe;
1103 break;
1104 case Attendees.ATTENDEE_STATUS_DECLINED:
1105 buttonId = R.id.response_no;
1106 break;
1107 default:
1108 buttonId = -1;
1109 }
1110 return buttonId;
Mason Tang2bebde72010-08-04 17:20:24 -07001111 }
1112
1113 private void doEdit() {
Erikc41b99b2010-11-24 10:27:13 -08001114 Context c = getActivity();
1115 // This ensures that we aren't in the process of closing and have been
1116 // unattached already
1117 if (c != null) {
1118 CalendarController.getInstance(c).sendEventRelatedEvent(
Michael Chan61bf5932011-08-11 12:57:58 -07001119 this, EventType.EDIT_EVENT, mEventId, mStartMillis, mEndMillis, 0
Isaac Katzenelson60f01c22011-06-03 15:42:01 -07001120 , 0, -1);
Erikc41b99b2010-11-24 10:27:13 -08001121 }
Mason Tang2bebde72010-08-04 17:20:24 -07001122 }
1123
Mason Tang2bebde72010-08-04 17:20:24 -07001124 private void updateEvent(View view) {
Isaac Katzenelsonb7762cf2011-06-09 14:04:42 -07001125 if (mEventCursor == null || view == null) {
Mason Tang2bebde72010-08-04 17:20:24 -07001126 return;
1127 }
1128
1129 String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
1130 if (eventName == null || eventName.length() == 0) {
1131 eventName = getActivity().getString(R.string.no_title_label);
1132 }
1133
Michael Chan8b1119b2012-01-06 16:04:55 -08001134 mAllDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
Mason Tang2bebde72010-08-04 17:20:24 -07001135 String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
1136 String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
1137 String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
Isaac Katzenelsonf6f79772011-06-02 16:29:18 -07001138 String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
Mason Tang2bebde72010-08-04 17:20:24 -07001139
RoboErik4acb2fd2011-07-18 15:39:49 -07001140 mColor = Utils.getDisplayColorFromColor(mEventCursor.getInt(EVENT_INDEX_COLOR));
Isaac Katzenelsonfae6d5c2011-06-16 15:43:41 -07001141 mHeadlines.setBackgroundColor(mColor);
Mason Tang2bebde72010-08-04 17:20:24 -07001142
Sara Ting4e926272012-04-19 10:41:56 -07001143 // What
1144 if (eventName != null) {
1145 setTextCommon(view, R.id.title, eventName);
1146 }
1147
Mason Tang2bebde72010-08-04 17:20:24 -07001148 // When
Isaac Katzenelsonfae6d5c2011-06-16 15:43:41 -07001149 // Set the date and repeats (if any)
Sara Ting75f53662012-04-09 15:37:10 -07001150 String localTimezone = Utils.getTimeZone(mActivity, mTZUpdater);
1151 Activity context = getActivity();
Sara Ting23acd262012-04-20 13:27:39 -07001152 Resources resources = context.getResources();
1153 String displayedDatetime = Utils.getDisplayedDatetime(mStartMillis, mEndMillis,
1154 System.currentTimeMillis(), localTimezone, mAllDay, context);
1155 String displayedTimezone = Utils.getDisplayedTimezone(mStartMillis, localTimezone,
1156 eventTimezone);
1157 int timezoneIndex = displayedDatetime.length();
1158 if (displayedTimezone != null) {
1159 displayedDatetime += " " + displayedTimezone;
1160 }
1161 // Display the datetime. Make the timezone (if any) transparent.
1162 if (displayedTimezone == null) {
1163 setTextCommon(view, R.id.when_datetime, displayedDatetime);
1164 } else {
1165 SpannableStringBuilder sb = new SpannableStringBuilder(displayedDatetime);
1166 ForegroundColorSpan transparentColorSpan = new ForegroundColorSpan(
1167 resources.getColor(R.color.event_info_headline_transparent_color));
1168 sb.setSpan(transparentColorSpan, timezoneIndex, displayedDatetime.length(),
1169 Spannable.SPAN_INCLUSIVE_INCLUSIVE);
1170 setTextCommon(view, R.id.when_datetime, sb);
1171 }
Isaac Katzenelson74c90322011-06-02 15:20:29 -07001172
Sara Ting75f53662012-04-09 15:37:10 -07001173 // Display the repeat string (if any)
Isaac Katzenelsonfae6d5c2011-06-16 15:43:41 -07001174 String repeatString = null;
Michael Chanbfa0e3f2010-12-20 12:36:55 -08001175 if (!TextUtils.isEmpty(rRule)) {
Mason Tang2bebde72010-08-04 17:20:24 -07001176 EventRecurrence eventRecurrence = new EventRecurrence();
1177 eventRecurrence.parse(rRule);
Sara Ting75f53662012-04-09 15:37:10 -07001178 Time date = new Time(localTimezone);
1179 date.set(mStartMillis);
Michael Chan8b1119b2012-01-06 16:04:55 -08001180 if (mAllDay) {
Mason Tang2bebde72010-08-04 17:20:24 -07001181 date.timezone = Time.TIMEZONE_UTC;
1182 }
Mason Tang2bebde72010-08-04 17:20:24 -07001183 eventRecurrence.setStartDate(date);
Sara Ting23acd262012-04-20 13:27:39 -07001184 repeatString = EventRecurrenceFormatter.getRepeatString(resources, eventRecurrence);
Mason Tang2bebde72010-08-04 17:20:24 -07001185 }
Sara Ting75f53662012-04-09 15:37:10 -07001186 if (repeatString == null) {
1187 view.findViewById(R.id.when_repeat).setVisibility(View.GONE);
Isaac Katzenelsonfae6d5c2011-06-16 15:43:41 -07001188 } else {
Sara Ting75f53662012-04-09 15:37:10 -07001189 setTextCommon(view, R.id.when_repeat, repeatString);
Isaac Katzenelsonfae6d5c2011-06-16 15:43:41 -07001190 }
1191
Isaac Katzenelsonfae6d5c2011-06-16 15:43:41 -07001192 // Organizer view is setup in the updateCalendar method
1193
Mason Tang2bebde72010-08-04 17:20:24 -07001194
1195 // Where
Michael Chanddfb59d2011-01-12 15:41:21 -08001196 if (location == null || location.trim().length() == 0) {
Mason Tang2bebde72010-08-04 17:20:24 -07001197 setVisibilityCommon(view, R.id.where, View.GONE);
1198 } else {
Erik259b8f92011-01-12 14:01:12 -08001199 final TextView textView = mWhere;
Mason Tang2bebde72010-08-04 17:20:24 -07001200 if (textView != null) {
Michael Chanddfb59d2011-01-12 15:41:21 -08001201 textView.setAutoLinkMask(0);
1202 textView.setText(location.trim());
Andy McFaddenaf577fd2011-11-07 11:08:14 -08001203 try {
1204 linkifyTextView(textView);
1205 } catch (Exception ex) {
1206 // unexpected
1207 Log.e(TAG, "Linkification failed", ex);
1208 }
Andy McFadden73b07652011-08-23 15:45:09 -07001209
Michael Chanddfb59d2011-01-12 15:41:21 -08001210 textView.setOnTouchListener(new OnTouchListener() {
Andy McFaddenc928e3d2011-06-13 09:53:20 -07001211 @Override
Michael Chanddfb59d2011-01-12 15:41:21 -08001212 public boolean onTouch(View v, MotionEvent event) {
1213 try {
1214 return v.onTouchEvent(event);
1215 } catch (ActivityNotFoundException e) {
1216 // ignore
1217 return true;
Mason Tang2bebde72010-08-04 17:20:24 -07001218 }
Michael Chanddfb59d2011-01-12 15:41:21 -08001219 }
1220 });
Mason Tang2bebde72010-08-04 17:20:24 -07001221 }
1222 }
1223
1224 // Description
Michael Chan352e1a22010-09-28 05:15:08 -07001225 if (description != null && description.length() != 0) {
Michael Chan99704a22011-11-11 10:24:37 -08001226 mDesc.setText(description);
Mason Tang2bebde72010-08-04 17:20:24 -07001227 }
Mason Tang2bebde72010-08-04 17:20:24 -07001228
Michael Chan2c8485c2012-05-09 10:23:45 -07001229 // Launch Custom App
1230 updateCustomAppButton();
1231 }
1232
1233 private void updateCustomAppButton() {
1234 buttonSetup: {
1235 final Button launchButton = (Button) mView.findViewById(R.id.launch_custom_app_button);
1236 if (launchButton == null)
1237 break buttonSetup;
1238
1239 final String customAppPackage = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_PACKAGE);
1240 final String customAppUri = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_URI);
1241
1242 if (TextUtils.isEmpty(customAppPackage) || TextUtils.isEmpty(customAppUri))
1243 break buttonSetup;
1244
1245 PackageManager pm = mContext.getPackageManager();
1246 if (pm == null)
1247 break buttonSetup;
1248
1249 ApplicationInfo info;
1250 try {
1251 info = pm.getApplicationInfo(customAppPackage, 0);
1252 if (info == null)
1253 break buttonSetup;
1254 } catch (NameNotFoundException e) {
1255 break buttonSetup;
1256 }
1257
1258 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
1259 final Intent intent = new Intent(CalendarContract.ACTION_HANDLE_CUSTOM_EVENT, uri);
1260 intent.setPackage(customAppPackage);
1261 intent.putExtra(CalendarContract.EXTRA_CUSTOM_APP_URI, customAppUri);
1262 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, mStartMillis);
1263
1264 // See if we have a taker for our intent
1265 if (pm.resolveActivity(intent, 0) == null)
1266 break buttonSetup;
1267
1268 Drawable icon = pm.getApplicationIcon(info);
1269 if (icon != null) {
1270
1271 Drawable[] d = launchButton.getCompoundDrawables();
1272 icon.setBounds(0, 0, mCustomAppIconSize, mCustomAppIconSize);
1273 launchButton.setCompoundDrawables(icon, d[1], d[2], d[3]);
1274 }
1275
1276 CharSequence label = pm.getApplicationLabel(info);
1277 if (label != null && label.length() != 0) {
1278 launchButton.setText(label);
1279 } else if (icon == null) {
1280 // No icon && no label. Hide button?
1281 break buttonSetup;
1282 }
1283
1284 // Launch custom app
1285 launchButton.setOnClickListener(new View.OnClickListener() {
1286 @Override
1287 public void onClick(View v) {
1288 try {
1289 startActivityForResult(intent, 0);
1290 } catch (ActivityNotFoundException e) {
1291 // Shouldn't happen as we checked it already
1292 setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
1293 }
1294 }
1295 });
1296
1297 setVisibilityCommon(mView, R.id.launch_custom_app_container, View.VISIBLE);
1298 return;
1299
1300 }
1301
1302 setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
1303 return;
Isaac Katzenelson8f4d9692012-02-23 17:34:07 -08001304 }
Sara Ting75f53662012-04-09 15:37:10 -07001305
Andy McFadden73b07652011-08-23 15:45:09 -07001306 /**
Andy McFaddenfd8ce5e2011-11-15 15:44:57 -08001307 * Finds North American Numbering Plan (NANP) phone numbers in the input text.
1308 *
1309 * @param text The text to scan.
1310 * @return A list of [start, end) pairs indicating the positions of phone numbers in the input.
1311 */
1312 // @VisibleForTesting
1313 static int[] findNanpPhoneNumbers(CharSequence text) {
1314 ArrayList<Integer> list = new ArrayList<Integer>();
1315
1316 int startPos = 0;
1317 int endPos = text.length() - NANP_MIN_DIGITS + 1;
1318 if (endPos < 0) {
1319 return new int[] {};
1320 }
1321
1322 /*
1323 * We can't just strip the whitespace out and crunch it down, because the whitespace
1324 * is significant. March through, trying to figure out where numbers start and end.
1325 */
1326 while (startPos < endPos) {
1327 // skip whitespace
1328 while (Character.isWhitespace(text.charAt(startPos)) && startPos < endPos) {
1329 startPos++;
1330 }
1331 if (startPos == endPos) {
1332 break;
1333 }
1334
1335 // check for a match at this position
1336 int matchEnd = findNanpMatchEnd(text, startPos);
1337 if (matchEnd > startPos) {
1338 list.add(startPos);
1339 list.add(matchEnd);
1340 startPos = matchEnd; // skip past match
1341 } else {
1342 // skip to next whitespace char
1343 while (!Character.isWhitespace(text.charAt(startPos)) && startPos < endPos) {
1344 startPos++;
1345 }
1346 }
1347 }
1348
1349 int[] result = new int[list.size()];
1350 for (int i = list.size() - 1; i >= 0; i--) {
1351 result[i] = list.get(i);
1352 }
1353 return result;
1354 }
1355
1356 /**
1357 * Checks to see if there is a valid phone number in the input, starting at the specified
1358 * offset. If so, the index of the last character + 1 is returned. The input is assumed
1359 * to begin with a non-whitespace character.
1360 *
1361 * @return Exclusive end position, or -1 if not a match.
1362 */
1363 private static int findNanpMatchEnd(CharSequence text, int startPos) {
1364 /*
1365 * A few interesting cases:
1366 * 94043 # too short, ignore
1367 * 123456789012 # too long, ignore
1368 * +1 (650) 555-1212 # 11 digits, spaces
1369 * (650) 555-1212, (650) 555-1213 # two numbers, return first
1370 * 1-650-555-1212 # 11 digits with leading '1'
1371 * *#650.555.1212#*! # 10 digits, include #*, ignore trailing '!'
1372 * 555.1212 # 7 digits
1373 *
1374 * For the most part we want to break on whitespace, but it's common to leave a space
1375 * between the initial '1' and/or after the area code.
1376 */
1377
1378 int endPos = text.length();
1379 int curPos = startPos;
1380 int foundDigits = 0;
1381 char firstDigit = 'x';
1382
1383 while (curPos <= endPos) {
1384 char ch;
1385 if (curPos < endPos) {
1386 ch = text.charAt(curPos);
1387 } else {
1388 ch = 27; // fake invalid symbol at end to trigger loop break
1389 }
1390
1391 if (Character.isDigit(ch)) {
1392 if (foundDigits == 0) {
1393 firstDigit = ch;
1394 }
1395 foundDigits++;
1396 if (foundDigits > NANP_MAX_DIGITS) {
1397 // too many digits, stop early
1398 return -1;
1399 }
1400 } else if (Character.isWhitespace(ch)) {
1401 if (!( (firstDigit == '1' && (foundDigits == 1 || foundDigits == 4)) ||
1402 (foundDigits == 3)) ) {
1403 break;
1404 }
1405 } else if (NANP_ALLOWED_SYMBOLS.indexOf(ch) == -1) {
1406 break;
1407 }
1408 // else it's an allowed symbol
1409
1410 curPos++;
1411 }
1412
1413 if ((firstDigit != '1' && (foundDigits == 7 || foundDigits == 10)) ||
1414 (firstDigit == '1' && foundDigits == 11)) {
1415 // match
1416 return curPos;
1417 }
1418
1419 return -1;
1420 }
1421
1422 /**
Andy McFadden73b07652011-08-23 15:45:09 -07001423 * Replaces stretches of text that look like addresses and phone numbers with clickable
1424 * links.
1425 * <p>
1426 * This is really just an enhanced version of Linkify.addLinks().
1427 */
1428 private static void linkifyTextView(TextView textView) {
1429 /*
1430 * If the text includes a street address like "1600 Amphitheater Parkway, 94043",
1431 * the current Linkify code will identify "94043" as a phone number and invite
1432 * you to dial it (and not provide a map link for the address). We want to
1433 * have better recognition of phone numbers without losing any of the existing
1434 * annotations.
1435 *
1436 * Ideally this would be addressed by improving Linkify. For now we manage it as
1437 * a second pass over the text.
1438 *
1439 * URIs and e-mail addresses are pretty easy to pick out of text. Phone numbers
1440 * are a bit tricky because they have radically different formats in different
1441 * countries, in terms of both the digits and the way in which they are commonly
1442 * written or presented (e.g. the punctuation and spaces in "(650) 555-1212").
1443 * The expected format of a street address is defined in WebView.findAddress(). It's
1444 * pretty narrowly defined, so it won't often match.
1445 *
1446 * The RFC 3966 specification defines the format of a "tel:" URI.
1447 */
1448
1449 /*
Andy McFaddenfd8ce5e2011-11-15 15:44:57 -08001450 * If we're in the US, handle this specially. Otherwise, punt to Linkify.
1451 */
1452 String defaultPhoneRegion = System.getProperty("user.region", "US");
1453 if (!defaultPhoneRegion.equals("US")) {
1454 Linkify.addLinks(textView, Linkify.ALL);
1455 return;
1456 }
1457
1458 /*
Andy McFadden73b07652011-08-23 15:45:09 -07001459 * Start by letting Linkify find anything that isn't a phone number. We have to let it
1460 * run first because every invocation removes all previous URLSpan annotations.
Andy McFaddenfd8ce5e2011-11-15 15:44:57 -08001461 *
1462 * Ideally we'd use the external/libphonenumber routines, but those aren't available
1463 * to unbundled applications.
Andy McFadden73b07652011-08-23 15:45:09 -07001464 */
1465 boolean linkifyFoundLinks = Linkify.addLinks(textView,
1466 Linkify.ALL & ~(Linkify.PHONE_NUMBERS));
1467
1468 /*
1469 * Search for phone numbers.
1470 *
Andy McFaddenfd8ce5e2011-11-15 15:44:57 -08001471 * Some URIs contain strings of digits that look like phone numbers. If both the URI
1472 * scanner and the phone number scanner find them, we want the URI link to win. Since
1473 * the URI scanner runs first, we just need to avoid creating overlapping spans.
Andy McFadden73b07652011-08-23 15:45:09 -07001474 */
Andy McFadden73b07652011-08-23 15:45:09 -07001475 CharSequence text = textView.getText();
Andy McFaddenfd8ce5e2011-11-15 15:44:57 -08001476 int[] phoneSequences = findNanpPhoneNumbers(text);
Andy McFadden73b07652011-08-23 15:45:09 -07001477
1478 /*
1479 * If the contents of the TextView are already Spannable (which will be the case if
1480 * Linkify found stuff, but might not be otherwise), we can just add annotations
1481 * to what's there. If it's not, and we find phone numbers, we need to convert it to
1482 * a Spannable form. (This mimics the behavior of Linkable.addLinks().)
1483 */
1484 Spannable spanText;
1485 if (text instanceof SpannableString) {
1486 spanText = (SpannableString) text;
1487 } else {
1488 spanText = SpannableString.valueOf(text);
1489 }
1490
1491 /*
1492 * Get a list of any spans created by Linkify, for the overlapping span check.
1493 */
1494 URLSpan[] existingSpans = spanText.getSpans(0, spanText.length(), URLSpan.class);
1495
1496 /*
1497 * Insert spans for the numbers we found. We generate "tel:" URIs.
1498 */
1499 int phoneCount = 0;
Andy McFaddenfd8ce5e2011-11-15 15:44:57 -08001500 for (int match = 0; match < phoneSequences.length / 2; match++) {
1501 int start = phoneSequences[match*2];
1502 int end = phoneSequences[match*2 + 1];
Andy McFadden73b07652011-08-23 15:45:09 -07001503
1504 if (spanWillOverlap(spanText, existingSpans, start, end)) {
1505 if (Log.isLoggable(TAG, Log.VERBOSE)) {
Andy McFaddenfd8ce5e2011-11-15 15:44:57 -08001506 CharSequence seq = text.subSequence(start, end);
1507 Log.v(TAG, "Not linkifying " + seq + " as phone number due to overlap");
Andy McFadden73b07652011-08-23 15:45:09 -07001508 }
1509 continue;
1510 }
1511
1512 /*
Andy McFadden73b07652011-08-23 15:45:09 -07001513 * The Linkify code takes the matching span and strips out everything that isn't a
1514 * digit or '+' sign. We do the same here. Extension numbers will get appended
1515 * without a separator, but the dialer wasn't doing anything useful with ";ext="
1516 * anyway.
1517 */
1518
1519 //String dialStr = phoneUtil.format(match.number(),
1520 // PhoneNumberUtil.PhoneNumberFormat.RFC3966);
1521 StringBuilder dialBuilder = new StringBuilder();
1522 for (int i = start; i < end; i++) {
1523 char ch = spanText.charAt(i);
1524 if (ch == '+' || Character.isDigit(ch)) {
1525 dialBuilder.append(ch);
1526 }
1527 }
1528 URLSpan span = new URLSpan("tel:" + dialBuilder.toString());
1529
1530 spanText.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1531 phoneCount++;
1532 }
1533
1534 if (phoneCount != 0) {
1535 // If we had to "upgrade" to Spannable, store the object into the TextView.
1536 if (spanText != text) {
1537 textView.setText(spanText);
1538 }
1539
1540 // Linkify.addLinks() sets the TextView movement method if it finds any links. We
1541 // want to do the same here. (This is cloned from Linkify.addLinkMovementMethod().)
1542 MovementMethod mm = textView.getMovementMethod();
1543
1544 if ((mm == null) || !(mm instanceof LinkMovementMethod)) {
1545 if (textView.getLinksClickable()) {
1546 textView.setMovementMethod(LinkMovementMethod.getInstance());
1547 }
1548 }
1549 }
1550
1551 if (!linkifyFoundLinks && phoneCount == 0) {
1552 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1553 Log.v(TAG, "No linkification matches, using geo default");
1554 }
1555 Linkify.addLinks(textView, mWildcardPattern, "geo:0,0?q=");
1556 }
1557 }
1558
1559 /**
1560 * Determines whether a new span at [start,end) will overlap with any existing span.
1561 */
1562 private static boolean spanWillOverlap(Spannable spanText, URLSpan[] spanList, int start,
1563 int end) {
1564 if (start == end) {
1565 // empty span, ignore
1566 return false;
1567 }
1568 for (URLSpan span : spanList) {
1569 int existingStart = spanText.getSpanStart(span);
1570 int existingEnd = spanText.getSpanEnd(span);
1571 if ((start >= existingStart && start < existingEnd) ||
1572 end > existingStart && end <= existingEnd) {
1573 return true;
1574 }
1575 }
1576
1577 return false;
1578 }
1579
Erik259b8f92011-01-12 14:01:12 -08001580 private void sendAccessibilityEvent() {
Andy McFaddenef31a982011-06-09 07:38:56 -07001581 AccessibilityManager am =
1582 (AccessibilityManager) getActivity().getSystemService(Service.ACCESSIBILITY_SERVICE);
Erik259b8f92011-01-12 14:01:12 -08001583 if (!am.isEnabled()) {
1584 return;
1585 }
1586
1587 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
1588 event.setClassName(getClass().getName());
1589 event.setPackageName(getActivity().getPackageName());
1590 List<CharSequence> text = event.getText();
1591
Michael Chan99704a22011-11-11 10:24:37 -08001592 addFieldToAccessibilityEvent(text, mTitle, null);
Sara Ting75f53662012-04-09 15:37:10 -07001593 addFieldToAccessibilityEvent(text, mWhenDateTime, null);
Michael Chan99704a22011-11-11 10:24:37 -08001594 addFieldToAccessibilityEvent(text, mWhere, null);
1595 addFieldToAccessibilityEvent(text, null, mDesc);
Erik259b8f92011-01-12 14:01:12 -08001596
1597 RadioGroup response = (RadioGroup) getView().findViewById(R.id.response_value);
1598 if (response.getVisibility() == View.VISIBLE) {
1599 int id = response.getCheckedRadioButtonId();
1600 if (id != View.NO_ID) {
1601 text.add(((TextView) getView().findViewById(R.id.response_label)).getText());
1602 text.add((((RadioButton) (response.findViewById(id))).getText() + PERIOD_SPACE));
1603 }
1604 }
1605
1606 am.sendAccessibilityEvent(event);
1607 }
1608
Michael Chan99704a22011-11-11 10:24:37 -08001609 private void addFieldToAccessibilityEvent(List<CharSequence> text, TextView tv,
1610 ExpandableTextView etv) {
Michael Chan33893972011-11-16 12:45:53 -08001611 CharSequence cs;
Michael Chan99704a22011-11-11 10:24:37 -08001612 if (tv != null) {
Michael Chan33893972011-11-16 12:45:53 -08001613 cs = tv.getText();
Michael Chan99704a22011-11-11 10:24:37 -08001614 } else if (etv != null) {
Michael Chan33893972011-11-16 12:45:53 -08001615 cs = etv.getText();
Michael Chan99704a22011-11-11 10:24:37 -08001616 } else {
Isaac Katzenelson9ceed1f2011-06-08 17:26:26 -07001617 return;
1618 }
Michael Chan99704a22011-11-11 10:24:37 -08001619
Michael Chan33893972011-11-16 12:45:53 -08001620 if (!TextUtils.isEmpty(cs)) {
1621 cs = cs.toString().trim();
1622 if (cs.length() > 0) {
1623 text.add(cs);
1624 text.add(PERIOD_SPACE);
1625 }
Erik259b8f92011-01-12 14:01:12 -08001626 }
1627 }
1628
Mason Tang2bebde72010-08-04 17:20:24 -07001629 private void updateCalendar(View view) {
1630 mCalendarOwnerAccount = "";
1631 if (mCalendarsCursor != null && mEventCursor != null) {
1632 mCalendarsCursor.moveToFirst();
1633 String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
1634 mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount;
Michael Chan352e1a22010-09-28 05:15:08 -07001635 mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0;
Sara Ting42896f72012-03-15 15:24:36 -07001636 mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME);
Mason Tang2bebde72010-08-04 17:20:24 -07001637
1638 String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
1639
1640 // start duplicate calendars query
1641 mHandler.startQuery(TOKEN_QUERY_DUPLICATE_CALENDARS, null, Calendars.CONTENT_URI,
1642 CALENDARS_PROJECTION, CALENDARS_DUPLICATE_NAME_WHERE,
1643 new String[] {displayName}, null);
1644
Michael Chan83efa2d2012-04-24 17:42:21 -07001645 mEventOrganizerEmail = mEventCursor.getString(EVENT_INDEX_ORGANIZER);
1646 mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(mEventOrganizerEmail);
1647
Sara Ting0c0da942012-04-27 10:35:05 -07001648 if (!TextUtils.isEmpty(mEventOrganizerEmail) &&
1649 !mEventOrganizerEmail.endsWith(Utils.MACHINE_GENERATED_ADDRESS)) {
Michael Chan83efa2d2012-04-24 17:42:21 -07001650 mEventOrganizerDisplayName = mEventOrganizerEmail;
1651 }
1652
1653 if (!mIsOrganizer && !TextUtils.isEmpty(mEventOrganizerDisplayName)) {
1654 setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName);
Isaac Katzenelsonb7762cf2011-06-09 14:04:42 -07001655 setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE);
1656 } else {
1657 setVisibilityCommon(view, R.id.organizer_container, View.GONE);
1658 }
Mason Tang2bebde72010-08-04 17:20:24 -07001659 mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
RoboErik65eeb792011-08-08 12:54:00 -07001660 mCanModifyCalendar = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL)
1661 >= Calendars.CAL_ACCESS_CONTRIBUTOR;
1662 // TODO add "|| guestCanModify" after b/1299071 is fixed
1663 mCanModifyEvent = mCanModifyCalendar && mIsOrganizer;
Mason Tang2bebde72010-08-04 17:20:24 -07001664 mIsBusyFreeCalendar =
RoboErikef2add92011-06-09 14:49:53 -07001665 mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.CAL_ACCESS_FREEBUSY;
Michael Chan352e1a22010-09-28 05:15:08 -07001666
1667 if (!mIsBusyFreeCalendar) {
Michael Chan8ce3b7d2010-09-01 15:41:01 -07001668 Button b = (Button) mView.findViewById(R.id.edit);
Michael Chan352e1a22010-09-28 05:15:08 -07001669 b.setEnabled(true);
Michael Chan8ce3b7d2010-09-01 15:41:01 -07001670 b.setOnClickListener(new OnClickListener() {
1671 @Override
1672 public void onClick(View v) {
1673 doEdit();
Isaac Katzenelson10b60212011-06-28 17:59:14 -07001674 // For dialogs, just close the fragment
1675 // For full screen, close activity on phone, leave it for tablet
Isaac Katzenelson60f01c22011-06-03 15:42:01 -07001676 if (mIsDialog) {
1677 EventInfoFragment.this.dismiss();
1678 }
Isaac Katzenelson10b60212011-06-28 17:59:14 -07001679 else if (!mIsTabletConfig){
Isaac Katzenelson60f01c22011-06-03 15:42:01 -07001680 getActivity().finish();
1681 }
Michael Chan352e1a22010-09-28 05:15:08 -07001682 }
1683 });
Michael Chan8ce3b7d2010-09-01 15:41:01 -07001684 }
Mindy Pereiradd151692011-08-29 14:05:26 -07001685 View button;
Sara Ting42896f72012-03-15 15:24:36 -07001686 if (mCanModifyCalendar) {
Mindy Pereiradd151692011-08-29 14:05:26 -07001687 button = mView.findViewById(R.id.delete);
1688 if (button != null) {
Sara Ting42896f72012-03-15 15:24:36 -07001689 button.setEnabled(true);
1690 button.setVisibility(View.VISIBLE);
RoboErik65eeb792011-08-08 12:54:00 -07001691 }
Mindy Pereiradd151692011-08-29 14:05:26 -07001692 }
Sara Ting42896f72012-03-15 15:24:36 -07001693 if (mCanModifyEvent) {
Mindy Pereiradd151692011-08-29 14:05:26 -07001694 button = mView.findViewById(R.id.edit);
1695 if (button != null) {
Sara Ting42896f72012-03-15 15:24:36 -07001696 button.setEnabled(true);
1697 button.setVisibility(View.VISIBLE);
Isaac Katzenelson60f01c22011-06-03 15:42:01 -07001698 }
RoboErikd8353fb2011-07-21 11:28:11 -07001699 }
Sara Ting42896f72012-03-15 15:24:36 -07001700
Isaac Katzenelson7ef29812011-10-25 18:00:50 -07001701 if ((!mIsDialog && !mIsTabletConfig ||
1702 mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) && mMenu != null) {
RoboErikd8353fb2011-07-21 11:28:11 -07001703 mActivity.invalidateOptionsMenu();
RoboErik69be8552011-02-14 16:51:59 -08001704 }
Mason Tang2bebde72010-08-04 17:20:24 -07001705 } else {
Michael Chan71b221a2010-10-25 17:55:44 -07001706 setVisibilityCommon(view, R.id.calendar, View.GONE);
Erik259b8f92011-01-12 14:01:12 -08001707 sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS);
Mason Tang2bebde72010-08-04 17:20:24 -07001708 }
1709 }
1710
RoboErikd8353fb2011-07-21 11:28:11 -07001711 /**
1712 *
1713 */
1714 private void updateMenu() {
1715 if (mMenu == null) {
1716 return;
1717 }
1718 MenuItem delete = mMenu.findItem(R.id.info_action_delete);
1719 MenuItem edit = mMenu.findItem(R.id.info_action_edit);
1720 if (delete != null) {
1721 delete.setVisible(mCanModifyCalendar);
1722 delete.setEnabled(mCanModifyCalendar);
1723 }
1724 if (edit != null) {
RoboErik65eeb792011-08-08 12:54:00 -07001725 edit.setVisible(mCanModifyEvent);
1726 edit.setEnabled(mCanModifyEvent);
RoboErikd8353fb2011-07-21 11:28:11 -07001727 }
1728 }
1729
Mason Tang2bebde72010-08-04 17:20:24 -07001730 private void updateAttendees(View view) {
Isaac Katzenelsonfae6d5c2011-06-16 15:43:41 -07001731 if (mAcceptedAttendees.size() + mDeclinedAttendees.size() +
1732 mTentativeAttendees.size() + mNoResponseAttendees.size() > 0) {
RoboErikc7f3c382011-08-31 14:43:24 -07001733 mLongAttendees.clearAttendees();
RoboErik4acb2fd2011-07-18 15:39:49 -07001734 (mLongAttendees).addAttendees(mAcceptedAttendees);
1735 (mLongAttendees).addAttendees(mDeclinedAttendees);
1736 (mLongAttendees).addAttendees(mTentativeAttendees);
1737 (mLongAttendees).addAttendees(mNoResponseAttendees);
Isaac Katzenelsonfae6d5c2011-06-16 15:43:41 -07001738 mLongAttendees.setEnabled(false);
1739 mLongAttendees.setVisibility(View.VISIBLE);
Isaac Katzenelson60f01c22011-06-03 15:42:01 -07001740 } else {
Isaac Katzenelsonfae6d5c2011-06-16 15:43:41 -07001741 mLongAttendees.setVisibility(View.GONE);
Michael Chan71b221a2010-10-25 17:55:44 -07001742 }
Sara Tingcb5f5682012-04-04 22:52:20 -07001743
1744 updateEmailAttendees();
1745 }
1746
1747 /**
1748 * Initializes the list of 'to' and 'cc' emails from the attendee list.
1749 */
1750 private void updateEmailAttendees() {
1751 // The declined attendees will go in the 'cc' line, all others will go in the 'to' line.
1752 mToEmails = new ArrayList<String>();
1753 for (Attendee attendee : mAcceptedAttendees) {
1754 addIfEmailable(mToEmails, attendee.mEmail);
1755 }
1756 for (Attendee attendee : mTentativeAttendees) {
1757 addIfEmailable(mToEmails, attendee.mEmail);
1758 }
1759 for (Attendee attendee : mNoResponseAttendees) {
1760 addIfEmailable(mToEmails, attendee.mEmail);
1761 }
1762 mCcEmails = new ArrayList<String>();
1763 for (Attendee attendee : this.mDeclinedAttendees) {
1764 addIfEmailable(mCcEmails, attendee.mEmail);
1765 }
1766
1767 // The meeting organizer doesn't appear as an attendee sometimes (particularly
1768 // when viewing someone else's calendar), so add the organizer now.
Michael Chan83efa2d2012-04-24 17:42:21 -07001769 if (mEventOrganizerEmail != null && !mToEmails.contains(mEventOrganizerEmail) &&
1770 !mCcEmails.contains(mEventOrganizerEmail)) {
1771 addIfEmailable(mToEmails, mEventOrganizerEmail);
Sara Tingcb5f5682012-04-04 22:52:20 -07001772 }
1773
1774 // The Email app behaves strangely when there is nothing in the 'mailto' part,
1775 // so move all the 'cc' emails to the 'to' list. Gmail works fine though.
1776 if (mToEmails.size() <= 0 && mCcEmails.size() > 0) {
1777 mToEmails.addAll(mCcEmails);
1778 mCcEmails.clear();
1779 }
1780
1781 if (mToEmails.size() <= 0) {
1782 setVisibilityCommon(mView, R.id.email_attendees_container, View.GONE);
1783 } else {
1784 setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE);
1785 }
Michael Chan8ce3b7d2010-09-01 15:41:01 -07001786 }
1787
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07001788 public void initReminders(View view, Cursor cursor) {
1789
1790 // Add reminders
RoboErikc7f3c382011-08-31 14:43:24 -07001791 mOriginalReminders.clear();
RoboErikc08957b2011-09-09 13:43:38 -07001792 mUnsupportedReminders.clear();
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07001793 while (cursor.moveToNext()) {
1794 int minutes = cursor.getInt(EditEventHelper.REMINDERS_INDEX_MINUTES);
1795 int method = cursor.getInt(EditEventHelper.REMINDERS_INDEX_METHOD);
RoboErike35a0322011-09-01 16:21:49 -07001796
RoboErikc08957b2011-09-09 13:43:38 -07001797 if (method != Reminders.METHOD_DEFAULT && !mReminderMethodValues.contains(method)) {
RoboErike35a0322011-09-01 16:21:49 -07001798 // Stash unsupported reminder types separately so we don't alter
1799 // them in the UI
1800 mUnsupportedReminders.add(ReminderEntry.valueOf(minutes, method));
1801 } else {
1802 mOriginalReminders.add(ReminderEntry.valueOf(minutes, method));
1803 }
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07001804 }
1805 // Sort appropriately for display (by time, then type)
1806 Collections.sort(mOriginalReminders);
1807
RoboErik8b5743c2011-09-27 16:07:09 -07001808 if (mUserModifiedReminders) {
1809 // If the user has changed the list of reminders don't change what's
1810 // shown.
1811 return;
1812 }
1813
RoboErik5d40a382011-09-02 14:33:09 -07001814 LinearLayout parent = (LinearLayout) mScrollView
1815 .findViewById(R.id.reminder_items_container);
1816 if (parent != null) {
1817 parent.removeAllViews();
1818 }
RoboErikc08957b2011-09-09 13:43:38 -07001819 if (mReminderViews != null) {
1820 mReminderViews.clear();
1821 }
RoboErik5d40a382011-09-02 14:33:09 -07001822
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07001823 if (mHasAlarm) {
1824 ArrayList<ReminderEntry> reminders = mOriginalReminders;
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07001825 // Insert any minute values that aren't represented in the minutes list.
1826 for (ReminderEntry re : reminders) {
1827 EventViewUtils.addMinutesToList(
1828 mActivity, mReminderMinuteValues, mReminderMinuteLabels, re.getMinutes());
1829 }
1830 // Create a UI element for each reminder. We display all of the reminders we get
1831 // from the provider, even if the count exceeds the calendar maximum. (Also, for
1832 // a new event, we won't have a maxReminders value available.)
1833 for (ReminderEntry re : reminders) {
RoboErike35a0322011-09-01 16:21:49 -07001834 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
1835 mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
RoboErik8b5743c2011-09-27 16:07:09 -07001836 mReminderMethodLabels, re, Integer.MAX_VALUE, mReminderChangeListener);
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07001837 }
Sara Ting06f3d6d2012-03-23 17:19:22 -07001838 EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
RoboErike35a0322011-09-01 16:21:49 -07001839 // TODO show unsupported reminder types in some fashion.
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07001840 }
1841 }
1842
Mason Tang2bebde72010-08-04 17:20:24 -07001843 void updateResponse(View view) {
1844 // we only let the user accept/reject/etc. a meeting if:
1845 // a) you can edit the event's containing calendar AND
1846 // b) you're not the organizer and only attendee AND
1847 // c) organizerCanRespond is enabled for the calendar
1848 // (if the attendee data has been hidden, the visible number of attendees
1849 // will be 1 -- the calendar owner's).
1850 // (there are more cases involved to be 100% accurate, such as
1851 // paying attention to whether or not an attendee status was
1852 // included in the feed, but we're currently omitting those corner cases
1853 // for simplicity).
Michael Chan352e1a22010-09-28 05:15:08 -07001854
1855 // TODO Switch to EditEventHelper.canRespond when this class uses CalendarEventModel.
Michael Chand785cd52010-09-03 13:57:19 -07001856 if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) ||
Michael Chan352e1a22010-09-28 05:15:08 -07001857 (mIsOrganizer && !mOwnerCanRespond)) {
Mason Tang2bebde72010-08-04 17:20:24 -07001858 setVisibilityCommon(view, R.id.response_container, View.GONE);
1859 return;
1860 }
1861
1862 setVisibilityCommon(view, R.id.response_container, View.VISIBLE);
1863
Mason Tang2bebde72010-08-04 17:20:24 -07001864
Michael Chanf92f46b2010-10-27 18:59:05 -07001865 int response;
Isaac Katzenelson6bcafcf2012-05-02 18:25:36 -07001866 if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
RoboErik5afc0832011-09-27 14:14:09 -07001867 response = mUserSetResponse;
Isaac Katzenelson6bcafcf2012-05-02 18:25:36 -07001868 } else if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) {
Michael Chanf92f46b2010-10-27 18:59:05 -07001869 response = mAttendeeResponseFromIntent;
Mason Tang2bebde72010-08-04 17:20:24 -07001870 } else {
Michael Chanf92f46b2010-10-27 18:59:05 -07001871 response = mOriginalAttendeeResponse;
Mason Tang2bebde72010-08-04 17:20:24 -07001872 }
Michael Chanf92f46b2010-10-27 18:59:05 -07001873
1874 int buttonToCheck = findButtonIdForResponse(response);
1875 RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.response_value);
1876 radioGroup.check(buttonToCheck); // -1 clear all radio buttons
1877 radioGroup.setOnCheckedChangeListener(this);
Mason Tang2bebde72010-08-04 17:20:24 -07001878 }
1879
1880 private void setTextCommon(View view, int id, CharSequence text) {
1881 TextView textView = (TextView) view.findViewById(id);
1882 if (textView == null)
1883 return;
1884 textView.setText(text);
1885 }
1886
1887 private void setVisibilityCommon(View view, int id, int visibility) {
1888 View v = view.findViewById(id);
1889 if (v != null) {
1890 v.setVisibility(visibility);
1891 }
1892 return;
1893 }
1894
1895 /**
1896 * Taken from com.google.android.gm.HtmlConversationActivity
1897 *
1898 * Send the intent that shows the Contact info corresponding to the email address.
1899 */
1900 public void showContactInfo(Attendee attendee, Rect rect) {
1901 // First perform lookup query to find existing contact
1902 final ContentResolver resolver = getActivity().getContentResolver();
1903 final String address = attendee.mEmail;
1904 final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI,
1905 Uri.encode(address));
1906 final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri);
1907
1908 if (lookupUri != null) {
1909 // Found matching contact, trigger QuickContact
1910 QuickContact.showQuickContact(getActivity(), rect, lookupUri,
1911 QuickContact.MODE_MEDIUM, null);
1912 } else {
1913 // No matching contact, ask user to create one
1914 final Uri mailUri = Uri.fromParts("mailto", address, null);
1915 final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri);
1916
1917 // Pass along full E-mail string for possible create dialog
1918 Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null);
1919 intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString());
1920
1921 // Only provide personal name hint if we have one
1922 final String senderPersonal = attendee.mName;
1923 if (!TextUtils.isEmpty(senderPersonal)) {
1924 intent.putExtra(Intents.Insert.NAME, senderPersonal);
1925 }
1926
1927 startActivity(intent);
1928 }
1929 }
Erikcb811892010-09-28 13:44:19 -07001930
1931 @Override
RoboErik7c6236d2011-02-24 16:41:18 -08001932 public void onPause() {
1933 mIsPaused = true;
1934 mHandler.removeCallbacks(onDeleteRunnable);
1935 super.onPause();
Isaac Katzenelsonb0a313a2011-10-24 11:41:56 -07001936 // Remove event deletion alert box since it is being rebuild in the OnResume
1937 // This is done to get the same behavior on OnResume since the AlertDialog is gone on
1938 // rotation but not if you press the HOME key
1939 if (mDeleteDialogVisible && mDeleteHelper != null) {
1940 mDeleteHelper.dismissAlertDialog();
1941 mDeleteHelper = null;
1942 }
RoboErik7c6236d2011-02-24 16:41:18 -08001943 }
1944
1945 @Override
1946 public void onResume() {
1947 super.onResume();
1948 mIsPaused = false;
1949 if (mDismissOnResume) {
1950 mHandler.post(onDeleteRunnable);
1951 }
Isaac Katzenelsonb0a313a2011-10-24 11:41:56 -07001952 // Display the "delete confirmation" dialog if needed
1953 if (mDeleteDialogVisible) {
1954 mDeleteHelper = new DeleteEventHelper(
1955 mContext, mActivity,
1956 !mIsDialog && !mIsTabletConfig /* exitWhenDone */);
1957 mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener());
1958 mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
1959 }
RoboErik7c6236d2011-02-24 16:41:18 -08001960 }
1961
1962 @Override
Erikcb811892010-09-28 13:44:19 -07001963 public void eventsChanged() {
1964 }
1965
1966 @Override
Erikcb811892010-09-28 13:44:19 -07001967 public long getSupportedEventTypes() {
1968 return EventType.EVENTS_CHANGED;
1969 }
1970
1971 @Override
Erikcb811892010-09-28 13:44:19 -07001972 public void handleEvent(EventInfo event) {
RoboErikf836d4a2011-07-22 17:01:27 -07001973 if (event.eventType == EventType.EVENTS_CHANGED && mHandler != null) {
Erikcb811892010-09-28 13:44:19 -07001974 // reload the data
Isaac Katzenelson80d640f2012-04-20 09:21:28 -07001975 reloadEvents();
Erikcb811892010-09-28 13:44:19 -07001976 }
Erikcb811892010-09-28 13:44:19 -07001977 }
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07001978
Isaac Katzenelson80d640f2012-04-20 09:21:28 -07001979 public void reloadEvents() {
1980 mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
1981 null, null, null);
1982 }
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07001983
1984 @Override
1985 public void onClick(View view) {
1986
1987 // This must be a click on one of the "remove reminder" buttons
1988 LinearLayout reminderItem = (LinearLayout) view.getParent();
1989 LinearLayout parent = (LinearLayout) reminderItem.getParent();
1990 parent.removeView(reminderItem);
1991 mReminderViews.remove(reminderItem);
RoboErik8b5743c2011-09-27 16:07:09 -07001992 mUserModifiedReminders = true;
Sara Ting06f3d6d2012-03-23 17:19:22 -07001993 EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07001994 }
1995
1996
1997 /**
1998 * Add a new reminder when the user hits the "add reminder" button. We use the default
1999 * reminder time and method.
2000 */
2001 private void addReminder() {
2002 // TODO: when adding a new reminder, make it different from the
2003 // last one in the list (if any).
2004 if (mDefaultReminderMinutes == GeneralPreferences.NO_REMINDER) {
2005 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
RoboErik8b5743c2011-09-27 16:07:09 -07002006 mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
2007 mReminderMethodLabels,
2008 ReminderEntry.valueOf(GeneralPreferences.REMINDER_DEFAULT_TIME), mMaxReminders,
2009 mReminderChangeListener);
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07002010 } else {
2011 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
RoboErik8b5743c2011-09-27 16:07:09 -07002012 mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
2013 mReminderMethodLabels, ReminderEntry.valueOf(mDefaultReminderMinutes),
2014 mMaxReminders, mReminderChangeListener);
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07002015 }
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07002016
Sara Ting06f3d6d2012-03-23 17:19:22 -07002017 EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
2018 }
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07002019
RoboErike35a0322011-09-01 16:21:49 -07002020 synchronized private void prepareReminders() {
2021 // Nothing to do if we've already built these lists _and_ we aren't
2022 // removing not allowed methods
2023 if (mReminderMinuteValues != null && mReminderMinuteLabels != null
2024 && mReminderMethodValues != null && mReminderMethodLabels != null
2025 && mCalendarAllowedReminders == null) {
2026 return;
2027 }
2028 // Load the labels and corresponding numeric values for the minutes and methods lists
2029 // from the assets. If we're switching calendars, we need to clear and re-populate the
2030 // lists (which may have elements added and removed based on calendar properties). This
2031 // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a
2032 // new event that aren't in the default set.
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07002033 Resources r = mActivity.getResources();
2034 mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values);
2035 mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels);
2036 mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values);
2037 mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels);
RoboErike35a0322011-09-01 16:21:49 -07002038
2039 // Remove any reminder methods that aren't allowed for this calendar. If this is
2040 // a new event, mCalendarAllowedReminders may not be set the first time we're called.
RoboErike35a0322011-09-01 16:21:49 -07002041 if (mCalendarAllowedReminders != null) {
2042 EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels,
2043 mCalendarAllowedReminders);
2044 }
2045 if (mView != null) {
2046 mView.invalidate();
2047 }
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07002048 }
2049
2050
2051 private boolean saveReminders() {
2052 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3);
2053
2054 // Read reminders from UI
2055 mReminders = EventViewUtils.reminderItemsToReminders(mReminderViews,
2056 mReminderMinuteValues, mReminderMethodValues);
RoboErike35a0322011-09-01 16:21:49 -07002057 mOriginalReminders.addAll(mUnsupportedReminders);
2058 Collections.sort(mOriginalReminders);
2059 mReminders.addAll(mUnsupportedReminders);
2060 Collections.sort(mReminders);
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07002061
2062 // Check if there are any changes in the reminder
2063 boolean changed = EditEventHelper.saveReminders(ops, mEventId, mReminders,
2064 mOriginalReminders, false /* no force save */);
2065
2066 if (!changed) {
2067 return false;
2068 }
2069
2070 // save new reminders
2071 AsyncQueryService service = new AsyncQueryService(getActivity());
2072 service.startBatch(0, null, Calendars.CONTENT_URI.getAuthority(), ops, 0);
2073 // Update the "hasAlarm" field for the event
2074 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
2075 int len = mReminders.size();
2076 boolean hasAlarm = len > 0;
2077 if (hasAlarm != mHasAlarm) {
2078 ContentValues values = new ContentValues();
2079 values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0);
2080 service.startUpdate(0, null, uri, values, null, null, 0);
2081 }
2082 return true;
2083 }
2084
2085 /**
Sara Ting42896f72012-03-15 15:24:36 -07002086 * Adds the attendee's email to the list if:
2087 * (1) the attendee is not a resource like a conference room or another calendar.
2088 * Catch most of these by filtering out suffix calendar.google.com.
2089 * (2) the attendee is not the viewer, to prevent mailing himself.
2090 */
2091 private void addIfEmailable(ArrayList<String> emailList, String email) {
Sara Tingddbc0022012-04-26 17:08:46 -07002092 if (Utils.isValidEmail(email) && !email.equals(mSyncAccountName)) {
Sara Ting42896f72012-03-15 15:24:36 -07002093 emailList.add(email);
2094 }
2095 }
2096
2097 /**
2098 * Email all the attendees of the event, except for the viewer (so as to not email
2099 * himself) and resources like conference rooms.
2100 */
2101 private void emailAttendees() {
Sara Tingd9d123d2012-04-23 15:50:46 -07002102 String eventTitle = (mTitle == null || mTitle.getText() == null) ? null :
2103 mTitle.getText().toString();
2104 Intent emailIntent = Utils.createEmailAttendeesIntent(getActivity().getResources(),
2105 eventTitle, mToEmails, mCcEmails, mCalendarOwnerAccount);
2106 startActivity(emailIntent);
Sara Ting42896f72012-03-15 15:24:36 -07002107 }
2108
2109 /**
Isaac Katzenelson1ce35aa2011-07-05 10:03:28 -07002110 * Loads an integer array asset into a list.
2111 */
2112 private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) {
2113 int[] vals = r.getIntArray(resNum);
2114 int size = vals.length;
2115 ArrayList<Integer> list = new ArrayList<Integer>(size);
2116
2117 for (int i = 0; i < size; i++) {
2118 list.add(vals[i]);
2119 }
2120
2121 return list;
2122 }
2123 /**
2124 * Loads a String array asset into a list.
2125 */
2126 private static ArrayList<String> loadStringArray(Resources r, int resNum) {
2127 String[] labels = r.getStringArray(resNum);
2128 ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels));
2129 return list;
2130 }
2131
Isaac Katzenelsonb33cdf42011-10-14 18:15:18 -07002132 public void onDeleteStarted() {
2133 mEventDeletionStarted = true;
2134 }
2135
Isaac Katzenelsonb0a313a2011-10-24 11:41:56 -07002136 private Dialog.OnDismissListener createDeleteOnDismissListener() {
2137 return new Dialog.OnDismissListener() {
2138 @Override
2139 public void onDismiss(DialogInterface dialog) {
2140 // Since OnPause will force the dialog to dismiss , do
2141 // not change the dialog status
2142 if (!mIsPaused) {
2143 mDeleteDialogVisible = false;
2144 }
2145 }
2146 };
2147 }
2148
Isaac Katzenelson80d640f2012-04-20 09:21:28 -07002149 public long getEventId() {
2150 return mEventId;
2151 }
2152
2153 public long getStartMillis() {
2154 return mStartMillis;
2155 }
2156 public long getEndMillis() {
2157 return mEndMillis;
2158 }
2159
2160
2161
Mason Tang2bebde72010-08-04 17:20:24 -07002162}