| /* |
| ** |
| ** Copyright 2009, The Android Open Source Project |
| ** |
| ** Licensed under the Apache License, Version 2.0 (the "License"); |
| ** you may not use this file except in compliance with the License. |
| ** You may obtain a copy of the License at |
| ** |
| ** http://www.apache.org/licenses/LICENSE-2.0 |
| ** |
| ** Unless required by applicable law or agreed to in writing, software |
| ** distributed under the License is distributed on an "AS IS" BASIS, |
| ** See the License for the specific language governing permissions and |
| ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| ** limitations under the License. |
| */ |
| |
| package com.android.calendar; |
| |
| import android.app.Activity; |
| import android.content.ActivityNotFoundException; |
| import android.content.AsyncQueryHandler; |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.ContentValues; |
| import android.content.Intent; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.CalendarContract; |
| import android.provider.CalendarContract.Attendees; |
| import android.provider.CalendarContract.Calendars; |
| import android.provider.CalendarContract.Events; |
| import android.text.TextUtils; |
| import android.util.Base64; |
| import android.util.Log; |
| import android.widget.Toast; |
| |
| import com.android.calendarcommon2.DateException; |
| import com.android.calendarcommon2.Duration; |
| |
| public class GoogleCalendarUriIntentFilter extends Activity { |
| private static final String TAG = "GoogleCalendarUriIntentFilter"; |
| static final boolean debug = false; |
| |
| private static final int EVENT_INDEX_ID = 0; |
| private static final int EVENT_INDEX_START = 1; |
| private static final int EVENT_INDEX_END = 2; |
| private static final int EVENT_INDEX_DURATION = 3; |
| |
| private static final String[] EVENT_PROJECTION = new String[] { |
| Events._ID, // 0 |
| Events.DTSTART, // 1 |
| Events.DTEND, // 2 |
| Events.DURATION, // 3 |
| }; |
| |
| /** |
| * Extracts the ID and calendar email from the eid parameter of a URI. |
| * |
| * The URI contains an "eid" parameter, which is comprised of an ID, followed |
| * by a space, followed by the calendar email address. The domain is sometimes |
| * shortened. See the switch statement. This is Base64-encoded before being |
| * added to the URI. |
| * |
| * @param uri incoming request |
| * @return the decoded event ID and calendar email |
| */ |
| private String[] extractEidAndEmail(Uri uri) { |
| try { |
| String eidParam = uri.getQueryParameter("eid"); |
| if (debug) Log.d(TAG, "eid=" + eidParam ); |
| if (eidParam == null) { |
| return null; |
| } |
| |
| byte[] decodedBytes = Base64.decode(eidParam, Base64.DEFAULT); |
| if (debug) Log.d(TAG, "decoded eid=" + new String(decodedBytes) ); |
| |
| for (int spacePosn = 0; spacePosn < decodedBytes.length; spacePosn++) { |
| if (decodedBytes[spacePosn] == ' ') { |
| int emailLen = decodedBytes.length - spacePosn - 1; |
| if (spacePosn == 0 || emailLen < 3) { |
| break; |
| } |
| |
| String domain = null; |
| if (decodedBytes[decodedBytes.length - 2] == '@') { |
| // Drop the special one character domain |
| emailLen--; |
| |
| switch(decodedBytes[decodedBytes.length - 1]) { |
| case 'm': |
| domain = "gmail.com"; |
| break; |
| case 'g': |
| domain = "group.calendar.google.com"; |
| break; |
| case 'h': |
| domain = "holiday.calendar.google.com"; |
| break; |
| case 'i': |
| domain = "import.calendar.google.com"; |
| break; |
| case 'v': |
| domain = "group.v.calendar.google.com"; |
| break; |
| default: |
| Log.wtf(TAG, "Unexpected one letter domain: " |
| + decodedBytes[decodedBytes.length - 1]); |
| // Add sql wild card char to handle new cases |
| // that we don't know about. |
| domain = "%"; |
| break; |
| } |
| } |
| |
| String eid = new String(decodedBytes, 0, spacePosn); |
| String email = new String(decodedBytes, spacePosn + 1, emailLen); |
| if (debug) Log.d(TAG, "eid= " + eid ); |
| if (debug) Log.d(TAG, "email= " + email ); |
| if (debug) Log.d(TAG, "domain=" + domain ); |
| if (domain != null) { |
| email += domain; |
| } |
| |
| return new String[] { eid, email }; |
| } |
| } |
| } catch (RuntimeException e) { |
| Log.w(TAG, "Punting malformed URI " + uri); |
| } |
| return null; |
| } |
| |
| @Override |
| protected void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| Intent intent = getIntent(); |
| if (intent != null) { |
| Uri uri = intent.getData(); |
| if (uri != null) { |
| String[] eidParts = extractEidAndEmail(uri); |
| if (eidParts == null) { |
| Log.i(TAG, "Could not find event for uri: " +uri); |
| } else { |
| final String syncId = eidParts[0]; |
| final String ownerAccount = eidParts[1]; |
| if (debug) Log.d(TAG, "eidParts=" + syncId + "/" + ownerAccount); |
| final String selection = Events._SYNC_ID + " LIKE \"%" + syncId + "\" AND " |
| + Calendars.OWNER_ACCOUNT + " LIKE \"" + ownerAccount + "\""; |
| |
| if (debug) Log.d(TAG, "selection: " + selection); |
| Cursor eventCursor = getContentResolver().query(Events.CONTENT_URI, |
| EVENT_PROJECTION, selection, null, |
| Calendars.CALENDAR_ACCESS_LEVEL + " desc"); |
| if (debug) Log.d(TAG, "Found: " + eventCursor.getCount()); |
| |
| if (eventCursor == null || eventCursor.getCount() == 0) { |
| Log.i(TAG, "NOTE: found no matches on event with id='" + syncId + "'"); |
| return; |
| } |
| Log.i(TAG, "NOTE: found " + eventCursor.getCount() |
| + " matches on event with id='" + syncId + "'"); |
| // Don't print eidPart[1] as it contains the user's PII |
| |
| try { |
| // Get info from Cursor |
| while (eventCursor.moveToNext()) { |
| int eventId = eventCursor.getInt(EVENT_INDEX_ID); |
| long startMillis = eventCursor.getLong(EVENT_INDEX_START); |
| long endMillis = eventCursor.getLong(EVENT_INDEX_END); |
| if (debug) Log.d(TAG, "_id: " + eventCursor.getLong(EVENT_INDEX_ID)); |
| if (debug) Log.d(TAG, "startMillis: " + startMillis); |
| if (debug) Log.d(TAG, "endMillis: " + endMillis); |
| |
| if (endMillis == 0) { |
| String duration = eventCursor.getString(EVENT_INDEX_DURATION); |
| if (debug) Log.d(TAG, "duration: " + duration); |
| if (TextUtils.isEmpty(duration)) { |
| continue; |
| } |
| |
| try { |
| Duration d = new Duration(); |
| d.parse(duration); |
| endMillis = startMillis + d.getMillis(); |
| if (debug) Log.d(TAG, "startMillis! " + startMillis); |
| if (debug) Log.d(TAG, "endMillis! " + endMillis); |
| if (endMillis < startMillis) { |
| continue; |
| } |
| } catch (DateException e) { |
| if (debug) Log.d(TAG, "duration:" + e.toString()); |
| continue; |
| } |
| } |
| |
| // Pick up attendee status action from uri clicked |
| int attendeeStatus = Attendees.ATTENDEE_STATUS_NONE; |
| if ("RESPOND".equals(uri.getQueryParameter("action"))) { |
| try { |
| switch (Integer.parseInt(uri.getQueryParameter("rst"))) { |
| case 1: // Yes |
| attendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED; |
| break; |
| case 2: // No |
| attendeeStatus = Attendees.ATTENDEE_STATUS_DECLINED; |
| break; |
| case 3: // Maybe |
| attendeeStatus = Attendees.ATTENDEE_STATUS_TENTATIVE; |
| break; |
| } |
| } catch (NumberFormatException e) { |
| // ignore this error as if the response code |
| // wasn't in the uri. |
| } |
| } |
| |
| final Uri calendarUri = ContentUris.withAppendedId( |
| Events.CONTENT_URI, eventId); |
| intent = new Intent(Intent.ACTION_VIEW, calendarUri); |
| intent.setClass(this, EventInfoActivity.class); |
| intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis); |
| intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis); |
| if (attendeeStatus == Attendees.ATTENDEE_STATUS_NONE) { |
| startActivity(intent); |
| } else { |
| updateSelfAttendeeStatus( |
| eventId, ownerAccount, attendeeStatus, intent); |
| } |
| finish(); |
| return; |
| } |
| } finally { |
| eventCursor.close(); |
| } |
| } |
| } |
| |
| // Can't handle the intent. Pass it on to the next Activity. |
| try { |
| startNextMatchingActivity(intent); |
| } catch (ActivityNotFoundException ex) { |
| // no browser installed? Just drop it. |
| } |
| } |
| finish(); |
| } |
| |
| private void updateSelfAttendeeStatus( |
| int eventId, String ownerAccount, final int status, final Intent intent) { |
| final ContentResolver cr = getContentResolver(); |
| final AsyncQueryHandler queryHandler = |
| new AsyncQueryHandler(cr) { |
| @Override |
| protected void onUpdateComplete(int token, Object cookie, int result) { |
| if (result == 0) { |
| Log.w(TAG, "No rows updated - starting event viewer"); |
| intent.putExtra(Attendees.ATTENDEE_STATUS, status); |
| startActivity(intent); |
| return; |
| } |
| final int toastId; |
| switch (status) { |
| case Attendees.ATTENDEE_STATUS_ACCEPTED: |
| toastId = R.string.rsvp_accepted; |
| break; |
| case Attendees.ATTENDEE_STATUS_DECLINED: |
| toastId = R.string.rsvp_declined; |
| break; |
| case Attendees.ATTENDEE_STATUS_TENTATIVE: |
| toastId = R.string.rsvp_tentative; |
| break; |
| default: |
| return; |
| } |
| Toast.makeText(GoogleCalendarUriIntentFilter.this, |
| toastId, Toast.LENGTH_LONG).show(); |
| } |
| }; |
| final ContentValues values = new ContentValues(); |
| values.put(Attendees.ATTENDEE_STATUS, status); |
| queryHandler.startUpdate(0, null, |
| Attendees.CONTENT_URI, |
| values, |
| Attendees.ATTENDEE_EMAIL + "=? AND " + Attendees.EVENT_ID + "=?", |
| new String[]{ ownerAccount, String.valueOf(eventId) }); |
| } |
| } |