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 static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS;
     21 import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED;
     22 import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED;
     23 import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS_NONE;
     24 import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS_TENTATIVE;
     25 import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
     26 import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
     27 
     28 import android.app.Activity;
     29 import android.content.ActivityNotFoundException;
     30 import android.content.ContentUris;
     31 import android.content.Intent;
     32 import android.database.Cursor;
     33 import android.net.Uri;
     34 import android.os.Bundle;
     35 import android.provider.CalendarContract.Calendars;
     36 import android.provider.CalendarContract.Events;
     37 import android.text.TextUtils;
     38 import android.util.Base64;
     39 import android.util.Log;
     40 
     41 import com.android.calendarcommon2.DateException;
     42 import com.android.calendarcommon2.Duration;
     43 
     44 public class GoogleCalendarUriIntentFilter extends Activity {
     45     private static final String TAG = "GoogleCalendarUriIntentFilter";
     46     static final boolean debug = false;
     47 
     48     private static final int EVENT_INDEX_ID = 0;
     49     private static final int EVENT_INDEX_START = 1;
     50     private static final int EVENT_INDEX_END = 2;
     51     private static final int EVENT_INDEX_DURATION = 3;
     52 
     53     private static final String[] EVENT_PROJECTION = new String[] {
     54         Events._ID,      // 0
     55         Events.DTSTART,  // 1
     56         Events.DTEND,    // 2
     57         Events.DURATION, // 3
     58     };
     59 
     60     /**
     61      * Extracts the ID and calendar email from the eid parameter of a URI.
     62      *
     63      * The URI contains an "eid" parameter, which is comprised of an ID, followed
     64      * by a space, followed by the calendar email address. The domain is sometimes
     65      * shortened. See the switch statement. This is Base64-encoded before being
     66      * added to the URI.
     67      *
     68      * @param uri incoming request
     69      * @return the decoded event ID and calendar email
     70      */
     71     private String[] extractEidAndEmail(Uri uri) {
     72         try {
     73             String eidParam = uri.getQueryParameter("eid");
     74             if (debug) Log.d(TAG, "eid=" + eidParam );
     75             if (eidParam == null) {
     76                 return null;
     77             }
     78 
     79             byte[] decodedBytes = Base64.decode(eidParam, Base64.DEFAULT);
     80             if (debug) Log.d(TAG, "decoded eid=" + new String(decodedBytes) );
     81 
     82             for (int spacePosn = 0; spacePosn < decodedBytes.length; spacePosn++) {
     83                 if (decodedBytes[spacePosn] == ' ') {
     84                     int emailLen = decodedBytes.length - spacePosn - 1;
     85                     if (spacePosn == 0 || emailLen < 3) {
     86                         break;
     87                     }
     88 
     89                     String domain = null;
     90                     if (decodedBytes[decodedBytes.length - 2] == '@') {
     91                         // Drop the special one character domain
     92                         emailLen--;
     93 
     94                         switch(decodedBytes[decodedBytes.length - 1]) {
     95                             case 'm':
     96                                 domain = "gmail.com";
     97                                 break;
     98                             case 'g':
     99                                 domain = "group.calendar.google.com";
    100                                 break;
    101                             case 'h':
    102                                 domain = "holiday.calendar.google.com";
    103                                 break;
    104                             case 'i':
    105                                 domain = "import.calendar.google.com";
    106                                 break;
    107                             case 'v':
    108                                 domain = "group.v.calendar.google.com";
    109                                 break;
    110                             default:
    111                                 Log.wtf(TAG, "Unexpected one letter domain: "
    112                                         + decodedBytes[decodedBytes.length - 1]);
    113                                 // Add sql wild card char to handle new cases
    114                                 // that we don't know about.
    115                                 domain = "%";
    116                                 break;
    117                         }
    118                     }
    119 
    120                     String eid = new String(decodedBytes, 0, spacePosn);
    121                     String email = new String(decodedBytes, spacePosn + 1, emailLen);
    122                     if (debug) Log.d(TAG, "eid=   " + eid );
    123                     if (debug) Log.d(TAG, "email= " + email );
    124                     if (debug) Log.d(TAG, "domain=" + domain );
    125                     if (domain != null) {
    126                         email += domain;
    127                     }
    128 
    129                     return new String[] { eid, email };
    130                 }
    131             }
    132         } catch (RuntimeException e) {
    133             Log.w(TAG, "Punting malformed URI " + uri);
    134         }
    135         return null;
    136     }
    137 
    138     @Override
    139     protected void onCreate(Bundle icicle) {
    140         super.onCreate(icicle);
    141 
    142         Intent intent = getIntent();
    143         if (intent != null) {
    144             Uri uri = intent.getData();
    145             if (uri != null) {
    146                 String[] eidParts = extractEidAndEmail(uri);
    147                 if (debug) Log.d(TAG, "eidParts=" + eidParts );
    148 
    149                 if (eidParts != null) {
    150                     final String selection = Events._SYNC_ID + " LIKE \"%" + eidParts[0]
    151                             + "\" AND " + Calendars.OWNER_ACCOUNT + " LIKE \"" + eidParts[1] + "\"";
    152 
    153                     if (debug) Log.d(TAG, "selection: " + selection);
    154                     Cursor eventCursor = getContentResolver().query(Events.CONTENT_URI,
    155                             EVENT_PROJECTION, selection, null,
    156                             Calendars.CALENDAR_ACCESS_LEVEL + " desc");
    157                     if (debug) Log.d(TAG, "Found: " + eventCursor.getCount());
    158 
    159                     if (eventCursor != null && eventCursor.getCount() > 0) {
    160                         if (eventCursor.getCount() > 1) {
    161                             Log.i(TAG, "NOTE: found " + eventCursor.getCount()
    162                                     + " matches on event with id='" + eidParts[0] + "'");
    163                             // Don't print eidPart[1] as it contains the user's PII
    164                         }
    165 
    166                         // Get info from Cursor
    167                         while (eventCursor.moveToNext()) {
    168                             int eventId = eventCursor.getInt(EVENT_INDEX_ID);
    169                             long startMillis = eventCursor.getLong(EVENT_INDEX_START);
    170                             long endMillis = eventCursor.getLong(EVENT_INDEX_END);
    171                             if (debug) Log.d(TAG, "_id: " + eventCursor.getLong(EVENT_INDEX_ID));
    172                             if (debug) Log.d(TAG, "startMillis: " + startMillis);
    173                             if (debug) Log.d(TAG, "endMillis:   " + endMillis);
    174 
    175                             if (endMillis == 0) {
    176                                 String duration = eventCursor.getString(EVENT_INDEX_DURATION);
    177                                 if (debug) Log.d(TAG, "duration:    " + duration);
    178                                 if (TextUtils.isEmpty(duration)) {
    179                                     continue;
    180                                 }
    181 
    182                                 try {
    183                                     Duration d = new Duration();
    184                                     d.parse(duration);
    185                                     endMillis = startMillis + d.getMillis();
    186                                     if (debug) Log.d(TAG, "startMillis! " + startMillis);
    187                                     if (debug) Log.d(TAG, "endMillis!   " + endMillis);
    188                                     if (endMillis < startMillis) {
    189                                         continue;
    190                                     }
    191                                 } catch (DateException e) {
    192                                     if (debug) Log.d(TAG, "duration:" + e.toString());
    193                                     continue;
    194                                 }
    195                             }
    196 
    197                             eventCursor.close();
    198                             eventCursor = null;
    199 
    200                             // Pick up attendee status action from uri clicked
    201                             int attendeeStatus = 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 = ATTENDEE_STATUS_ACCEPTED;
    207                                         break;
    208                                     case 2: // No
    209                                         attendeeStatus = ATTENDEE_STATUS_DECLINED;
    210                                         break;
    211                                     case 3: // Maybe
    212                                         attendeeStatus = 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                             // Send intent to calendar app
    222                             Uri calendarUri = ContentUris.withAppendedId(Events.CONTENT_URI,
    223                                     eventId);
    224                             intent = new Intent(Intent.ACTION_VIEW, calendarUri);
    225                             intent.setClass(this, EventInfoActivity.class);
    226                             intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis);
    227                             intent.putExtra(EXTRA_EVENT_END_TIME, endMillis);
    228                             if (attendeeStatus != ATTENDEE_STATUS_NONE) {
    229                                 intent.putExtra(ATTENDEE_STATUS, attendeeStatus);
    230                             }
    231                             startActivity(intent);
    232                             finish();
    233                             return;
    234                         }
    235                     }
    236                     if (eventCursor != null) eventCursor.close();
    237                 }
    238             }
    239 
    240             // Can't handle the intent. Pass it on to the next Activity.
    241             try {
    242                 startNextMatchingActivity(intent);
    243             } catch (ActivityNotFoundException ex) {
    244                 // no browser installed? Just drop it.
    245             }
    246         }
    247         finish();
    248     }
    249 }
    250