blob: 3f7bef539015f21e2c46a2a2976023cc06ce7c1d [file] [log] [blame]
/*
* Copyright (C) 2008 Google Inc.
*
* 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.providers.contacts;
import com.google.android.collect.Sets;
import com.google.android.providers.AbstractGDataSyncAdapter;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SyncAdapter;
import android.content.SyncContext;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.SystemClock;
import android.provider.Contacts;
import android.text.TextUtils;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* A subclass of the platform contacts provider that adds the Google contacts
* sync adapter.
*/
public class GoogleContactsProvider extends ContactsProvider {
private static final int PURGE_CONTACTS_DELAY_IN_MS = 30000;
private static final String ACTION_PURGE_CONTACTS =
"com.android.providers.contacts.PURGE_CONTACTS";
/**
* SQL query that deletes all contacts for a given account that are not a member of
* at least one group that has the "should_sync" column set to a non-zero value.
*/
private static final String PURGE_UNSYNCED_CONTACTS_SQL = ""
+ "DELETE FROM people "
+ "WHERE (_id IN ("
+ " SELECT person "
+ " FROM ("
+ " SELECT MAX(should_sync) AS max_should_sync, person "
+ " FROM ("
+ " SELECT should_sync, person "
+ " FROM groupmembership AS gm "
+ " OUTER JOIN groups AS g "
+ " ON (gm.group_id=g._id "
+ " OR (gm.group_sync_id=g._sync_id "
+ " AND gm.group_sync_account=g._sync_account))) "
+ " GROUP BY person) "
+ " WHERE max_should_sync=0)"
+ " OR _id NOT IN (SELECT person FROM groupmembership))"
+ " AND _sync_dirty=0 "
+ " AND _sync_account=?";
private SyncAdapter mSyncAdapter = null;
private AlarmManager mAlarmService = null;
@Override
public boolean onCreate() {
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_PURGE_CONTACTS.equals(intent.getAction())) {
purgeContacts(intent.getStringExtra("account"));
}
}
};
getContext().registerReceiver(receiver, new IntentFilter(ACTION_PURGE_CONTACTS));
return super.onCreate();
}
@Override
public synchronized SyncAdapter getSyncAdapter() {
if (mSyncAdapter != null) {
return mSyncAdapter;
}
mSyncAdapter = new ContactsSyncAdapter(getContext(), this);
return mSyncAdapter;
}
@Override
protected void onLocalChangesForAccount(final ContentResolver resolver, String account,
boolean groupsModified) {
ContactsSyncAdapter.updateSubscribedFeeds(resolver, account);
if (groupsModified) {
schedulePurge(account);
}
}
/**
* Delete any non-sync_dirty contacts associated with the given account
* that are not in any of the synced groups.
*/
private void schedulePurge(String account) {
if (isTemporary()) {
throw new IllegalStateException("this must not be called on temp providers");
}
ensureAlarmService();
final Intent intent = new Intent(ACTION_PURGE_CONTACTS);
intent.putExtra("account", account);
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
getContext(), 0 /* ignored */, intent, 0);
mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + PURGE_CONTACTS_DELAY_IN_MS,
pendingIntent);
}
private void ensureAlarmService() {
if (mAlarmService == null) {
mAlarmService = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
}
}
@Override
public void onSyncStop(SyncContext context, boolean success) {
super.onSyncStop(context, success);
purgeContacts(getSyncingAccount());
}
private void purgeContacts(String account) {
if (isTemporary()) {
throw new IllegalStateException("this must not be called on temp providers");
}
SQLiteDatabase db = getDatabase();
db.beginTransaction();
try {
final String value = Contacts.Settings.getSetting(getContext().getContentResolver(),
account, Contacts.Settings.SYNC_EVERYTHING);
final boolean shouldSyncEverything = !TextUtils.isEmpty(value) && !"0".equals(value);
if (!shouldSyncEverything) {
db.execSQL(PURGE_UNSYNCED_CONTACTS_SQL, new String[]{account});
}
// remove any feeds in the SyncData that aren't in the current sync set.
Set<String> feedsToSync = Sets.newHashSet();
feedsToSync.add(ContactsSyncAdapter.getGroupsFeedForAccount(account));
ContactsSyncAdapter.addContactsFeedsToSync(getContext().getContentResolver(), account,
feedsToSync);
AbstractGDataSyncAdapter.GDataSyncData syncData = readSyncData(account);
if (syncData != null) {
Iterator<Map.Entry<String, AbstractGDataSyncAdapter.GDataSyncData.FeedData>> iter =
syncData.feedData.entrySet().iterator();
boolean updatedSyncData = false;
while (iter.hasNext()) {
Map.Entry<String, AbstractGDataSyncAdapter.GDataSyncData.FeedData> entry =
iter.next();
if (!feedsToSync.contains(entry.getKey())) {
iter.remove();
updatedSyncData = true;
}
}
if (updatedSyncData) {
writeSyncData(account, syncData);
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
private AbstractGDataSyncAdapter.GDataSyncData readSyncData(String account) {
if (!getDatabase().inTransaction()) {
throw new IllegalStateException("you can only call this from within a transaction");
}
Cursor c = getDatabase().query("_sync_state", new String[]{"data"}, "_sync_account=?",
new String[]{account}, null, null, null);
try {
byte[] data = null;
if (c.moveToNext()) data = c.getBlob(0);
return ContactsSyncAdapter.newGDataSyncDataFromBytes(data);
} finally {
c.close();
}
}
private void writeSyncData(String account, AbstractGDataSyncAdapter.GDataSyncData syncData) {
final SQLiteDatabase db = getDatabase();
if (!db.inTransaction()) {
throw new IllegalStateException("you can only call this from within a transaction");
}
db.delete("_sync_state", "_sync_account=?", new String[]{account});
ContentValues values = new ContentValues();
values.put("data", ContactsSyncAdapter.newBytesFromGDataSyncData(syncData));
values.put("_sync_account", account);
db.insert("_sync_state", "_sync_account", values);
}
}