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.EXTRA_EVENT_BEGIN_TIME; 21 import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME; 22 import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS; 23 import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED; 24 import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED; 25 import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS_NONE; 26 import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS_TENTATIVE; 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.Events; 36 import android.text.TextUtils; 37 import android.util.Base64; 38 import android.util.Log; 39 40 import com.android.calendarcommon.DateException; 41 42 public class GoogleCalendarUriIntentFilter extends Activity { 43 private static final String TAG = "GoogleCalendarUriIntentFilter"; 44 45 private static final int EVENT_INDEX_ID = 0; 46 private static final int EVENT_INDEX_START = 1; 47 private static final int EVENT_INDEX_END = 2; 48 private static final int EVENT_INDEX_DURATION = 3; 49 50 private static final String[] EVENT_PROJECTION = new String[] { 51 Events._ID, // 0 52 Events.DTSTART, // 1 53 Events.DTEND, // 2 54 Events.DURATION, // 3 55 }; 56 57 /** 58 * Extracts the ID from the eid parameter of a URI. 59 * 60 * The URI contains an "eid" parameter, which is comprised of an ID, followed by a space, 61 * followed by some other stuff. This is Base64-encoded before being added to the URI. 62 * 63 * @param uri incoming request 64 * @return the decoded ID 65 */ 66 private String extractEid(Uri uri) { 67 try { 68 String eid = uri.getQueryParameter("eid"); 69 if (eid == null) { 70 return null; 71 } 72 73 byte[] decodedBytes = Base64.decode(eid, Base64.DEFAULT); 74 int spacePosn; 75 for (spacePosn = 0; spacePosn < decodedBytes.length; spacePosn++) { 76 if (decodedBytes[spacePosn] == ' ') { 77 break; 78 } 79 } 80 return new String(decodedBytes, 0, spacePosn); 81 } catch (RuntimeException e) { 82 Log.w(TAG, "Punting malformed URI " + uri); 83 return null; 84 } 85 } 86 87 @Override 88 protected void onCreate(Bundle icicle) { 89 super.onCreate(icicle); 90 91 Intent intent = getIntent(); 92 if (intent != null) { 93 Uri uri = intent.getData(); 94 if (uri != null) { 95 String eid = extractEid(uri); 96 if (eid != null) { 97 String selection = Events._SYNC_ID + " LIKE \"%/" + eid + "\""; 98 Cursor eventCursor = managedQuery(Events.CONTENT_URI, EVENT_PROJECTION, 99 selection, null, null); 100 101 if (eventCursor != null && eventCursor.getCount() > 0) { 102 if (eventCursor.getCount() > 1) { 103 // TODO what to do when there's more than one match? 104 // 105 // Probably the case of multiple calendar having the 106 // same event. 107 // 108 // If the intent has info about account (Gmail 109 // hashes the account name in some cases), we can 110 // try to match it. 111 // 112 // Otherwise, pull up the copy with higher permission level. 113 Log.i(TAG, "NOTE: found " + eventCursor.getCount() 114 + " matches on event with id='" + eid + "'"); 115 } 116 117 // Get info from Cursor 118 while (eventCursor.moveToNext()) { 119 int eventId = eventCursor.getInt(EVENT_INDEX_ID); 120 long startMillis = eventCursor.getLong(EVENT_INDEX_START); 121 long endMillis = eventCursor.getLong(EVENT_INDEX_END); 122 123 if (endMillis == 0) { 124 String duration = eventCursor.getString(EVENT_INDEX_DURATION); 125 if (TextUtils.isEmpty(duration)) { 126 continue; 127 } 128 129 try { 130 Duration d = new Duration(); 131 d.parse(duration); 132 endMillis = startMillis + d.getMillis(); 133 if (endMillis < startMillis) { 134 continue; 135 } 136 } catch (DateException e) { 137 continue; 138 } 139 } 140 141 // Pick up attendee status action from uri clicked 142 int attendeeStatus = ATTENDEE_STATUS_NONE; 143 if ("RESPOND".equals(uri.getQueryParameter("action"))) { 144 try { 145 switch (Integer.parseInt(uri.getQueryParameter("rst"))) { 146 case 1: // Yes 147 attendeeStatus = ATTENDEE_STATUS_ACCEPTED; 148 break; 149 case 2: // No 150 attendeeStatus = ATTENDEE_STATUS_DECLINED; 151 break; 152 case 3: // Maybe 153 attendeeStatus = ATTENDEE_STATUS_TENTATIVE; 154 break; 155 } 156 } catch (NumberFormatException e) { 157 // ignore this error as if the response code 158 // wasn't in the uri. 159 } 160 } 161 162 // Send intent to calendar app 163 Uri calendarUri = ContentUris.withAppendedId(Events.CONTENT_URI, 164 eventId); 165 intent = new Intent(Intent.ACTION_VIEW, calendarUri); 166 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis); 167 intent.putExtra(EXTRA_EVENT_END_TIME, endMillis); 168 if (attendeeStatus != ATTENDEE_STATUS_NONE) { 169 intent.putExtra(ATTENDEE_STATUS, attendeeStatus); 170 } 171 startActivity(intent); 172 finish(); 173 return; 174 } 175 } 176 } 177 } 178 179 // Can't handle the intent. Pass it on to the next Activity. 180 try { 181 startNextMatchingActivity(intent); 182 } catch (ActivityNotFoundException ex) { 183 // no browser installed? Just drop it. 184 } 185 } 186 finish(); 187 } 188 } 189