Home | History | Annotate | Download | only in calendar
      1 /*
      2 **
      3 ** Copyright 2009, The Android Open Source Project
      4 **
      5 ** Licensed under the Apache License, Version 2.0 (the "License");
      6 ** you may not use this file except in compliance with the License.
      7 ** You may obtain a copy of the License at
      8 **
      9 **     http://www.apache.org/licenses/LICENSE-2.0
     10 **
     11 ** Unless required by applicable law or agreed to in writing, software
     12 ** distributed under the License is distributed on an "AS IS" BASIS,
     13 ** See the License for the specific language governing permissions and
     14 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 ** limitations under the License.
     16 */
     17 
     18 package com.android.calendar;
     19 
     20 import android.app.Activity;
     21 import android.content.ActivityNotFoundException;
     22 import android.content.AsyncQueryHandler;
     23 import android.content.ContentResolver;
     24 import android.content.ContentUris;
     25 import android.content.ContentValues;
     26 import android.content.Intent;
     27 import android.database.Cursor;
     28 import android.net.Uri;
     29 import android.os.Bundle;
     30 import android.provider.CalendarContract;
     31 import android.provider.CalendarContract.Attendees;
     32 import android.provider.CalendarContract.Calendars;
     33 import android.provider.CalendarContract.Events;
     34 import android.text.TextUtils;
     35 import android.util.Base64;
     36 import android.util.Log;
     37 import android.widget.Toast;
     38 
     39 import com.android.calendarcommon2.DateException;
     40 import com.android.calendarcommon2.Duration;
     41 
     42 public class GoogleCalendarUriIntentFilter extends Activity {
     43     private static final String TAG = "GoogleCalendarUriIntentFilter";
     44     static final boolean debug = false;
     45 
     46     private static final int EVENT_INDEX_ID = 0;
     47     private static final int EVENT_INDEX_START = 1;
     48     private static final int EVENT_INDEX_END = 2;
     49     private static final int EVENT_INDEX_DURATION = 3;
     50 
     51     private static final String[] EVENT_PROJECTION = new String[] {
     52         Events._ID,      // 0
     53         Events.DTSTART,  // 1
     54         Events.DTEND,    // 2
     55         Events.DURATION, // 3
     56     };
     57 
     58     /**
     59      * Extracts the ID and calendar email from the eid parameter of a URI.
     60      *
     61      * The URI contains an "eid" parameter, which is comprised of an ID, followed
     62      * by a space, followed by the calendar email address. The domain is sometimes
     63      * shortened. See the switch statement. This is Base64-encoded before being
     64      * added to the URI.
     65      *
     66      * @param uri incoming request
     67      * @return the decoded event ID and calendar email
     68      */
     69     private String[] extractEidAndEmail(Uri uri) {
     70         try {
     71             String eidParam = uri.getQueryParameter("eid");
     72             if (debug) Log.d(TAG, "eid=" + eidParam );
     73             if (eidParam == null) {
     74                 return null;
     75             }
     76 
     77             byte[] decodedBytes = Base64.decode(eidParam, Base64.DEFAULT);
     78             if (debug) Log.d(TAG, "decoded eid=" + new String(decodedBytes) );
     79 
     80             for (int spacePosn = 0; spacePosn < decodedBytes.length; spacePosn++) {
     81                 if (decodedBytes[spacePosn] == ' ') {
     82                     int emailLen = decodedBytes.length - spacePosn - 1;
     83                     if (spacePosn == 0 || emailLen < 3) {
     84                         break;
     85                     }
     86 
     87                     String domain = null;
     88                     if (decodedBytes[decodedBytes.length - 2] == '@') {
     89                         // Drop the special one character domain
     90                         emailLen--;
     91 
     92                         switch(decodedBytes[decodedBytes.length - 1]) {
     93                             case 'm':
     94                                 domain = "gmail.com";
     95                                 break;
     96                             case 'g':
     97                                 domain = "group.calendar.google.com";
     98                                 break;
     99                             case 'h':
    100                                 domain = "holiday.calendar.google.com";
    101                                 break;
    102                             case 'i':
    103                                 domain = "import.calendar.google.com";
    104                                 break;
    105                             case 'v':
    106                                 domain = "group.v.calendar.google.com";
    107                                 break;
    108                             default:
    109                                 Log.wtf(TAG, "Unexpected one letter domain: "
    110                                         + decodedBytes[decodedBytes.length - 1]);
    111                                 // Add sql wild card char to handle new cases
    112                                 // that we don't know about.
    113                                 domain = "%";
    114                                 break;
    115                         }
    116                     }
    117 
    118                     String eid = new String(decodedBytes, 0, spacePosn);
    119                     String email = new String(decodedBytes, spacePosn + 1, emailLen);
    120                     if (debug) Log.d(TAG, "eid=   " + eid );
    121                     if (debug) Log.d(TAG, "email= " + email );
    122                     if (debug) Log.d(TAG, "domain=" + domain );
    123                     if (domain != null) {
    124                         email += domain;
    125                     }
    126 
    127                     return new String[] { eid, email };
    128                 }
    129             }
    130         } catch (RuntimeException e) {
    131             Log.w(TAG, "Punting malformed URI " + uri);
    132         }
    133         return null;
    134     }
    135 
    136     @Override
    137     protected void onCreate(Bundle icicle) {
    138         super.onCreate(icicle);
    139 
    140         Intent intent = getIntent();
    141         if (intent != null) {
    142             Uri uri = intent.getData();
    143             if (uri != null) {
    144                 String[] eidParts = extractEidAndEmail(uri);
    145                 if (eidParts == null) {
    146                     Log.i(TAG, "Could not find event for uri: " +uri);
    147                 } else {
    148                     final String syncId = eidParts[0];
    149                     final String ownerAccount = eidParts[1];
    150                     if (debug) Log.d(TAG, "eidParts=" + syncId + "/" + ownerAccount);
    151                     final String selection = Events._SYNC_ID + " LIKE \"%" + syncId + "\" AND "
    152                             + Calendars.OWNER_ACCOUNT + " LIKE \"" + ownerAccount + "\"";
    153 
    154                     if (debug) Log.d(TAG, "selection: " + selection);
    155                     Cursor eventCursor = getContentResolver().query(Events.CONTENT_URI,
    156                             EVENT_PROJECTION, selection, null,
    157                             Calendars.CALENDAR_ACCESS_LEVEL + " desc");
    158                     if (debug) Log.d(TAG, "Found: " + eventCursor.getCount());
    159 
    160                     if (eventCursor == null || eventCursor.getCount() == 0) {
    161                         Log.i(TAG, "NOTE: found no matches on event with id='" + syncId + "'");
    162                         return;
    163                     }
    164                     Log.i(TAG, "NOTE: found " + eventCursor.getCount()
    165                             + " matches on event with id='" + syncId + "'");
    166                     // Don't print eidPart[1] as it contains the user's PII
    167 
    168                     try {
    169                         // Get info from Cursor
    170                         while (eventCursor.moveToNext()) {
    171                             int eventId = eventCursor.getInt(EVENT_INDEX_ID);
    172                             long startMillis = eventCursor.getLong(EVENT_INDEX_START);
    173                             long endMillis = eventCursor.getLong(EVENT_INDEX_END);
    174                             if (debug) Log.d(TAG, "_id: " + eventCursor.getLong(EVENT_INDEX_ID));
    175                             if (debug) Log.d(TAG, "startMillis: " + startMillis);
    176                             if (debug) Log.d(TAG, "endMillis:   " + endMillis);
    177 
    178                             if (endMillis == 0) {
    179                                 String duration = eventCursor.getString(EVENT_INDEX_DURATION);
    180                                 if (debug) Log.d(TAG, "duration:    " + duration);
    181                                 if (TextUtils.isEmpty(duration)) {
    182                                     continue;
    183                                 }
    184 
    185                                 try {
    186                                     Duration d = new Duration();
    187                                     d.parse(duration);
    188                                     endMillis = startMillis + d.getMillis();
    189                                     if (debug) Log.d(TAG, "startMillis! " + startMillis);
    190                                     if (debug) Log.d(TAG, "endMillis!   " + endMillis);
    191                                     if (endMillis < startMillis) {
    192                                         continue;
    193                                     }
    194                                 } catch (DateException e) {
    195                                     if (debug) Log.d(TAG, "duration:" + e.toString());
    196                                     continue;
    197                                 }
    198                             }
    199 
    200                             // Pick up attendee status action from uri clicked
    201                             int attendeeStatus = Attendees.ATTENDEE_STATUS_NONE;
    202                             if ("RESPOND".equals(uri.getQueryParameter("action"))) {
    203                                 try {
    204                                     switch (Integer.parseInt(uri.getQueryParameter("rst"))) {
    205                                     case 1: // Yes
    206                                         attendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED;
    207                                         break;
    208                                     case 2: // No
    209                                         attendeeStatus = Attendees.ATTENDEE_STATUS_DECLINED;
    210                                         break;
    211                                     case 3: // Maybe
    212                                         attendeeStatus = Attendees.ATTENDEE_STATUS_TENTATIVE;
    213                                         break;
    214                                     }
    215                                 } catch (NumberFormatException e) {
    216                                     // ignore this error as if the response code
    217                                     // wasn't in the uri.
    218                                 }
    219                             }
    220 
    221                             final Uri calendarUri = ContentUris.withAppendedId(
    222                                     Events.CONTENT_URI, eventId);
    223                             intent = new Intent(Intent.ACTION_VIEW, calendarUri);
    224                             intent.setClass(this, EventInfoActivity.class);
    225                             intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis);
    226                             intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis);
    227                             if (attendeeStatus == Attendees.ATTENDEE_STATUS_NONE) {
    228                                 startActivity(intent);
    229                             } else {
    230                                 updateSelfAttendeeStatus(
    231                                         eventId, ownerAccount, attendeeStatus, intent);
    232                             }
    233                             finish();
    234                             return;
    235                         }
    236                     } finally {
    237                         eventCursor.close();
    238                     }
    239                 }
    240             }
    241 
    242             // Can't handle the intent. Pass it on to the next Activity.
    243             try {
    244                 startNextMatchingActivity(intent);
    245             } catch (ActivityNotFoundException ex) {
    246                 // no browser installed? Just drop it.
    247             }
    248         }
    249         finish();
    250     }
    251 
    252     private void updateSelfAttendeeStatus(
    253             int eventId, String ownerAccount, final int status, final Intent intent) {
    254         final ContentResolver cr = getContentResolver();
    255         final AsyncQueryHandler queryHandler =
    256                 new AsyncQueryHandler(cr) {
    257                     @Override
    258                     protected void onUpdateComplete(int token, Object cookie, int result) {
    259                         if (result == 0) {
    260                             Log.w(TAG, "No rows updated - starting event viewer");
    261                             intent.putExtra(Attendees.ATTENDEE_STATUS, status);
    262                             startActivity(intent);
    263                             return;
    264                         }
    265                         final int toastId;
    266                         switch (status) {
    267                             case Attendees.ATTENDEE_STATUS_ACCEPTED:
    268                                 toastId = R.string.rsvp_accepted;
    269                                 break;
    270                             case Attendees.ATTENDEE_STATUS_DECLINED:
    271                                 toastId = R.string.rsvp_declined;
    272                                 break;
    273                             case Attendees.ATTENDEE_STATUS_TENTATIVE:
    274                                 toastId = R.string.rsvp_tentative;
    275                                 break;
    276                             default:
    277                                 return;
    278                         }
    279                         Toast.makeText(GoogleCalendarUriIntentFilter.this,
    280                                 toastId, Toast.LENGTH_LONG).show();
    281                     }
    282                 };
    283         final ContentValues values = new ContentValues();
    284         values.put(Attendees.ATTENDEE_STATUS, status);
    285         queryHandler.startUpdate(0, null,
    286                 Attendees.CONTENT_URI,
    287                 values,
    288                 Attendees.ATTENDEE_EMAIL + "=? AND " + Attendees.EVENT_ID + "=?",
    289                 new String[]{ ownerAccount, String.valueOf(eventId) });
    290     }
    291 }
    292