am 9449b8be: Make name of Calendar Provider localizable.
Merge commit '9449b8be778a2f53361adc81ac38141096c8857c'
* commit '9449b8be778a2f53361adc81ac38141096c8857c':
Make name of Calendar Provider localizable.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 438a17e..50e9b97 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -39,6 +39,13 @@
android:syncable="true" android:multiprocess="false"
android:readPermission="android.permission.READ_CALENDAR"
android:writePermission="android.permission.WRITE_CALENDAR" />
+ <service android:name="CalendarSyncAdapterService" android:exported="true">
+ <intent-filter>
+ <action android:name="android.content.SyncAdapter" />
+ </intent-filter>
+ <meta-data android:name="android.content.SyncAdapter"
+ android:resource="@xml/syncadapter" />
+ </service>
<activity android:name="CalendarContentProviderTests" android:label="Calendar Content Provider">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/res/xml/syncadapter.xml b/res/xml/syncadapter.xml
new file mode 100644
index 0000000..b5f89cc
--- /dev/null
+++ b/res/xml/syncadapter.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2009, 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.
+ */
+-->
+
+<!-- The attributes in this XML file provide configuration information -->
+<!-- for the SyncAdapter. -->
+
+<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+ android:contentAuthority="calendar"
+ android:accountType="com.google.GAIA"
+/>
diff --git a/src/com/android/providers/calendar/CalendarProvider.java b/src/com/android/providers/calendar/CalendarProvider.java
index 59cc896..623757f 100644
--- a/src/com/android/providers/calendar/CalendarProvider.java
+++ b/src/com/android/providers/calendar/CalendarProvider.java
@@ -17,22 +17,6 @@
package com.android.providers.calendar;
-import com.google.android.collect.Sets;
-import com.google.android.gdata.client.AndroidGDataClient;
-import com.google.android.gdata.client.AndroidXmlParserFactory;
-import com.google.android.googlelogin.GoogleLoginServiceBlockingHelper;
-import com.google.android.googlelogin.GoogleLoginServiceNotFoundException;
-import com.google.android.providers.AbstractGDataSyncAdapter;
-import com.google.android.providers.AbstractGDataSyncAdapter.GDataSyncData;
-import com.google.wireless.gdata.calendar.client.CalendarClient;
-import com.google.wireless.gdata.calendar.data.CalendarEntry;
-import com.google.wireless.gdata.calendar.data.CalendarsFeed;
-import com.google.wireless.gdata.calendar.parser.xml.XmlCalendarGDataParserFactory;
-import com.google.wireless.gdata.client.HttpException;
-import com.google.wireless.gdata.data.Entry;
-import com.google.wireless.gdata.parser.GDataParser;
-import com.google.wireless.gdata.parser.ParseException;
-
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.AbstractSyncableContentProvider;
@@ -45,7 +29,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SyncAdapter;
import android.content.SyncContext;
import android.content.UriMatcher;
import android.database.Cursor;
@@ -53,7 +36,6 @@
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
-import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Debug;
@@ -69,34 +51,41 @@
import android.provider.Calendar.ExtendedProperties;
import android.provider.Calendar.Instances;
import android.provider.Calendar.Reminders;
-import android.provider.SyncConstValue;
import android.text.TextUtils;
import android.text.format.Time;
import android.util.Config;
import android.util.Log;
import android.util.TimeFormatException;
+import android.accounts.Account;
+import com.google.android.collect.Sets;
+import com.google.android.collect.Maps;
+import com.google.android.gdata.client.AndroidGDataClient;
+import com.google.android.gdata.client.AndroidXmlParserFactory;
+import com.google.android.providers.AbstractGDataSyncAdapter;
+import com.google.android.providers.AbstractGDataSyncAdapter.GDataSyncData;
+import com.google.wireless.gdata.calendar.client.CalendarClient;
+import com.google.wireless.gdata.calendar.parser.xml.XmlCalendarGDataParserFactory;
-import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
public class CalendarProvider extends AbstractSyncableContentProvider {
-
private static final boolean PROFILE = false;
private static final boolean MULTIPLE_ATTENDEES_PER_EVENT = false;
- private static final String[] ACCOUNTS_PROJECTION = new String[] { Calendars._SYNC_ACCOUNT};
+ private static final String[] ACCOUNTS_PROJECTION =
+ new String[] {Calendars._SYNC_ACCOUNT, Calendars._SYNC_ACCOUNT_TYPE};
private static final String[] EVENTS_PROJECTION = new String[] {
Events._SYNC_ID,
Events._SYNC_VERSION,
Events._SYNC_ACCOUNT,
+ Events._SYNC_ACCOUNT_TYPE,
Events.CALENDAR_ID,
Events.RRULE,
Events.RDATE,
@@ -104,11 +93,12 @@
};
private static final int EVENTS_SYNC_ID_INDEX = 0;
private static final int EVENTS_SYNC_VERSION_INDEX = 1;
- private static final int EVENTS_SYNC_ACCOUNT_INDEX = 2;
- private static final int EVENTS_CALENDAR_ID_INDEX = 3;
- private static final int EVENTS_RRULE_INDEX = 4;
- private static final int EVENTS_RDATE_INDEX = 5;
- private static final int EVENTS_ORIGINAL_EVENT_INDEX = 6;
+ private static final int EVENTS_SYNC_ACCOUNT_NAME_INDEX = 2;
+ private static final int EVENTS_SYNC_ACCOUNT_TYPE_INDEX = 3;
+ private static final int EVENTS_CALENDAR_ID_INDEX = 4;
+ private static final int EVENTS_RRULE_INDEX = 5;
+ private static final int EVENTS_RDATE_INDEX = 6;
+ private static final int EVENTS_ORIGINAL_EVENT_INDEX = 7;
private DatabaseUtils.InsertHelper mCalendarsInserter;
private DatabaseUtils.InsertHelper mEventsInserter;
@@ -235,13 +225,7 @@
// Note: if you update the version number, you must also update the code
// in upgradeDatabase() to modify the database (gracefully, if possible).
- private static final int DATABASE_VERSION = 54;
-
- private static final String EXPECTED_PROJECTION = "/full";
-
- private static final String DESIRED_PROJECTION = "/full-selfattendance";
-
- private static final String FEEDS_SUBSTRING = "/feeds/";
+ private static final int DATABASE_VERSION = 55;
// Make sure we load at least two months worth of data.
// Client apps can load more data in a background thread.
@@ -275,7 +259,6 @@
private AlarmManager mAlarmManager;
- private CalendarSyncAdapter mSyncAdapter;
private CalendarAppWidgetProvider mAppWidgetProvider = CalendarAppWidgetProvider.getInstance();
/**
@@ -309,6 +292,8 @@
public boolean onCreate() {
super.onCreate();
+ setTempProviderSyncAdapter(new CalendarSyncAdapter(getContext(), this));
+
// Register for Intent broadcasts
IntentFilter filter = new IntentFilter();
@@ -566,6 +551,27 @@
oldVersion += 1;
}
+ if (oldVersion == 54) {
+ db.execSQL("ALTER TABLE Calendars ADD COLUMN _sync_account_type TEXT;");
+ db.execSQL("ALTER TABLE Events ADD COLUMN _sync_account_type TEXT;");
+ db.execSQL("ALTER TABLE DeletedEvents ADD COLUMN _sync_account_type TEXT;");
+ db.execSQL("UPDATE Calendars"
+ + " SET _sync_account_type='com.google.GAIA'"
+ + " WHERE _sync_account IS NOT NULL");
+ db.execSQL("UPDATE Events"
+ + " SET _sync_account_type='com.google.GAIA'"
+ + " WHERE _sync_account IS NOT NULL");
+ db.execSQL("UPDATE DeletedEvents"
+ + " SET _sync_account_type='com.google.GAIA'"
+ + " WHERE _sync_account IS NOT NULL");
+ Log.w(TAG, "re-creating eventSyncAccountAndIdIndex");
+ db.execSQL("DROP INDEX eventSyncAccountAndIdIndex");
+ db.execSQL("CREATE INDEX eventSyncAccountAndIdIndex ON Events ("
+ + Events._SYNC_ACCOUNT_TYPE + ", " + Events._SYNC_ACCOUNT + ", "
+ + Events._SYNC_ID + ");");
+ oldVersion += 1;
+ }
+
return true; // this was lossless
}
@@ -589,6 +595,7 @@
db.execSQL("CREATE TABLE Calendars (" +
"_id INTEGER PRIMARY KEY," +
"_sync_account TEXT," +
+ "_sync_account_type TEXT," +
"_sync_id TEXT," +
"_sync_version TEXT," +
"_sync_time TEXT," + // UTC
@@ -618,6 +625,7 @@
db.execSQL("CREATE TABLE Events (" +
"_id INTEGER PRIMARY KEY," +
"_sync_account TEXT," +
+ "_sync_account_type TEXT," +
"_sync_id TEXT," +
"_sync_version TEXT," +
"_sync_time TEXT," + // UTC
@@ -652,7 +660,8 @@
");");
db.execSQL("CREATE INDEX eventSyncAccountAndIdIndex ON Events ("
- + Events._SYNC_ACCOUNT + ", " + Events._SYNC_ID + ");");
+ + Events._SYNC_ACCOUNT_TYPE + ", " + Events._SYNC_ACCOUNT + ", "
+ + Events._SYNC_ID + ");");
db.execSQL("CREATE INDEX eventsCalendarIdIndex ON Events (" +
Events.CALENDAR_ID +
@@ -675,6 +684,7 @@
"_sync_id TEXT," +
"_sync_version TEXT," +
"_sync_account TEXT," +
+ "_sync_account_type TEXT," +
(isTemporary() ? "_sync_local_id INTEGER," : "") + // Used while syncing,
"_sync_mark INTEGER," + // To filter out new rows
"calendar_id INTEGER" +
@@ -831,18 +841,17 @@
* syncable.
*/
@Override
- protected void onAccountsChanged(String[] accountsArray) {
+ protected void onAccountsChanged(Account[] accountsArray) {
super.onAccountsChanged(accountsArray);
- Map<String, Boolean> accounts = new HashMap<String, Boolean>();
- for (String account : accountsArray) {
+ Map<Account, Boolean> accounts = Maps.newHashMap();
+ for (Account account : accountsArray) {
accounts.put(account, false);
}
mDb.beginTransaction();
try {
- deleteRowsForRemovedAccounts(accounts, "Calendars",
- SyncConstValue._SYNC_ACCOUNT);
+ deleteRowsForRemovedAccounts(accounts, "Calendars");
mDb.setTransactionSuccessful();
} finally {
mDb.endTransaction();
@@ -856,28 +865,28 @@
// If there are no calendars at all for a given account, add the
// default calendar.
- for (Map.Entry<String, Boolean> entry : accounts.entrySet()) {
+ for (Map.Entry<Account, Boolean> entry : accounts.entrySet()) {
entry.setValue(false);
// TODO: remove this break when Calendar supports multiple accounts. Until then
// pretend that only the first account exists.
break;
}
- Set<String> handledAccounts = Sets.newHashSet();
- ContentResolver cr = getContext().getContentResolver();
+ Set<Account> handledAccounts = Sets.newHashSet();
if (Config.LOGV) Log.v(TAG, "querying calendars");
- Cursor c = cr.query(Calendars.CONTENT_URI, ACCOUNTS_PROJECTION, null, null, null);
+ Cursor c = queryInternal(Calendars.CONTENT_URI, ACCOUNTS_PROJECTION, null, null, null);
try {
while (c.moveToNext()) {
- String account = c.getString(0);
+ final String accountName = c.getString(0);
+ final String accountType = c.getString(1);
+ final Account account = new Account(accountName, accountType);
if (handledAccounts.contains(account)) {
continue;
}
handledAccounts.add(account);
if (accounts.containsKey(account)) {
if (Config.LOGV) {
- Log.v(TAG, "calendars for account " + account
- + " exist");
+ Log.v(TAG, "calendars for account " + account + " exist");
}
accounts.put(account, true /* hasCalendar */);
}
@@ -890,8 +899,8 @@
if (Config.LOGV) {
Log.v(TAG, "scanning over " + accounts.size() + " account(s)");
}
- for (Map.Entry<String, Boolean> entry : accounts.entrySet()) {
- String account = entry.getKey();
+ for (Map.Entry<Account, Boolean> entry : accounts.entrySet()) {
+ final Account account = entry.getKey();
boolean hasCalendar = entry.getValue();
if (hasCalendar) {
if (Config.LOGV) {
@@ -900,14 +909,15 @@
}
continue;
}
- String feedUrl = mCalendarClient.getDefaultCalendarUrl(account,
+ String feedUrl = mCalendarClient.getDefaultCalendarUrl(account.mName,
CalendarClient.PROJECTION_PRIVATE_SELF_ATTENDANCE, null/* query params */);
feedUrl = CalendarSyncAdapter.rewriteUrlforAccount(account, feedUrl);
if (Config.LOGV) {
Log.v(TAG, "adding default calendar for account " + account);
}
ContentValues values = new ContentValues();
- values.put(Calendars._SYNC_ACCOUNT, account);
+ values.put(Calendars._SYNC_ACCOUNT, account.mName);
+ values.put(Calendars._SYNC_ACCOUNT_TYPE, account.mType);
values.put(Calendars.URL, feedUrl);
values.put(Calendars.DISPLAY_NAME, "Default");
values.put(Calendars.SYNC_EVENTS, 1);
@@ -918,7 +928,7 @@
// when the user does a sync.
values.put(Calendars.TIMEZONE, Time.getCurrentTimezone());
values.put(Calendars.ACCESS_LEVEL, Calendars.OWNER_ACCESS);
- cr.insert(Calendars.CONTENT_URI, values);
+ insertInternal(Calendars.CONTENT_URI, values);
scheduleSync(account, false /* do a full sync */, null /* no url */);
}
@@ -957,14 +967,6 @@
}
case CALENDARS:
qb.setTables("Calendars");
- // see if we want to update the list of calendars from the
- // server.
- String update = null;
- update = url.getQueryParameter("update");
- if ("1".equals(update)) {
- fetchCalendarsFromServer();
- }
-
break;
case CALENDARS_ID:
qb.setTables("Calendars");
@@ -1075,279 +1077,6 @@
return ret;
}
- private void fetchCalendarsFromServer() {
- if (mCalendarClient == null) {
- Log.w(TAG, "Cannot fetch calendars -- calendar url defined.");
- return;
- }
-
- GoogleLoginServiceBlockingHelper loginHelper = null;
- String username = null;
- String authToken = null;
-
- try {
- loginHelper = new GoogleLoginServiceBlockingHelper(getContext());
-
- // TODO: allow caller to specify which account's feeds should be updated
- username = loginHelper.getAccount(false);
- if (TextUtils.isEmpty(username)) {
- Log.w(TAG, "Unable to update calendars from server -- "
- + "no users configured.");
- return;
- }
-
- try {
- authToken = loginHelper.getAuthToken(username,
- mCalendarClient.getServiceName());
- } catch (GoogleLoginServiceBlockingHelper.AuthenticationException e) {
- Log.w(TAG, "Unable to update calendars from server -- could not "
- + "authenticate user " + username, e);
- return;
- }
- } catch (GoogleLoginServiceNotFoundException e) {
- Log.e(TAG, "Could not find Google login service", e);
- return;
- } finally {
- if (loginHelper != null) {
- loginHelper.close();
- }
- }
-
- // get the current set of calendars. we'll need to pay attention to
- // which calendars we get back from the server, so we can delete
- // calendars that have been deleted from the server.
- Set<Long> existingCalendarIds = new HashSet<Long>();
-
- final SQLiteDatabase db = getDatabase();
- db.beginTransaction();
- try {
- getCurrentCalendars(existingCalendarIds);
-
- // get and process the calendars meta feed
- GDataParser parser = null;
- try {
- String feedUrl = mCalendarClient.getUserCalendarsUrl(username);
- feedUrl = CalendarSyncAdapter.rewriteUrlforAccount(username, feedUrl);
- parser = mCalendarClient.getParserForUserCalendars(feedUrl, authToken);
- // process the calendars
- processCalendars(username, parser, existingCalendarIds);
- } catch (ParseException pe) {
- Log.w(TAG, "Unable to process calendars from server -- could not "
- + "parse calendar feed.", pe);
- return;
- } catch (IOException ioe) {
- Log.w(TAG, "Unable to process calendars from server -- encountered "
- + "i/o error", ioe);
- return;
- } catch (HttpException e) {
- switch (e.getStatusCode()) {
- case HttpException.SC_UNAUTHORIZED:
- Log.w(TAG, "Unable to process calendars from server -- could not "
- + "authenticate user.", e);
- return;
- case HttpException.SC_GONE:
- Log.w(TAG, "Unable to process calendars from server -- encountered "
- + "an AllDeletedUnavailableException, this should never happen", e);
- return;
- default:
- Log.w(TAG, "Unable to process calendars from server -- error", e);
- return;
- }
- } finally {
- if (parser != null) {
- parser.close();
- }
- }
-
- // delete calendars that are no longer sent from the server.
- final Uri calendarContentUri = Calendars.CONTENT_URI;
- for (long calId : existingCalendarIds) {
- // NOTE: triggers delete all events, instances for this calendar.
- delete(ContentUris.withAppendedId(calendarContentUri, calId),
- null /* where */, null /* selectionArgs */);
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- private void getCurrentCalendars(Set<Long> calendarIds) {
- Cursor cursor = query(Calendars.CONTENT_URI,
- new String[] { Calendars._ID },
- null /* selection */,
- null /* selectionArgs */,
- null /* sort */);
- if (cursor != null) {
- try {
- while (cursor.moveToNext()) {
- calendarIds.add(cursor.getLong(0));
- }
- } finally {
- cursor.close();
- }
- }
- }
-
- private void processCalendars(String username,
- GDataParser parser,
- Set<Long> existingCalendarIds)
- throws ParseException, IOException {
- CalendarsFeed feed = (CalendarsFeed) parser.init();
- Entry entry = null;
- ContentValues map = new ContentValues();
- final Uri calendarContentUri = Calendars.CONTENT_URI;
- while (parser.hasMoreData()) {
- entry = parser.readNextEntry(entry);
- if (Config.LOGV) Log.v(TAG, "Read entry: " + entry.toString());
- CalendarEntry calendarEntry = (CalendarEntry) entry;
- String feedUrl = calendarEntryToContentValues(username, feed, calendarEntry, map);
- if (TextUtils.isEmpty(feedUrl)) {
- continue;
- }
- long calId = -1;
-
- Cursor c = query(calendarContentUri,
- new String[] { Calendars._ID },
- Calendars.URL + "='"
- + feedUrl + '\'' /* selection */,
- null /* selectionArgs */,
- null /* sort */);
- if (c != null) {
- try {
- if (c.moveToFirst()) {
- calId = c.getLong(0);
- existingCalendarIds.remove(calId);
- }
- } finally {
- c.close();
- }
- }
-
- if (calId != -1) {
- if (Config.LOGV) Log.v(TAG, "Updating calendar " + map);
- // don't override the existing "selected" or "hidden" settings.
- map.remove(Calendars.SELECTED);
- map.remove(Calendars.HIDDEN);
- // write to db directly, so we don't send a notification.
- updateInternal(ContentUris.withAppendedId(calendarContentUri, calId), map,
- null /* where */, null /* selectionArgs */);
- } else {
- // Select this calendar for syncing and display if it is
- // selected and not hidden.
- int syncAndDisplay = 0;
- if (calendarEntry.isSelected() && !calendarEntry.isHidden()) {
- syncAndDisplay = 1;
- }
- map.put(Calendars.SYNC_EVENTS, syncAndDisplay);
- map.put(Calendars.SELECTED, syncAndDisplay);
- map.put(Calendars.HIDDEN, 0);
- map.put(Calendars._SYNC_ACCOUNT, username);
- if (Config.LOGV) Log.v(TAG, "Adding calendar " + map);
- // write to db directly, so we don't send a notification.
- Uri row = insertInternal(calendarContentUri, map);
- }
- }
- }
-
- // TODO: unit test.
- protected static final String convertCalendarIdToFeedUrl(String url) {
- // id: http://www.google.com/calendar/feeds/<username>/<cal id>
- // desired feed:
- // http://www.google.com/calendar/feeds/<cal id>/<projection>
- int start = url.indexOf(FEEDS_SUBSTRING);
- if (start != -1) {
- // strip out the */ in /feeds/*/
- start += FEEDS_SUBSTRING.length();
- int end = url.indexOf('/', start);
- if (end != -1) {
- url = url.replace(url.substring(start, end + 1), "");
- }
- url = url + "/private" + DESIRED_PROJECTION;
- }
- return url;
- }
-
- /**
- * Convert the CalenderEntry to a Bundle that can be inserted/updated into the
- * Calendars table.
- */
- private String calendarEntryToContentValues(String account, CalendarsFeed feed,
- CalendarEntry entry,
- ContentValues map) {
- map.clear();
-
- String url = entry.getAlternateLink();
-
- // we only want to fetch the full-selfattendance calendar feeds
- if (!TextUtils.isEmpty(url)) {
- if (url.endsWith(EXPECTED_PROJECTION)) {
- url = url.replace(EXPECTED_PROJECTION, DESIRED_PROJECTION);
- }
- } else {
- // yuck. the alternate link was not available. we should
- // reconstruct from the id.
- url = entry.getId();
- if (!TextUtils.isEmpty(url)) {
- url = convertCalendarIdToFeedUrl(url);
- } else {
- if (Config.LOGV) {
- Log.v(TAG, "Cannot generate url for calendar feed.");
- }
- return null;
- }
- }
-
- url = CalendarSyncAdapter.rewriteUrlforAccount(account, url);
-
- map.put(Calendars.URL, url);
- map.put(Calendars.NAME, entry.getTitle());
-
- // TODO:
- map.put(Calendars.DISPLAY_NAME, entry.getTitle());
-
- map.put(Calendars.TIMEZONE, entry.getTimezone());
-
- String colorStr = entry.getColor();
- if (!TextUtils.isEmpty(colorStr)) {
- int color = Color.parseColor(colorStr);
- // Ensure the alpha is set to max
- color |= 0xff000000;
- map.put(Calendars.COLOR, color);
- }
-
- map.put(Calendars.SELECTED, entry.isSelected() ? 1 : 0);
-
- map.put(Calendars.HIDDEN, entry.isHidden() ? 1 : 0);
-
- int accesslevel;
- switch (entry.getAccessLevel()) {
- case CalendarEntry.ACCESS_NONE:
- accesslevel = Calendars.NO_ACCESS;
- break;
- case CalendarEntry.ACCESS_READ:
- accesslevel = Calendars.READ_ACCESS;
- break;
- case CalendarEntry.ACCESS_FREEBUSY:
- accesslevel = Calendars.FREEBUSY_ACCESS;
- break;
- case CalendarEntry.ACCESS_EDITOR:
- accesslevel = Calendars.EDITOR_ACCESS;
- break;
- case CalendarEntry.ACCESS_OWNER:
- accesslevel = Calendars.OWNER_ACCESS;
- break;
- default:
- accesslevel = Calendars.NO_ACCESS;
- }
- map.put(Calendars.ACCESS_LEVEL, accesslevel);
- // TODO: use the update time, when calendar actually supports this.
- // right now, calendar modifies the update time frequently.
- map.put(Calendars._SYNC_TIME, System.currentTimeMillis());
-
- return url;
- }
-
/*
* Fills the Instances table, if necessary, for the given range and then
* queries the Instances table.
@@ -2472,7 +2201,10 @@
if (!isTemporary()) {
Integer syncEvents = initialValues.getAsInteger(Calendars.SYNC_EVENTS);
if (syncEvents != null && syncEvents == 1) {
- String account = initialValues.getAsString(Calendars._SYNC_ACCOUNT);
+ String accountName = initialValues.getAsString(Calendars._SYNC_ACCOUNT);
+ String accountType = initialValues.getAsString(
+ Calendars._SYNC_ACCOUNT_TYPE);
+ final Account account = new Account(accountName, accountType);
String calendarUrl = initialValues.getAsString(Calendars.URL);
scheduleSync(account, false /* two-way sync */, calendarUrl);
}
@@ -3083,13 +2815,17 @@
String syncId = cursor.getString(EVENTS_SYNC_ID_INDEX);
if (!TextUtils.isEmpty(syncId)) {
String syncVersion = cursor.getString(EVENTS_SYNC_VERSION_INDEX);
- String syncAccount = cursor.getString(EVENTS_SYNC_ACCOUNT_INDEX);
+ String syncAccountName =
+ cursor.getString(EVENTS_SYNC_ACCOUNT_NAME_INDEX);
+ String syncAccountType =
+ cursor.getString(EVENTS_SYNC_ACCOUNT_TYPE_INDEX);
Long calId = cursor.getLong(EVENTS_CALENDAR_ID_INDEX);
ContentValues values = new ContentValues();
values.put(Events._SYNC_ID, syncId);
values.put(Events._SYNC_VERSION, syncVersion);
- values.put(Events._SYNC_ACCOUNT, syncAccount);
+ values.put(Events._SYNC_ACCOUNT, syncAccountName);
+ values.put(Events._SYNC_ACCOUNT_TYPE, syncAccountType);
values.put(Events.CALENDAR_ID, calId);
mDeletedEventsInserter.insert(values);
@@ -3332,12 +3068,12 @@
* Schedule a calendar sync for the account.
* @param account the account for which to schedule a sync
* @param uploadChangesOnly if set, specify that the sync should only send
- * up local changes
+ * up local changes
* @param url the url feed for the calendar to sync (may be null)
*/
- private void scheduleSync(String account, boolean uploadChangesOnly, String url) {
+ private void scheduleSync(Account account, boolean uploadChangesOnly, String url) {
Bundle extras = new Bundle();
- extras.putString(ContentResolver.SYNC_EXTRAS_ACCOUNT, account);
+ extras.putParcelable(ContentResolver.SYNC_EXTRAS_ACCOUNT, account);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, uploadChangesOnly);
if (url != null) {
extras.putString("feed", url);
@@ -3350,28 +3086,29 @@
// get the account, url, and current selected state
// for this calendar.
Cursor cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, id),
- new String[] { Calendars._SYNC_ACCOUNT,
- Calendars.URL,
- Calendars.SYNC_EVENTS},
+ new String[] {Calendars._SYNC_ACCOUNT, Calendars._SYNC_ACCOUNT_TYPE,
+ Calendars.URL, Calendars.SYNC_EVENTS},
null /* selection */,
null /* selectionArgs */,
null /* sort */);
- String account = null;
+ Account account = null;
String calendarUrl = null;
boolean oldSyncEvents = false;
if (cursor != null) {
try {
cursor.moveToFirst();
- account = cursor.getString(0);
- calendarUrl = cursor.getString(1);
- oldSyncEvents = (cursor.getInt(2) != 0);
+ final String accountName = cursor.getString(0);
+ final String accountType = cursor.getString(1);
+ account = new Account(accountName, accountType);
+ calendarUrl = cursor.getString(2);
+ oldSyncEvents = (cursor.getInt(3) != 0);
} finally {
cursor.close();
}
}
- if (TextUtils.isEmpty(account) || TextUtils.isEmpty(calendarUrl)) {
+ if (account == null || TextUtils.isEmpty(calendarUrl)) {
// should not happen?
Log.w(TAG, "Cannot update subscription because account "
+ "or calendar url empty -- should not happen.");
@@ -3422,14 +3159,6 @@
}
@Override
- public synchronized SyncAdapter getSyncAdapter() {
- if (mSyncAdapter == null) {
- mSyncAdapter = new CalendarSyncAdapter(getContext(), this);
- }
- return mSyncAdapter;
- }
-
- @Override
public void onSyncStop(SyncContext context, boolean success) {
super.onSyncStop(context, success);
if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -4031,6 +3760,8 @@
DatabaseUtils.cursorStringToContentValues(diffsCursor, Events._SYNC_VERSION, values);
DatabaseUtils.cursorStringToContentValues(diffsCursor, Events._SYNC_DIRTY, values);
DatabaseUtils.cursorStringToContentValues(diffsCursor, Events._SYNC_ACCOUNT, values);
+ DatabaseUtils.cursorStringToContentValues(diffsCursor,
+ Events._SYNC_ACCOUNT_TYPE, values);
DatabaseUtils.cursorStringToContentValues(diffsCursor, Events.HTML_URI, values);
DatabaseUtils.cursorStringToContentValues(diffsCursor, Events.TITLE, values);
DatabaseUtils.cursorStringToContentValues(diffsCursor, Events.EVENT_LOCATION, values);
@@ -4151,6 +3882,8 @@
sEventsProjectionMap.put(Events._SYNC_LOCAL_ID, "Events._sync_local_id AS _sync_local_id");
sEventsProjectionMap.put(Events._SYNC_DIRTY, "Events._sync_dirty AS _sync_dirty");
sEventsProjectionMap.put(Events._SYNC_ACCOUNT, "Events._sync_account AS _sync_account");
+ sEventsProjectionMap.put(Events._SYNC_ACCOUNT_TYPE,
+ "Events._sync_account_type AS _sync_account_type");
// Instances columns
sInstancesProjectionMap.put(Instances.BEGIN, "begin");
diff --git a/src/com/android/providers/calendar/CalendarSyncAdapter.java b/src/com/android/providers/calendar/CalendarSyncAdapter.java
index 0c53c6a..9c18a28 100644
--- a/src/com/android/providers/calendar/CalendarSyncAdapter.java
+++ b/src/com/android/providers/calendar/CalendarSyncAdapter.java
@@ -17,23 +17,11 @@
package com.android.providers.calendar;
-import com.google.android.gdata.client.AndroidGDataClient;
-import com.google.android.gdata.client.AndroidXmlParserFactory;
-import com.google.android.providers.AbstractGDataSyncAdapter;
-import com.google.wireless.gdata.calendar.client.CalendarClient;
-import com.google.wireless.gdata.calendar.data.EventEntry;
-import com.google.wireless.gdata.calendar.data.EventsFeed;
-import com.google.wireless.gdata.calendar.data.Reminder;
-import com.google.wireless.gdata.calendar.data.When;
-import com.google.wireless.gdata.calendar.data.Who;
-import com.google.wireless.gdata.calendar.parser.xml.XmlCalendarGDataParserFactory;
-import com.google.wireless.gdata.client.GDataServiceClient;
-import com.google.wireless.gdata.client.QueryParams;
-import com.google.wireless.gdata.data.Entry;
-import com.google.wireless.gdata.data.Feed;
-import com.google.wireless.gdata.data.StringUtils;
-import com.google.wireless.gdata.parser.ParseException;
-
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.Constants;
+import android.accounts.OperationCanceledException;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -44,24 +32,50 @@
import android.content.SyncableContentProvider;
import android.database.Cursor;
import android.database.CursorJoiner;
+import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemProperties;
import android.pim.ICalendar;
import android.pim.RecurrenceSet;
import android.provider.Calendar;
-import android.provider.SubscribedFeeds;
-import android.provider.SyncConstValue;
import android.provider.Calendar.Calendars;
import android.provider.Calendar.Events;
+import android.provider.SubscribedFeeds;
+import android.provider.SyncConstValue;
import android.text.TextUtils;
import android.text.format.Time;
import android.util.Config;
import android.util.Log;
+import com.google.android.gdata.client.AndroidGDataClient;
+import com.google.android.gdata.client.AndroidXmlParserFactory;
+import com.google.android.googlelogin.GoogleLoginServiceConstants;
+import com.google.android.providers.AbstractGDataSyncAdapter;
+import com.google.wireless.gdata.calendar.client.CalendarClient;
+import com.google.wireless.gdata.calendar.data.CalendarEntry;
+import com.google.wireless.gdata.calendar.data.CalendarsFeed;
+import com.google.wireless.gdata.calendar.data.EventEntry;
+import com.google.wireless.gdata.calendar.data.EventsFeed;
+import com.google.wireless.gdata.calendar.data.Reminder;
+import com.google.wireless.gdata.calendar.data.When;
+import com.google.wireless.gdata.calendar.data.Who;
+import com.google.wireless.gdata.calendar.parser.xml.XmlCalendarGDataParserFactory;
+import com.google.wireless.gdata.client.GDataServiceClient;
+import com.google.wireless.gdata.client.HttpException;
+import com.google.wireless.gdata.client.QueryParams;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.parser.GDataParser;
+import com.google.wireless.gdata.parser.ParseException;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.Enumeration;
+import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
+import java.util.Set;
import java.util.TimeZone;
import java.util.Vector;
@@ -74,18 +88,27 @@
/* package */ static final String USER_AGENT_APP_VERSION = "Android-GData-Calendar/1.1";
- private static final String SELECT_BY_ACCOUNT = Calendars._SYNC_ACCOUNT + "=?";
+ private static final String SELECT_BY_ACCOUNT =
+ Calendars._SYNC_ACCOUNT + "=? AND " + Calendars._SYNC_ACCOUNT_TYPE + "=?";
private static final String SELECT_BY_ACCOUNT_AND_FEED =
SELECT_BY_ACCOUNT + " AND " + Calendars.URL + "=?";
private static final String[] CALENDAR_KEY_COLUMNS =
- new String[]{Calendars._SYNC_ACCOUNT, Calendars.URL};
+ new String[]{Calendars._SYNC_ACCOUNT, Calendars._SYNC_ACCOUNT_TYPE, Calendars.URL};
private static final String CALENDAR_KEY_SORT_ORDER =
- Calendars._SYNC_ACCOUNT + "," + Calendars.URL;
+ Calendars._SYNC_ACCOUNT + "," + Calendars._SYNC_ACCOUNT_TYPE + "," + Calendars.URL;
private static final String[] FEEDS_KEY_COLUMNS =
- new String[]{SubscribedFeeds.Feeds._SYNC_ACCOUNT, SubscribedFeeds.Feeds.FEED};
+ new String[]{SubscribedFeeds.Feeds._SYNC_ACCOUNT,
+ SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE, SubscribedFeeds.Feeds.FEED};
private static final String FEEDS_KEY_SORT_ORDER =
- SubscribedFeeds.Feeds._SYNC_ACCOUNT + ", " + SubscribedFeeds.Feeds.FEED;
+ SubscribedFeeds.Feeds._SYNC_ACCOUNT + ", " + SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE
+ + ", " + SubscribedFeeds.Feeds.FEED;
+
+ private static final String EXPECTED_PROJECTION = "/full";
+
+ private static final String DESIRED_PROJECTION = "/full-selfattendance";
+
+ private static final String FEEDS_SUBSTRING = "/feeds/";
public static class SyncInfo {
// public String feedUrl;
@@ -519,14 +542,17 @@
protected boolean handleAllDeletedUnavailable(GDataSyncData syncData, String feed) {
syncData.feedData.remove(feed);
+ final Account account = getAccount();
getContext().getContentResolver().delete(Calendar.Calendars.CONTENT_URI,
- Calendar.Calendars._SYNC_ACCOUNT + "=? AND " + Calendar.Calendars.URL + "=?",
- new String[]{getAccount(), feed});
+ Calendar.Calendars._SYNC_ACCOUNT + "=? AND "
+ + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "=? AND "
+ + Calendar.Calendars.URL + "=?",
+ new String[]{account.mName, account.mType, feed});
return true;
}
@Override
- public void onSyncStarting(SyncContext context, String account, boolean forced,
+ public void onSyncStarting(SyncContext context, Account account, boolean forced,
SyncResult result) {
mContentResolver = getContext().getContentResolver();
mServerDiffs = 0;
@@ -598,10 +624,11 @@
// Base sync info
map.put(Events._SYNC_ID, event.getId());
String version = event.getEditUri();
+ final Account account = getAccount();
if (!StringUtils.isEmpty(version)) {
// Always rewrite the edit URL to https for dasher account to avoid
// redirection.
- map.put(Events._SYNC_VERSION, rewriteUrlforAccount(getAccount(), version));
+ map.put(Events._SYNC_VERSION, rewriteUrlforAccount(account, version));
}
// see if this is an exception to an existing event/recurrence.
@@ -804,7 +831,8 @@
return ENTRY_INVALID;
}
- map.put(SyncConstValue._SYNC_ACCOUNT, getAccount());
+ map.put(SyncConstValue._SYNC_ACCOUNT, account.mName);
+ map.put(SyncConstValue._SYNC_ACCOUNT_TYPE, account.mType);
return ENTRY_OK;
}
@@ -1030,22 +1058,43 @@
final ContentResolver cr = getContext().getContentResolver();
mServerDiffs++;
final boolean syncingSingleFeed = (extras != null) && extras.containsKey("feed");
+ final boolean syncingMetafeedOnly = (extras != null) && extras.containsKey("metafeedonly");
+
if (syncingSingleFeed) {
+ if (syncingMetafeedOnly) {
+ Log.d(TAG, "metafeedonly and feed both set.");
+ return;
+ }
String feedUrl = extras.getString("feed");
getServerDiffsForFeed(context, baseSyncData, tempProvider, feedUrl,
baseSyncInfo, syncResult);
return;
}
+ // At this point, either metafeed sync or poll.
+ // For the poll (or metafeed sync), refresh the list of calendars.
+ // we can move away from this when we move to the new allcalendars feed, which is
+ // syncable. until then, we'll rely on the daily poll to keep the list of calendars
+ // up to date.
+
+ mRefresh++;
+ context.setStatusText("Fetching list of calendars");
+ fetchCalendarsFromServer();
+
+ if (syncingMetafeedOnly) {
+ // If not polling, nothing more to do.
+ return;
+ }
+
// select the set of calendars for this account.
+ final Account account = getAccount();
+ final String[] accountSelectionArgs = new String[]{account.mName, account.mType};
Cursor cursor = cr.query(Calendar.Calendars.CONTENT_URI,
CALENDARS_PROJECTION, SELECT_BY_ACCOUNT,
- new String[] { getAccount() }, null /* sort order */);
+ accountSelectionArgs, null /* sort order */);
Bundle syncExtras = new Bundle();
- boolean refreshCalendars = true;
-
try {
while (cursor.moveToNext()) {
boolean syncEvents = (cursor.getInt(6) == 1);
@@ -1055,24 +1104,6 @@
continue;
}
- // since this is a poll (no specific feed selected), refresh the list of calendars.
- // we can move away from this when we move to the new allcalendars feed, which is
- // syncable. until then, we'll rely on the daily poll to keep the list of calendars
- // up to date.
- if (refreshCalendars) {
- mRefresh++;
- context.setStatusText("Fetching list of calendars");
- // get rid of the current cursor and fetch from the server.
- cursor.close();
- final String[] accountSelectionArgs = new String[]{getAccount()};
- cursor = cr.query(
- Calendar.Calendars.LIVE_CONTENT_URI, CALENDARS_PROJECTION,
- SELECT_BY_ACCOUNT, accountSelectionArgs, null /* sort order */);
- // start over with the loop
- refreshCalendars = false;
- continue;
- }
-
// schedule syncs for each of these feeds.
syncExtras.clear();
syncExtras.putAll(extras);
@@ -1091,9 +1122,10 @@
final SyncInfo syncInfo = (SyncInfo) baseSyncInfo;
final GDataSyncData syncData = (GDataSyncData) baseSyncData;
+ final Account account = getAccount();
Cursor cursor = getContext().getContentResolver().query(Calendar.Calendars.CONTENT_URI,
CALENDARS_PROJECTION, SELECT_BY_ACCOUNT_AND_FEED,
- new String[] { getAccount(), feed }, null /* sort order */);
+ new String[] { account.mName, account.mType, feed }, null /* sort order */);
ContentValues map = new ContentValues();
int maxResults = getMaxEntriesPerSync();
@@ -1154,10 +1186,12 @@
// populate temp provider with calendar ids, so joins work.
ContentValues map = new ContentValues();
+ final Account account = getAccount();
Cursor c = getContext().getContentResolver().query(
Calendar.Calendars.CONTENT_URI,
CALENDARS_PROJECTION,
- SELECT_BY_ACCOUNT, new String[]{getAccount()}, null /* sort order */);
+ SELECT_BY_ACCOUNT, new String[]{account.mName, account.mType},
+ null /* sort order */);
final int idIndex = c.getColumnIndexOrThrow(Calendars._ID);
final int urlIndex = c.getColumnIndexOrThrow(Calendars.URL);
final int timezoneIndex = c.getColumnIndexOrThrow(Calendars.TIMEZONE);
@@ -1171,7 +1205,7 @@
c.close();
}
- public void onAccountsChanged(String[] accountsArray) {
+ public void onAccountsChanged(Account[] accountsArray) {
if (!"yes".equals(SystemProperties.get("ro.config.sync"))) {
return;
}
@@ -1188,12 +1222,17 @@
cursorA = Calendar.Calendars.query(cr, null /* projection */,
Calendar.Calendars.SELECTED + "=1", CALENDAR_KEY_SORT_ORDER);
int urlIndexA = cursorA.getColumnIndexOrThrow(Calendar.Calendars.URL);
- int accountIndexA = cursorA.getColumnIndexOrThrow(Calendar.Calendars._SYNC_ACCOUNT);
+ int accountNameIndexA = cursorA.getColumnIndexOrThrow(Calendar.Calendars._SYNC_ACCOUNT);
+ int accountTypeIndexA =
+ cursorA.getColumnIndexOrThrow(Calendar.Calendars._SYNC_ACCOUNT_TYPE);
cursorB = SubscribedFeeds.Feeds.query(cr, FEEDS_KEY_COLUMNS,
SubscribedFeeds.Feeds.AUTHORITY + "=?", new String[]{Calendar.AUTHORITY},
FEEDS_KEY_SORT_ORDER);
int urlIndexB = cursorB.getColumnIndexOrThrow(SubscribedFeeds.Feeds.FEED);
- int accountIndexB = cursorB.getColumnIndexOrThrow(SubscribedFeeds.Feeds._SYNC_ACCOUNT);
+ int accountNameIndexB =
+ cursorB.getColumnIndexOrThrow(SubscribedFeeds.Feeds._SYNC_ACCOUNT);
+ int accountTypeIndexB =
+ cursorB.getColumnIndexOrThrow(SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE);
for (CursorJoiner.Result joinerResult :
new CursorJoiner(cursorA, CALENDAR_KEY_COLUMNS, cursorB, FEEDS_KEY_COLUMNS)) {
switch (joinerResult) {
@@ -1201,7 +1240,8 @@
SubscribedFeeds.addFeed(
cr,
cursorA.getString(urlIndexA),
- cursorA.getString(accountIndexA),
+ new Account(cursorA.getString(accountNameIndexA),
+ cursorA.getString(accountTypeIndexA)),
Calendar.AUTHORITY,
CalendarClient.SERVICE);
break;
@@ -1209,7 +1249,8 @@
SubscribedFeeds.deleteFeed(
cr,
cursorB.getString(urlIndexB),
- cursorB.getString(accountIndexB),
+ new Account(cursorB.getString(accountNameIndexB),
+ cursorB.getString(accountTypeIndexB)),
Calendar.AUTHORITY);
break;
case BOTH:
@@ -1229,7 +1270,7 @@
* to/from the device, and thus is determined and passed around as a local variable, where
* appropriate.
*/
- protected String getFeedUrl(String account) {
+ protected String getFeedUrl(Account account) {
throw new UnsupportedOperationException("getFeedUrl() should not get called.");
}
@@ -1279,4 +1320,286 @@
sb.append("s").append(mServerDiffs);
}
}
+
+ private void fetchCalendarsFromServer() {
+ if (mCalendarClient == null) {
+ Log.w(TAG, "Cannot fetch calendars -- calendar url defined.");
+ return;
+ }
+
+ Account account = null;
+ String authToken = null;
+
+
+ try {
+ // TODO: allow caller to specify which account's feeds should be updated
+ Account[] accounts = AccountManager.get(getContext())
+ .blockingGetAccountsWithTypeAndFeatures(
+ GoogleLoginServiceConstants.ACCOUNT_TYPE,
+ new String[]{GoogleLoginServiceConstants.FEATURE_GOOGLE_OR_DASHER});
+ if (accounts.length == 0) {
+ Log.w(TAG, "Unable to update calendars from server -- no users configured.");
+ return;
+ }
+
+ account = accounts[0];
+
+ Bundle bundle = AccountManager.get(getContext()).getAuthToken(
+ account, mCalendarClient.getServiceName(),
+ true /* notifyAuthFailure */, null /* callback */, null /* handler */)
+ .getResult();
+ authToken = bundle.getString(Constants.AUTHTOKEN_KEY);
+ if (authToken == null) {
+ Log.w(TAG, "Unable to update calendars from server -- could not "
+ + "authenticate user " + account);
+ return;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to update calendars from server -- could not "
+ + "authenticate user " + account, e);
+ return;
+ } catch (AuthenticatorException e) {
+ Log.w(TAG, "Unable to update calendars from server -- could not "
+ + "authenticate user " + account, e);
+ return;
+ } catch (OperationCanceledException e) {
+ Log.w(TAG, "Unable to update calendars from server -- could not "
+ + "authenticate user " + account, e);
+ return;
+ }
+
+ // get the current set of calendars. we'll need to pay attention to
+ // which calendars we get back from the server, so we can delete
+ // calendars that have been deleted from the server.
+ Set<Long> existingCalendarIds = new HashSet<Long>();
+
+ getCurrentCalendars(existingCalendarIds);
+
+ // get and process the calendars meta feed
+ GDataParser parser = null;
+ try {
+ String feedUrl = mCalendarClient.getUserCalendarsUrl(account.mName);
+ feedUrl = CalendarSyncAdapter.rewriteUrlforAccount(account, feedUrl);
+ parser = mCalendarClient.getParserForUserCalendars(feedUrl, authToken);
+ // process the calendars
+ processCalendars(account, parser, existingCalendarIds);
+ } catch (ParseException pe) {
+ Log.w(TAG, "Unable to process calendars from server -- could not "
+ + "parse calendar feed.", pe);
+ return;
+ } catch (IOException ioe) {
+ Log.w(TAG, "Unable to process calendars from server -- encountered "
+ + "i/o error", ioe);
+ return;
+ } catch (HttpException e) {
+ switch (e.getStatusCode()) {
+ case HttpException.SC_UNAUTHORIZED:
+ Log.w(TAG, "Unable to process calendars from server -- could not "
+ + "authenticate user.", e);
+ return;
+ case HttpException.SC_GONE:
+ Log.w(TAG, "Unable to process calendars from server -- encountered "
+ + "an AllDeletedUnavailableException, this should never happen", e);
+ return;
+ default:
+ Log.w(TAG, "Unable to process calendars from server -- error", e);
+ return;
+ }
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+
+ // delete calendars that are no longer sent from the server.
+ final Uri calendarContentUri = Calendars.CONTENT_URI;
+ final ContentResolver cr = getContext().getContentResolver();
+ for (long calId : existingCalendarIds) {
+ // NOTE: triggers delete all events, instances for this calendar.
+ cr.delete(ContentUris.withAppendedId(calendarContentUri, calId),
+ null /* where */, null /* selectionArgs */);
+ }
+ }
+
+ private void getCurrentCalendars(Set<Long> calendarIds) {
+ final ContentResolver cr = getContext().getContentResolver();
+ Cursor cursor = cr.query(Calendars.CONTENT_URI,
+ new String[] { Calendars._ID },
+ null /* selection */,
+ null /* selectionArgs */,
+ null /* sort */);
+ if (cursor != null) {
+ try {
+ while (cursor.moveToNext()) {
+ calendarIds.add(cursor.getLong(0));
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ private void processCalendars(Account account,
+ GDataParser parser,
+ Set<Long> existingCalendarIds)
+ throws ParseException, IOException {
+ final ContentResolver cr = getContext().getContentResolver();
+ CalendarsFeed feed = (CalendarsFeed) parser.init();
+ Entry entry = null;
+ final Uri calendarContentUri = Calendars.CONTENT_URI;
+ ArrayList<ContentValues> inserts = new ArrayList<ContentValues>();
+ while (parser.hasMoreData()) {
+ entry = parser.readNextEntry(entry);
+ if (Config.LOGV) Log.v(TAG, "Read entry: " + entry.toString());
+ CalendarEntry calendarEntry = (CalendarEntry) entry;
+ ContentValues map = new ContentValues();
+ String feedUrl = calendarEntryToContentValues(account, feed, calendarEntry, map);
+ if (TextUtils.isEmpty(feedUrl)) {
+ continue;
+ }
+ long calId = -1;
+
+ Cursor c = cr.query(calendarContentUri,
+ new String[] { Calendars._ID },
+ Calendars.URL + "='"
+ + feedUrl + '\'' /* selection */,
+ null /* selectionArgs */,
+ null /* sort */);
+ if (c != null) {
+ try {
+ if (c.moveToFirst()) {
+ calId = c.getLong(0);
+ existingCalendarIds.remove(calId);
+ }
+ } finally {
+ c.close();
+ }
+ }
+
+ if (calId != -1) {
+ if (Config.LOGV) Log.v(TAG, "Updating calendar " + map);
+ // don't override the existing "selected" or "hidden" settings.
+ map.remove(Calendars.SELECTED);
+ map.remove(Calendars.HIDDEN);
+ cr.update(ContentUris.withAppendedId(calendarContentUri, calId), map,
+ null /* where */, null /* selectionArgs */);
+ } else {
+ // Select this calendar for syncing and display if it is
+ // selected and not hidden.
+ int syncAndDisplay = 0;
+ if (calendarEntry.isSelected() && !calendarEntry.isHidden()) {
+ syncAndDisplay = 1;
+ }
+ map.put(Calendars.SYNC_EVENTS, syncAndDisplay);
+ map.put(Calendars.SELECTED, syncAndDisplay);
+ map.put(Calendars.HIDDEN, 0);
+ map.put(Calendars._SYNC_ACCOUNT, account.mName);
+ map.put(Calendars._SYNC_ACCOUNT_TYPE, account.mType);
+ if (Config.LOGV) Log.v(TAG, "Adding calendar " + map);
+ inserts.add(map);
+ }
+ }
+ if (!inserts.isEmpty()) {
+ if (Config.LOGV) Log.v(TAG, "Bulk updating calendar list.");
+ cr.bulkInsert(calendarContentUri, inserts.toArray(new ContentValues[inserts.size()]));
+ }
+ }
+
+ /**
+ * Convert the CalenderEntry to a Bundle that can be inserted/updated into the
+ * Calendars table.
+ */
+ private String calendarEntryToContentValues(Account account, CalendarsFeed feed,
+ CalendarEntry entry,
+ ContentValues map) {
+ map.clear();
+
+ String url = entry.getAlternateLink();
+
+ // we only want to fetch the full-selfattendance calendar feeds
+ if (!TextUtils.isEmpty(url)) {
+ if (url.endsWith(EXPECTED_PROJECTION)) {
+ url = url.replace(EXPECTED_PROJECTION, DESIRED_PROJECTION);
+ }
+ } else {
+ // yuck. the alternate link was not available. we should
+ // reconstruct from the id.
+ url = entry.getId();
+ if (!TextUtils.isEmpty(url)) {
+ url = convertCalendarIdToFeedUrl(url);
+ } else {
+ if (Config.LOGV) {
+ Log.v(TAG, "Cannot generate url for calendar feed.");
+ }
+ return null;
+ }
+ }
+
+ url = rewriteUrlforAccount(account, url);
+
+ map.put(Calendars.URL, url);
+ map.put(Calendars.NAME, entry.getTitle());
+
+ // TODO:
+ map.put(Calendars.DISPLAY_NAME, entry.getTitle());
+
+ map.put(Calendars.TIMEZONE, entry.getTimezone());
+
+ String colorStr = entry.getColor();
+ if (!TextUtils.isEmpty(colorStr)) {
+ int color = Color.parseColor(colorStr);
+ // Ensure the alpha is set to max
+ color |= 0xff000000;
+ map.put(Calendars.COLOR, color);
+ }
+
+ map.put(Calendars.SELECTED, entry.isSelected() ? 1 : 0);
+
+ map.put(Calendars.HIDDEN, entry.isHidden() ? 1 : 0);
+
+ int accesslevel;
+ switch (entry.getAccessLevel()) {
+ case CalendarEntry.ACCESS_NONE:
+ accesslevel = Calendars.NO_ACCESS;
+ break;
+ case CalendarEntry.ACCESS_READ:
+ accesslevel = Calendars.READ_ACCESS;
+ break;
+ case CalendarEntry.ACCESS_FREEBUSY:
+ accesslevel = Calendars.FREEBUSY_ACCESS;
+ break;
+ case CalendarEntry.ACCESS_EDITOR:
+ accesslevel = Calendars.EDITOR_ACCESS;
+ break;
+ case CalendarEntry.ACCESS_OWNER:
+ accesslevel = Calendars.OWNER_ACCESS;
+ break;
+ default:
+ accesslevel = Calendars.NO_ACCESS;
+ }
+ map.put(Calendars.ACCESS_LEVEL, accesslevel);
+ // TODO: use the update time, when calendar actually supports this.
+ // right now, calendar modifies the update time frequently.
+ map.put(Calendars._SYNC_TIME, System.currentTimeMillis());
+
+ return url;
+ }
+
+ // TODO: unit test.
+ protected static final String convertCalendarIdToFeedUrl(String url) {
+ // id: http://www.google.com/calendar/feeds/<username>/<cal id>
+ // desired feed:
+ // http://www.google.com/calendar/feeds/<cal id>/<projection>
+ int start = url.indexOf(FEEDS_SUBSTRING);
+ if (start != -1) {
+ // strip out the */ in /feeds/*/
+ start += FEEDS_SUBSTRING.length();
+ int end = url.indexOf('/', start);
+ if (end != -1) {
+ url = url.replace(url.substring(start, end + 1), "");
+ }
+ url = url + "/private" + DESIRED_PROJECTION;
+ }
+ return url;
+ }
}
diff --git a/src/com/android/providers/calendar/CalendarSyncAdapterService.java b/src/com/android/providers/calendar/CalendarSyncAdapterService.java
new file mode 100644
index 0000000..7634f2c
--- /dev/null
+++ b/src/com/android/providers/calendar/CalendarSyncAdapterService.java
@@ -0,0 +1,29 @@
+package com.android.providers.calendar;
+
+import android.app.Service;
+import android.os.IBinder;
+import android.content.Intent;
+import android.content.ContentProviderClient;
+import android.content.ContentProvider;
+import android.content.SyncableContentProvider;
+import android.provider.Calendar;
+
+public class CalendarSyncAdapterService extends Service {
+ private ContentProviderClient mContentProviderClient = null;
+
+ public void onCreate() {
+ mContentProviderClient =
+ getContentResolver().acquireContentProviderClient(Calendar.CONTENT_URI);
+ }
+
+ public void onDestroy() {
+ mContentProviderClient.release();
+ }
+
+ public IBinder onBind(Intent intent) {
+ ContentProvider contentProvider = mContentProviderClient.getLocalContentProvider();
+ if (contentProvider == null) throw new IllegalStateException();
+ SyncableContentProvider syncableContentProvider = (SyncableContentProvider)contentProvider;
+ return syncableContentProvider.getTempProviderSyncAdapter().getISyncAdapter().asBinder();
+ }
+}
diff --git a/tests/src/com/android/providers/calendar/TestCalendarSyncAdapter.java b/tests/src/com/android/providers/calendar/TestCalendarSyncAdapter.java
index 136b382..1fc77e6 100644
--- a/tests/src/com/android/providers/calendar/TestCalendarSyncAdapter.java
+++ b/tests/src/com/android/providers/calendar/TestCalendarSyncAdapter.java
@@ -26,6 +26,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.text.format.DateUtils;
+import android.accounts.Account;
import java.util.Calendar;
import java.util.TimeZone;
@@ -40,7 +41,7 @@
}
@Override
- public void onSyncStarting(SyncContext context, String account, boolean forced,
+ public void onSyncStarting(SyncContext context, Account account, boolean forced,
SyncResult result)
{
}
@@ -162,7 +163,7 @@
throw new UnsupportedOperationException("not implemented");
}
- public void onAccountsChanged(String[] accounts) {
+ public void onAccountsChanged(Account[] accounts) {
}
}