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