Initial Contribution
[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.DateFormat;
33 import android.pim.DateUtils;
34 import android.pim.EventRecurrence;
35 import android.pim.Time;
36 import android.preference.PreferenceManager;
37 import android.provider.Calendar;
38 import android.provider.Calendar.Attendees;
39 import android.provider.Calendar.Calendars;
40 import android.provider.Calendar.Events;
41 import android.provider.Calendar.Reminders;
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         initEventCursor();
170
171         setContentView(R.layout.event_info_activity);
172
173         // Attendees cursor
174         Uri uri = Attendees.CONTENT_URI;
175         String where = String.format(ATTENDEES_WHERE, mEventId);
176         mAttendeesCursor = managedQuery(uri, ATTENDEES_PROJECTION, where, null);
177         initAttendeesCursor();
178
179         // Calendars cursor
180         uri = Calendars.CONTENT_URI;
181         where = String.format(CALENDARS_WHERE, mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID));
182         mCalendarsCursor = managedQuery(uri, CALENDARS_PROJECTION, where, null);
183         initCalendarsCursor();
184
185         Resources res = getResources();
186
187         if (mVisibility >= Calendars.CONTRIBUTOR_ACCESS &&
188                 mRelationship == Attendees.RELATIONSHIP_ATTENDEE) {
189             setTitle(res.getString(R.string.event_info_title_invite));
190         } else {
191             setTitle(res.getString(R.string.event_info_title));
192         }
193
194         // Initialize the reminder values array.
195         Resources r = getResources();
196         String[] strings = r.getStringArray(R.array.reminder_minutes_values);
197         int size = strings.length;
198         ArrayList<Integer> list = new ArrayList<Integer>(size);
199         for (int i = 0 ; i < size ; i++) {
200             list.add(Integer.parseInt(strings[i]));
201         }
202         mReminderValues = list;
203         String[] labels = r.getStringArray(R.array.reminder_minutes_labels);
204         mReminderLabels = new ArrayList<String>(Arrays.asList(labels));
205
206         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
207         String durationString =
208                 prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0");
209         mDefaultReminderMinutes = Integer.parseInt(durationString);
210
211         mRemindersContainer = (LinearLayout) findViewById(R.id.reminders_container);
212
213         // Reminders cursor
214         boolean hasAlarm = mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0;
215         if (hasAlarm) {
216             uri = Reminders.CONTENT_URI;
217             where = String.format(REMINDERS_WHERE, mEventId);
218             Cursor reminderCursor = cr.query(uri, REMINDERS_PROJECTION, where, null, null);
219             try {
220                 // First pass: collect all the custom reminder minutes (e.g.,
221                 // a reminder of 8 minutes) into a global list.
222                 while (reminderCursor.moveToNext()) {
223                     int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
224                     EditEvent.addMinutesToList(this, mReminderValues, mReminderLabels, minutes);
225                 }
226                 
227                 // Second pass: create the reminder spinners
228                 reminderCursor.moveToPosition(-1);
229                 while (reminderCursor.moveToNext()) {
230                     int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
231                     mOriginalMinutes.add(minutes);
232                     EditEvent.addReminder(this, this, mReminderItems, mReminderValues,
233                             mReminderLabels, minutes);
234                 }
235             } finally {
236                 reminderCursor.close();
237             }
238         }
239
240         updateView();
241         updateRemindersVisibility();
242
243         mDeleteEventHelper = new DeleteEventHelper(this, true /* exit when done */);
244     }
245
246     @Override
247     protected void onResume() {
248         super.onResume();
249         initEventCursor();
250         initAttendeesCursor();
251         initCalendarsCursor();
252     }
253
254
255     private void initEventCursor() {
256         if ((mEventCursor != null) && (mEventCursor.getCount() > 0)) {
257             mEventCursor.moveToFirst();
258             mVisibility = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL);
259             mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
260         }
261     }
262
263     private void initAttendeesCursor() {
264         if (mAttendeesCursor != null) {
265             if (mAttendeesCursor.moveToFirst()) {
266                 mRelationship = mAttendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP);
267             }
268         }
269     }
270
271     private void initCalendarsCursor() {
272         if (mCalendarsCursor != null) {
273             mCalendarsCursor.moveToFirst();
274         }
275     }
276
277     @Override
278     public void onPause() {
279         super.onPause();
280         ContentResolver cr = getContentResolver();
281         ArrayList<Integer> reminderMinutes = EditEvent.reminderItemsToMinutes(mReminderItems,
282                 mReminderValues);
283         EditEvent.saveReminders(cr, mEventId, reminderMinutes, mOriginalMinutes);
284         saveResponse();
285     }
286
287     @Override
288     public boolean onCreateOptionsMenu(Menu menu) {
289         MenuItem item;
290         item = menu.add(MENU_GROUP_REMINDER, MENU_ADD_REMINDER, 0,
291                 R.string.add_new_reminder);
292         item.setIcon(R.drawable.ic_menu_reminder);
293         item.setAlphabeticShortcut('r');
294
295         item = menu.add(MENU_GROUP_EDIT, MENU_EDIT, 0, R.string.edit_event_label);
296         item.setIcon(android.R.drawable.ic_menu_edit);
297         item.setAlphabeticShortcut('e');
298
299         item = menu.add(MENU_GROUP_DELETE, MENU_DELETE, 0, R.string.delete_event_label);
300         item.setIcon(android.R.drawable.ic_menu_delete);
301
302         return super.onCreateOptionsMenu(menu);
303     }
304
305     @Override
306     public boolean onPrepareOptionsMenu(Menu menu) {
307         // Cannot add reminders to a shared calendar with only free/busy
308         // permissions
309         if (mVisibility >= Calendars.READ_ACCESS && mReminderItems.size() < MAX_REMINDERS) {
310             menu.setGroupVisible(MENU_GROUP_REMINDER, true);
311             menu.setGroupEnabled(MENU_GROUP_REMINDER, true);
312         } else {
313             menu.setGroupVisible(MENU_GROUP_REMINDER, false);
314             menu.setGroupEnabled(MENU_GROUP_REMINDER, false);
315         }
316
317         if (mVisibility >= Calendars.CONTRIBUTOR_ACCESS &&
318                 mRelationship >= Attendees.RELATIONSHIP_ORGANIZER) {
319             menu.setGroupVisible(MENU_GROUP_EDIT, true);
320             menu.setGroupEnabled(MENU_GROUP_EDIT, true);
321             menu.setGroupVisible(MENU_GROUP_DELETE, true);
322             menu.setGroupEnabled(MENU_GROUP_DELETE, true);
323         } else {
324             menu.setGroupVisible(MENU_GROUP_EDIT, false);
325             menu.setGroupEnabled(MENU_GROUP_EDIT, false);
326             menu.setGroupVisible(MENU_GROUP_DELETE, false);
327             menu.setGroupEnabled(MENU_GROUP_DELETE, false);
328         }
329
330         return super.onPrepareOptionsMenu(menu);
331     }
332
333     @Override
334     public boolean onOptionsItemSelected(MenuItem item) {
335         super.onOptionsItemSelected(item);
336         switch (item.getItemId()) {
337         case MENU_ADD_REMINDER:
338             // TODO: when adding a new reminder, make it different from the
339             // last one in the list (if any).
340             if (mDefaultReminderMinutes == 0) {
341                 EditEvent.addReminder(this, this, mReminderItems,
342                         mReminderValues, mReminderLabels, 10 /* minutes */);
343             } else {
344                 EditEvent.addReminder(this, this, mReminderItems,
345                         mReminderValues, mReminderLabels, mDefaultReminderMinutes);
346             }
347             updateRemindersVisibility();
348             break;
349         case MENU_EDIT:
350             doEdit();
351             break;
352         case MENU_DELETE:
353             doDelete();
354             break;
355         }
356         return true;
357     }
358
359     @Override
360     public boolean onKeyDown(int keyCode, KeyEvent event) {
361         if (keyCode == KeyEvent.KEYCODE_DEL) {
362             doDelete();
363             return true;
364         }
365         return super.onKeyDown(keyCode, event);
366     }
367
368     private void updateRemindersVisibility() {
369         if (mReminderItems.size() == 0) {
370             mRemindersContainer.setVisibility(View.GONE);
371         } else {
372             mRemindersContainer.setVisibility(View.VISIBLE);
373         }
374     }
375
376     private void saveResponse() {
377         if (mAttendeesCursor == null) {
378             return;
379         }
380         Spinner spinner = (Spinner) findViewById(R.id.response_value);
381         int position = spinner.getSelectedItemPosition() - mResponseOffset;
382         if (position <= 0) {
383             return;
384         }
385
386         int status = ATTENDEE_VALUES[position];
387         mAttendeesCursor.updateInt(ATTENDEES_INDEX_STATUS, status);
388         mAttendeesCursor.commitUpdates();
389     }
390
391     private int findResponseIndexFor(int response) {
392         int size = ATTENDEE_VALUES.length;
393         for (int index = 0; index < size; index++) {
394             if (ATTENDEE_VALUES[index] == response) {
395                 return index;
396             }
397         }
398         return 0;
399     }
400
401     private void doEdit() {
402         Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
403         Intent intent = new Intent(Intent.ACTION_EDIT, uri);
404         intent.putExtra(Calendar.EVENT_BEGIN_TIME, mStartMillis);
405         intent.putExtra(Calendar.EVENT_END_TIME, mEndMillis);
406         intent.setClass(EventInfoActivity.this, EditEvent.class);
407         startActivity(intent);
408         finish();
409     }
410
411     private void doDelete() {
412         mDeleteEventHelper.delete(mStartMillis, mEndMillis, mEventCursor, -1);
413     }
414
415     private void updateView() {
416         if (mEventCursor == null) {
417             return;
418         }
419         Resources res = getResources();
420         ContentResolver cr = getContentResolver();
421
422         String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
423         if (eventName == null || eventName.length() == 0) {
424             eventName = res.getString(R.string.no_title_label);
425         }
426
427         boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
428         String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
429         String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
430         String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
431         boolean hasAlarm = mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0;
432         String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
433         int color = mEventCursor.getInt(EVENT_INDEX_COLOR) & 0xbbffffff;
434
435         ImageView stripe = (ImageView) findViewById(R.id.vertical_stripe);
436         stripe.getBackground().setColorFilter(color, PorterDuff.Mode.SRC_IN);
437
438         // What
439         if (eventName != null) {
440             setTextCommon(R.id.title, eventName);
441         }
442
443         // When
444         String when;
445         int flags;
446         if (allDay) {
447             flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE;
448         } else {
449             flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE;
450             if (DateFormat.is24HourFormat(this)) {
451                 flags |= DateUtils.FORMAT_24HOUR;
452             }
453         }
454         when = DateUtils.formatDateRange(mStartMillis, mEndMillis, flags);
455         setTextCommon(R.id.when, when);
456
457         // Show the event timezone if it is different from the local timezone
458         Time time = new Time();
459         String localTimezone = time.timezone;
460         if (allDay) {
461             localTimezone = Time.TIMEZONE_UTC;
462         }
463         if (eventTimezone != null && !localTimezone.equals(eventTimezone) && !allDay) {
464             setTextCommon(R.id.timezone, localTimezone);
465         } else {
466             setVisibilityCommon(R.id.timezone_container, View.GONE);
467         }
468
469         // Repeat
470         if (rRule != null) {
471             EventRecurrence eventRecurrence = new EventRecurrence();
472             eventRecurrence.parse(rRule);
473             Time date = new Time();
474             if (allDay) {
475                 date.timezone = Time.TIMEZONE_UTC;
476             }
477             date.set(mStartMillis);
478             eventRecurrence.setStartDate(date);
479             String repeatString = eventRecurrence.getRepeatString();
480             setTextCommon(R.id.repeat, repeatString);
481         } else {
482             setVisibilityCommon(R.id.repeat_container, View.GONE);
483         }
484
485         // Where
486         if (location == null || location.length() == 0) {
487             setVisibilityCommon(R.id.where, View.GONE);
488         } else {
489             setTextCommon(R.id.where, location);
490         }
491
492         // Description
493         if (description == null || description.length() == 0) {
494             setVisibilityCommon(R.id.description, View.GONE);
495         } else {
496             setTextCommon(R.id.description, description);
497         }
498
499         // Calendar
500         if (mCalendarsCursor != null) {
501             mCalendarsCursor.moveToFirst();
502             String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
503             setTextCommon(R.id.calendar, calendarName);
504         } else {
505             setVisibilityCommon(R.id.calendar_container, View.GONE);
506         }
507
508         // Response
509         updateResponse();
510     }
511
512     void updateResponse() {
513         if (mVisibility < Calendars.CONTRIBUTOR_ACCESS ||
514                 mRelationship != Attendees.RELATIONSHIP_ATTENDEE) {
515             setVisibilityCommon(R.id.response_container, View.GONE);
516             return;
517         }
518
519         setVisibilityCommon(R.id.response_container, View.VISIBLE);
520
521         Spinner spinner = (Spinner) findViewById(R.id.response_value);
522
523         int response = ATTENDEE_NO_RESPONSE;
524         if (mAttendeesCursor != null) {
525             response = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
526         }
527         mResponseOffset = 0;
528
529         /* If the user has previously responded to this event
530          * we should not allow them to select no response again.
531          * Switch the entries to a set of entries without the
532          * no response option.
533          */
534         if ((response != Attendees.ATTENDEE_STATUS_INVITED)
535                 && (response != ATTENDEE_NO_RESPONSE)
536                 && (response != Attendees.ATTENDEE_STATUS_NONE)) {
537             CharSequence[] entries;
538             entries = getResources().getTextArray(R.array.response_labels2);
539             mResponseOffset = -1;
540             ArrayAdapter<CharSequence> adapter =
541                 new ArrayAdapter<CharSequence>(this,
542                         android.R.layout.simple_spinner_item, entries);
543             adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
544             spinner.setAdapter(adapter);
545         }
546
547         int index = findResponseIndexFor(response);
548         spinner.setSelection(index + mResponseOffset);
549     }
550
551     private void setTextCommon(int id, CharSequence text) {
552         TextView textView = (TextView) findViewById(id);
553         if (textView == null)
554             return;
555         textView.setText(text);
556     }
557
558     private void setVisibilityCommon(int id, int visibility) {
559         View v = findViewById(id);
560         if (v != null) {
561             v.setVisibility(visibility);
562         }
563         return;
564     }
565 }