Merge 'goog/ics-ub-calendar-fuchsia' into klp-dev

Bug: 12070265
Change-Id: I293612eba20429dc6ffd7061eba51f24a5f84226
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9c05c8d..8f763f0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -189,6 +189,16 @@
         <receiver android:name=".alerts.GlobalDismissManager"
                   android:exported="false" />
 
+        <receiver android:name=".UpgradeReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
+            </intent-filter>
+        </receiver>
+
         <service android:name=".alerts.AlertService" />
 
         <service android:name=".alerts.DismissAlarmsService" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 49bcf87..caec61b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -208,6 +208,18 @@
     <string name="creating_event_with_guest">"Invitations will be sent."</string>
     <!-- Toast message displayed when an existing event with guests is saved after being modified -->
     <string name="saving_event_with_guest">"Updates will be sent."</string>
+
+    <!-- Toast message displayed responding to an event from an email as accepted
+     [CHAR LIMIT=50] -->
+    <string name="rsvp_accepted">"Responded yes."</string>
+    <!-- Toast message displayed responding to an event from an email as tentative
+     [CHAR LIMIT=50] -->
+    <string name="rsvp_tentative">"Responded maybe."</string>
+    <!-- Toast message displayed responding to an event from an email as declined
+     [CHAR LIMIT=50] -->
+    <string name="rsvp_declined">"Responded no."</string>
+
+
     <!-- Title of message displayed to indicate available calendars are being loaded when creating
          a new event -->
 
diff --git a/src/com/android/calendar/AllInOneActivity.java b/src/com/android/calendar/AllInOneActivity.java
index e61d69c..0e1feb4 100644
--- a/src/com/android/calendar/AllInOneActivity.java
+++ b/src/com/android/calendar/AllInOneActivity.java
@@ -507,6 +507,9 @@
     protected void onResume() {
         super.onResume();
 
+        // Check if the upgrade code has ever been run. If not, force a sync just this one time.
+        Utils.trySyncAndDisableUpgradeReceiver(this);
+
         // Must register as the first activity because this activity can modify
         // the list of event handlers in it's handle method. This affects who
         // the rest of the handlers the controller dispatches to are.
diff --git a/src/com/android/calendar/GoogleCalendarUriIntentFilter.java b/src/com/android/calendar/GoogleCalendarUriIntentFilter.java
index 4a3c0cb..fef89e8 100644
--- a/src/com/android/calendar/GoogleCalendarUriIntentFilter.java
+++ b/src/com/android/calendar/GoogleCalendarUriIntentFilter.java
@@ -17,26 +17,24 @@
 
 package com.android.calendar;
 
-import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS;
-import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED;
-import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED;
-import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS_NONE;
-import static android.provider.CalendarContract.Attendees.ATTENDEE_STATUS_TENTATIVE;
-import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
-import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
-
 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;
@@ -144,11 +142,14 @@
             Uri uri = intent.getData();
             if (uri != null) {
                 String[] eidParts = extractEidAndEmail(uri);
-                if (debug) Log.d(TAG, "eidParts=" + eidParts );
-
-                if (eidParts != null) {
-                    final String selection = Events._SYNC_ID + " LIKE \"%" + eidParts[0]
-                            + "\" AND " + Calendars.OWNER_ACCOUNT + " LIKE \"" + eidParts[1] + "\"";
+                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,
@@ -156,13 +157,15 @@
                             Calendars.CALENDAR_ACCESS_LEVEL + " desc");
                     if (debug) Log.d(TAG, "Found: " + eventCursor.getCount());
 
-                    if (eventCursor != null && eventCursor.getCount() > 0) {
-                        if (eventCursor.getCount() > 1) {
-                            Log.i(TAG, "NOTE: found " + eventCursor.getCount()
-                                    + " matches on event with id='" + eidParts[0] + "'");
-                            // Don't print eidPart[1] as it contains the user's PII
-                        }
+                    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);
@@ -194,22 +197,19 @@
                                 }
                             }
 
-                            eventCursor.close();
-                            eventCursor = null;
-
                             // Pick up attendee status action from uri clicked
-                            int attendeeStatus = ATTENDEE_STATUS_NONE;
+                            int attendeeStatus = Attendees.ATTENDEE_STATUS_NONE;
                             if ("RESPOND".equals(uri.getQueryParameter("action"))) {
                                 try {
                                     switch (Integer.parseInt(uri.getQueryParameter("rst"))) {
                                     case 1: // Yes
-                                        attendeeStatus = ATTENDEE_STATUS_ACCEPTED;
+                                        attendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED;
                                         break;
                                     case 2: // No
-                                        attendeeStatus = ATTENDEE_STATUS_DECLINED;
+                                        attendeeStatus = Attendees.ATTENDEE_STATUS_DECLINED;
                                         break;
                                     case 3: // Maybe
-                                        attendeeStatus = ATTENDEE_STATUS_TENTATIVE;
+                                        attendeeStatus = Attendees.ATTENDEE_STATUS_TENTATIVE;
                                         break;
                                     }
                                 } catch (NumberFormatException e) {
@@ -218,22 +218,24 @@
                                 }
                             }
 
-                            // Send intent to calendar app
-                            Uri calendarUri = ContentUris.withAppendedId(Events.CONTENT_URI,
-                                    eventId);
+                            final Uri calendarUri = ContentUris.withAppendedId(
+                                    Events.CONTENT_URI, eventId);
                             intent = new Intent(Intent.ACTION_VIEW, calendarUri);
                             intent.setClass(this, EventInfoActivity.class);
-                            intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis);
-                            intent.putExtra(EXTRA_EVENT_END_TIME, endMillis);
-                            if (attendeeStatus != ATTENDEE_STATUS_NONE) {
-                                intent.putExtra(ATTENDEE_STATUS, attendeeStatus);
+                            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);
                             }
-                            startActivity(intent);
                             finish();
                             return;
                         }
+                    } finally {
+                        eventCursor.close();
                     }
-                    if (eventCursor != null) eventCursor.close();
                 }
             }
 
@@ -246,4 +248,44 @@
         }
         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) });
+    }
 }
diff --git a/src/com/android/calendar/UpgradeReceiver.java b/src/com/android/calendar/UpgradeReceiver.java
new file mode 100644
index 0000000..0e89286
--- /dev/null
+++ b/src/com/android/calendar/UpgradeReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 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,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.calendar;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class UpgradeReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        Utils.trySyncAndDisableUpgradeReceiver(context);
+    }
+
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/Utils.java b/src/com/android/calendar/Utils.java
index 4018f22..4196185 100644
--- a/src/com/android/calendar/Utils.java
+++ b/src/com/android/calendar/Utils.java
@@ -22,6 +22,7 @@
 import android.app.Activity;
 import android.app.SearchManager;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -848,6 +849,28 @@
         return (0xff000000) | ((r | g | b) >> 8);
     }
 
+    public static void trySyncAndDisableUpgradeReceiver(Context context) {
+        final PackageManager pm = context.getPackageManager();
+        ComponentName upgradeComponent = new ComponentName(context, UpgradeReceiver.class);
+        if (pm.getComponentEnabledSetting(upgradeComponent) ==
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
+            // The upgrade receiver has been disabled, which means this code has been run before,
+            // so no need to sync.
+            return;
+        }
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+        ContentResolver.requestSync(
+                null /* no account */,
+                Calendars.CONTENT_URI.getAuthority(),
+                extras);
+
+        // Now unregister the receiver so that we won't continue to sync every time.
+        pm.setComponentEnabledSetting(upgradeComponent,
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+    }
+
     // A single strand represents one color of events. Events are divided up by
     // color to make them convenient to draw. The black strand is special in
     // that it holds conflicting events as well as color settings for allday on