Code drop from //branches/cupcake/...@124589
[android/platform/packages/apps/Calendar.git] / src / com / android / calendar / EventInfoActivity.java
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.calendar;
18
19 import static android.provider.Calendar.EVENT_BEGIN_TIME;
20 import static android.provider.Calendar.EVENT_END_TIME;
21
22 import android.app.Activity;
23 import android.content.ContentResolver;
24 import android.content.ContentUris;
25 import android.content.Intent;
26 import android.content.SharedPreferences;
27 import android.content.res.Resources;
28 import android.database.Cursor;
29 import android.graphics.PorterDuff;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.pim.EventRecurrence;
33 import android.preference.PreferenceManager;
34 import android.provider.Calendar;
35 import android.provider.Calendar.Attendees;
36 import android.provider.Calendar.Calendars;
37 import android.provider.Calendar.Events;
38 import android.provider.Calendar.Reminders;
39 import android.text.format.DateFormat;
40 import android.text.format.DateUtils;
41 import android.text.format.Time;
42 import android.view.KeyEvent;
43 import android.view.Menu;
44 import android.view.MenuItem;
45 import android.view.View;
46 import android.widget.ArrayAdapter;
47 import android.widget.ImageView;
48 import android.widget.LinearLayout;
49 import android.widget.Spinner;
50 import android.widget.TextView;
51
52 import java.util.ArrayList;
53 import java.util.Arrays;
54
55 public class EventInfoActivity extends Activity implements View.OnClickListener {
56     private static final int MAX_REMINDERS = 5;
57
58     private static final String[] EVENT_PROJECTION = new String[] {
59         Events._ID,             // 0  do not remove; used in DeleteEventHelper
60         Events.TITLE,           // 1  do not remove; used in DeleteEventHelper
61         Events.RRULE,           // 2  do not remove; used in DeleteEventHelper
62         Events.ALL_DAY,         // 3  do not remove; used in DeleteEventHelper
63         Events.CALENDAR_ID,     // 4  do not remove; used in DeleteEventHelper
64         Events.DTSTART,         // 5  do not remove; used in DeleteEventHelper
65         Events._SYNC_ID,        // 6  do not remove; used in DeleteEventHelper
66         Events.EVENT_TIMEZONE,  // 7  do not remove; used in DeleteEventHelper
67         Events.DESCRIPTION,     // 8
68         Events.EVENT_LOCATION,  // 9
69         Events.HAS_ALARM,       // 10
70         Events.ACCESS_LEVEL,    // 11
71         Events.COLOR,           // 12
72     };
73     private static final int EVENT_INDEX_ID = 0;
74     private static final int EVENT_INDEX_TITLE = 1;
75     private static final int EVENT_INDEX_RRULE = 2;
76     private static final int EVENT_INDEX_ALL_DAY = 3;
77     private static final int EVENT_INDEX_CALENDAR_ID = 4;
78     private static final int EVENT_INDEX_EVENT_TIMEZONE = 7;
79     private static final int EVENT_INDEX_DESCRIPTION = 8;
80     private static final int EVENT_INDEX_EVENT_LOCATION = 9;
81     private static final int EVENT_INDEX_HAS_ALARM = 10;
82     private static final int EVENT_INDEX_ACCESS_LEVEL = 11;
83     private static final int EVENT_INDEX_COLOR = 12;
84
85     private static final String[] ATTENDEES_PROJECTION = new String[] {
86         Attendees._ID,                      // 0
87         Attendees.ATTENDEE_RELATIONSHIP,    // 1
88         Attendees.ATTENDEE_STATUS,          // 2
89     };
90     private static final int ATTENDEES_INDEX_RELATIONSHIP = 1;
91     private static final int ATTENDEES_INDEX_STATUS = 2;
92     private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=%d";
93
94     private static final String[] CALENDARS_PROJECTION = new String[] {
95         Calendars._ID,          // 0
96         Calendars.DISPLAY_NAME, // 1
97     };
98     private static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
99     private static final String CALENDARS_WHERE = Calendars._ID + "=%d";
100
101     private static final String[] REMINDERS_PROJECTION = new String[] {
102         Reminders._ID,      // 0
103         Reminders.MINUTES,  // 1
104     };
105     private static final int REMINDERS_INDEX_MINUTES = 1;
106     private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=%d AND (" +
107             Reminders.METHOD + "=" + Reminders.METHOD_ALERT + " OR " + Reminders.METHOD + "=" +
108             Reminders.METHOD_DEFAULT + ")";
109
110     private static final int MENU_GROUP_REMINDER = 1;
111     private static final int MENU_GROUP_EDIT = 2;
112     private static final int MENU_GROUP_DELETE = 3;
113
114     private static final int MENU_ADD_REMINDER = 1;
115     private static final int MENU_EDIT = 2;
116     private static final int MENU_DELETE = 3;
117
118     private static final int ATTENDEE_NO_RESPONSE = -1;
119     private static final int[] ATTENDEE_VALUES = {
120             ATTENDEE_NO_RESPONSE,
121             Attendees.ATTENDEE_STATUS_ACCEPTED,
122             Attendees.ATTENDEE_STATUS_TENTATIVE,
123             Attendees.ATTENDEE_STATUS_DECLINED,
124     };
125
126     private LinearLayout mRemindersContainer;
127
128     private Uri mUri;
129     private long mEventId;
130     private Cursor mEventCursor;
131     private Cursor mAttendeesCursor;
132     private Cursor mCalendarsCursor;
133
134     private long mStartMillis;
135     private long mEndMillis;
136     private int mVisibility = Calendars.NO_ACCESS;
137     private int mRelationship = Attendees.RELATIONSHIP_ORGANIZER;
138
139     private ArrayList<Integer> mOriginalMinutes = new ArrayList<Integer>();
140     private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0);
141     private ArrayList<Integer> mReminderValues;
142     private ArrayList<String> mReminderLabels;
143     private int mDefaultReminderMinutes;
144
145     private DeleteEventHelper mDeleteEventHelper;
146
147     private int mResponseOffset;
148
149     // This is called when one of the "remove reminder" buttons is selected.
150     public void onClick(View v) {
151         LinearLayout reminderItem = (LinearLayout) v.getParent();
152         LinearLayout parent = (LinearLayout) reminderItem.getParent();
153         parent.removeView(reminderItem);
154         mReminderItems.remove(reminderItem);
155         updateRemindersVisibility();
156     }
157
158     @Override
159     protected void onCreate(Bundle icicle) {
160         super.onCreate(icicle);
161
162         // Event cursor
163         Intent intent = getIntent();
164         mUri = intent.getData();
165         ContentResolver cr = getContentResolver();
166         mStartMillis = intent.getLongExtra(EVENT_BEGIN_TIME, 0);
167         mEndMillis = intent.getLongExtra(EVENT_END_TIME, 0);
168         mEventCursor = managedQuery(mUri, EVENT_PROJECTION, null, null);
169         if (initEventCursor()) {
170             // The cursor is empty. This can happen if the event was deleted.
171             finish();
172             return;
173         }
174
175         setContentView(R.layout.event_info_activity);
176
177         // Attendees cursor
178         Uri uri = Attendees.CONTENT_URI;
179         String where = String.format(ATTENDEES_WHERE, mEventId);
180         mAttendeesCursor = managedQuery(uri, ATTENDEES_PROJECTION, where, null);
181         initAttendeesCursor();
182
183         // Calendars cursor
184         uri = Calendars.CONTENT_URI;
185         where = String.format(CALENDARS_WHERE, mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID));
186         mCalendarsCursor = managedQuery(uri, CALENDARS_PROJECTION, where, null);
187         initCalendarsCursor();
188
189         Resources res = getResources();
190
191         if (mVisibility >= Calendars.CONTRIBUTOR_ACCESS &&
192                 mRelationship == Attendees.RELATIONSHIP_ATTENDEE) {
193             setTitle(res.getString(R.string.event_info_title_invite));
194         } else {
195             setTitle(res.getString(R.string.event_info_title));
196         }
197
198         // Initialize the reminder values array.
199         Resources r = getResources();
200         String[] strings = r.getStringArray(R.array.reminder_minutes_values);
201         int size = strings.length;
202         ArrayList<Integer> list = new ArrayList<Integer>(size);
203         for (int i = 0 ; i < size ; i++) {
204             list.add(Integer.parseInt(strings[i]));
205         }
206         mReminderValues = list;
207         String[] labels = r.getStringArray(R.array.reminder_minutes_labels);
208         mReminderLabels = new ArrayList<String>(Arrays.asList(labels));
209
210         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
211         String durationString =
212                 prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0");
213         mDefaultReminderMinutes = Integer.parseInt(durationString);
214
215         mRemindersContainer = (LinearLayout) findViewById(R.id.reminders_container);
216
217         // Reminders cursor
218         boolean hasAlarm = mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0;
219         if (hasAlarm) {
220             uri = Reminders.CONTENT_URI;
221             where = String.format(REMINDERS_WHERE, mEventId);
222             Cursor reminderCursor = cr.query(uri, REMINDERS_PROJECTION, where, null, null);
223             try {
224                 // First pass: collect all the custom reminder minutes (e.g.,
225                 // a reminder of 8 minutes) into a global list.
226                 while (reminderCursor.moveToNext()) {
227                     int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
228                     EditEvent.addMinutesToList(this, mReminderValues, mReminderLabels, minutes);
229                 }
230                 
231                 // Second pass: create the reminder spinners
232                 reminderCursor.moveToPosition(-1);
233                 while (reminderCursor.moveToNext()) {
234                     int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
235                     mOriginalMinutes.add(minutes);
236                     EditEvent.addReminder(this, this, mReminderItems, mReminderValues,
237                             mReminderLabels, minutes);
238                 }
239             } finally {
240                 reminderCursor.close();
241             }
242         }
243
244         updateView();
245         updateRemindersVisibility();
246
247         mDeleteEventHelper = new DeleteEventHelper(this, true /* exit when done */);
248     }
249
250     @Override
251     protected void onResume() {
252         super.onResume();
253         if (initEventCursor()) {
254             // The cursor is empty. This can happen if the event was deleted.
255             finish();
256             return;
257         }
258         initAttendeesCursor();
259         initCalendarsCursor();
260     }
261
262     /**
263      * Initializes the event cursor, which is expected to point to the first
264      * (and only) result from a query.
265      * @return true if the cursor is empty.
266      */
267     private boolean initEventCursor() {
268         if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
269             return true;
270         }
271         mEventCursor.moveToFirst();
272         mVisibility = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL);
273         mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
274         return false;
275     }
276
277     private void initAttendeesCursor() {
278         if (mAttendeesCursor != null) {
279             if (mAttendeesCursor.moveToFirst()) {
280                 mRelationship = mAttendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP);
281             }
282         }
283     }
284
285     private void initCalendarsCursor() {
286         if (mCalendarsCursor != null) {
287             mCalendarsCursor.moveToFirst();
288         }
289     }
290
291     @Override
292     public void onPause() {
293         super.onPause();
294         ContentResolver cr = getContentResolver();
295         ArrayList<Integer> reminderMinutes = EditEvent.reminderItemsToMinutes(mReminderItems,
296                 mReminderValues);
297         EditEvent.saveReminders(cr, mEventId, reminderMinutes, mOriginalMinutes);
298         saveResponse();
299     }
300
301     @Override
302     public boolean onCreateOptionsMenu(Menu menu) {
303         MenuItem item;
304         item = menu.add(MENU_GROUP_REMINDER, MENU_ADD_REMINDER, 0,
305                 R.string.add_new_reminder);
306         item.setIcon(R.drawable.ic_menu_reminder);
307         item.setAlphabeticShortcut('r');
308
309         item = menu.add(MENU_GROUP_EDIT, MENU_EDIT, 0, R.string.edit_event_label);
310         item.setIcon(android.R.drawable.ic_menu_edit);
311         item.setAlphabeticShortcut('e');
312
313         item = menu.add(MENU_GROUP_DELETE, MENU_DELETE, 0, R.string.delete_event_label);
314         item.setIcon(android.R.drawable.ic_menu_delete);
315
316         return super.onCreateOptionsMenu(menu);
317     }
318
319     @Override
320     public boolean onPrepareOptionsMenu(Menu menu) {
321         // Cannot add reminders to a shared calendar with only free/busy
322         // permissions
323         if (mVisibility >= Calendars.READ_ACCESS && mReminderItems.size() < MAX_REMINDERS) {
324             menu.setGroupVisible(MENU_GROUP_REMINDER, true);
325             menu.setGroupEnabled(MENU_GROUP_REMINDER, true);
326         } else {
327             menu.setGroupVisible(MENU_GROUP_REMINDER, false);
328             menu.setGroupEnabled(MENU_GROUP_REMINDER, false);
329         }
330
331         if (mVisibility >= Calendars.CONTRIBUTOR_ACCESS &&
332                 mRelationship >= Attendees.RELATIONSHIP_ORGANIZER) {
333             menu.setGroupVisible(MENU_GROUP_EDIT, true);
334             menu.setGroupEnabled(MENU_GROUP_EDIT, true);
335             menu.setGroupVisible(MENU_GROUP_DELETE, true);
336             menu.setGroupEnabled(MENU_GROUP_DELETE, true);
337         } else {
338             menu.setGroupVisible(MENU_GROUP_EDIT, false);
339             menu.setGroupEnabled(MENU_GROUP_EDIT, false);
340             menu.setGroupVisible(MENU_GROUP_DELETE, false);
341             menu.setGroupEnabled(MENU_GROUP_DELETE, false);
342         }
343
344         return super.onPrepareOptionsMenu(menu);
345     }
346
347     @Override
348     public boolean onOptionsItemSelected(MenuItem item) {
349         super.onOptionsItemSelected(item);
350         switch (item.getItemId()) {
351         case MENU_ADD_REMINDER:
352             // TODO: when adding a new reminder, make it different from the
353             // last one in the list (if any).
354             if (mDefaultReminderMinutes == 0) {
355                 EditEvent.addReminder(this, this, mReminderItems,
356                         mReminderValues, mReminderLabels, 10 /* minutes */);
357             } else {
358                 EditEvent.addReminder(this, this, mReminderItems,
359                         mReminderValues, mReminderLabels, mDefaultReminderMinutes);
360             }
361             updateRemindersVisibility();
362             break;
363         case MENU_EDIT:
364             doEdit();
365             break;
366         case MENU_DELETE:
367             doDelete();
368             break;
369         }
370         return true;
371     }
372
373     @Override
374     public boolean onKeyDown(int keyCode, KeyEvent event) {
375         if (keyCode == KeyEvent.KEYCODE_DEL) {
376             doDelete();
377             return true;
378         }
379         return super.onKeyDown(keyCode, event);
380     }
381
382     private void updateRemindersVisibility() {
383         if (mReminderItems.size() == 0) {
384             mRemindersContainer.setVisibility(View.GONE);
385         } else {
386             mRemindersContainer.setVisibility(View.VISIBLE);
387         }
388     }
389
390     private void saveResponse() {
391         if (mAttendeesCursor == null) {
392             return;
393         }
394         Spinner spinner = (Spinner) findViewById(R.id.response_value);
395         int position = spinner.getSelectedItemPosition() - mResponseOffset;
396         if (position <= 0) {
397             return;
398         }
399
400         int status = ATTENDEE_VALUES[position];
401         mAttendeesCursor.updateInt(ATTENDEES_INDEX_STATUS, status);
402         mAttendeesCursor.commitUpdates();
403     }
404
405     private int findResponseIndexFor(int response) {
406         int size = ATTENDEE_VALUES.length;
407         for (int index = 0; index < size; index++) {
408             if (ATTENDEE_VALUES[index] == response) {
409                 return index;
410             }
411         }
412         return 0;
413     }
414
415     private void doEdit() {
416         Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
417         Intent intent = new Intent(Intent.ACTION_EDIT, uri);
418         intent.putExtra(Calendar.EVENT_BEGIN_TIME, mStartMillis);
419         intent.putExtra(Calendar.EVENT_END_TIME, mEndMillis);
420         intent.setClass(EventInfoActivity.this, EditEvent.class);
421         startActivity(intent);
422         finish();
423     }
424
425     private void doDelete() {
426         mDeleteEventHelper.delete(mStartMillis, mEndMillis, mEventCursor, -1);
427     }
428
429     private void updateView() {
430         if (mEventCursor == null) {
431             return;
432         }
433         Resources res = getResources();
434         ContentResolver cr = getContentResolver();
435
436         String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
437         if (eventName == null || eventName.length() == 0) {
438             eventName = res.getString(R.string.no_title_label);
439         }
440
441         boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
442         String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
443         String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
444         String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
445         boolean hasAlarm = mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0;
446         String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
447         int color = mEventCursor.getInt(EVENT_INDEX_COLOR) & 0xbbffffff;
448
449         ImageView stripe = (ImageView) findViewById(R.id.vertical_stripe);
450         stripe.getBackground().setColorFilter(color, PorterDuff.Mode.SRC_IN);
451
452         // What
453         if (eventName != null) {
454             setTextCommon(R.id.title, eventName);
455         }
456
457         // When
458         String when;
459         int flags;
460         if (allDay) {
461             flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE;
462         } else {
463             flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE;
464             if (DateFormat.is24HourFormat(this)) {
465                 flags |= DateUtils.FORMAT_24HOUR;
466             }
467         }
468         when = DateUtils.formatDateRange(this, mStartMillis, mEndMillis, flags);
469         setTextCommon(R.id.when, when);
470
471         // Show the event timezone if it is different from the local timezone
472         Time time = new Time();
473         String localTimezone = time.timezone;
474         if (allDay) {
475             localTimezone = Time.TIMEZONE_UTC;
476         }
477         if (eventTimezone != null && !localTimezone.equals(eventTimezone) && !allDay) {
478             setTextCommon(R.id.timezone, localTimezone);
479         } else {
480             setVisibilityCommon(R.id.timezone_container, View.GONE);
481         }
482
483         // Repeat
484         if (rRule != null) {
485             EventRecurrence eventRecurrence = new EventRecurrence();
486             eventRecurrence.parse(rRule);
487             Time date = new Time();
488             if (allDay) {
489                 date.timezone = Time.TIMEZONE_UTC;
490             }
491             date.set(mStartMillis);
492             eventRecurrence.setStartDate(date);
493             String repeatString = eventRecurrence.getRepeatString();
494             setTextCommon(R.id.repeat, repeatString);
495         } else {
496             setVisibilityCommon(R.id.repeat_container, View.GONE);
497         }
498
499         // Where
500         if (location == null || location.length() == 0) {
501             setVisibilityCommon(R.id.where, View.GONE);
502         } else {
503             setTextCommon(R.id.where, location);
504         }
505
506         // Description
507         if (description == null || description.length() == 0) {
508             setVisibilityCommon(R.id.description, View.GONE);
509         } else {
510             setTextCommon(R.id.description, description);
511         }
512
513         // Calendar
514         if (mCalendarsCursor != null) {
515             mCalendarsCursor.moveToFirst();
516             String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
517             setTextCommon(R.id.calendar, calendarName);
518         } else {
519             setVisibilityCommon(R.id.calendar_container, View.GONE);
520         }
521
522         // Response
523         updateResponse();
524     }
525
526     void updateResponse() {
527         if (mVisibility < Calendars.CONTRIBUTOR_ACCESS ||
528                 mRelationship != Attendees.RELATIONSHIP_ATTENDEE) {
529             setVisibilityCommon(R.id.response_container, View.GONE);
530             return;
531         }
532
533         setVisibilityCommon(R.id.response_container, View.VISIBLE);
534
535         Spinner spinner = (Spinner) findViewById(R.id.response_value);
536
537         int response = ATTENDEE_NO_RESPONSE;
538         if (mAttendeesCursor != null) {
539             response = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
540         }
541         mResponseOffset = 0;
542
543         /* If the user has previously responded to this event
544          * we should not allow them to select no response again.
545          * Switch the entries to a set of entries without the
546          * no response option.
547          */
548         if ((response != Attendees.ATTENDEE_STATUS_INVITED)
549                 && (response != ATTENDEE_NO_RESPONSE)
550                 && (response != Attendees.ATTENDEE_STATUS_NONE)) {
551             CharSequence[] entries;
552             entries = getResources().getTextArray(R.array.response_labels2);
553             mResponseOffset = -1;
554             ArrayAdapter<CharSequence> adapter =
555                 new ArrayAdapter<CharSequence>(this,
556                         android.R.layout.simple_spinner_item, entries);
557             adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
558             spinner.setAdapter(adapter);
559         }
560
561         int index = findResponseIndexFor(response);
562         spinner.setSelection(index + mResponseOffset);
563     }
564
565     private void setTextCommon(int id, CharSequence text) {
566         TextView textView = (TextView) findViewById(id);
567         if (textView == null)
568             return;
569         textView.setText(text);
570     }
571
572     private void setVisibilityCommon(int id, int visibility) {
573         View v = findViewById(id);
574         if (v != null) {
575             v.setVisibility(visibility);
576         }
577         return;
578     }
579 }