summaryrefslogtreecommitdiff
path: root/src/com/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/providers/subscribedfeeds/GoogleSubscribedFeedsProvider.java31
-rw-r--r--src/com/android/providers/subscribedfeeds/SubscribedFeedsSyncAdapter.java244
-rw-r--r--src/com/android/settings/SyncActivityTooManyDeletes.java120
-rw-r--r--src/com/android/settings/SyncSettings.java391
-rw-r--r--src/com/android/settings/SyncStateCheckBoxPreference.java95
5 files changed, 881 insertions, 0 deletions
diff --git a/src/com/android/providers/subscribedfeeds/GoogleSubscribedFeedsProvider.java b/src/com/android/providers/subscribedfeeds/GoogleSubscribedFeedsProvider.java
new file mode 100644
index 0000000..87e6364
--- /dev/null
+++ b/src/com/android/providers/subscribedfeeds/GoogleSubscribedFeedsProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2008 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.providers.subscribedfeeds;
+
+import android.content.SyncAdapter;
+
+public class GoogleSubscribedFeedsProvider extends SubscribedFeedsProvider {
+ private SubscribedFeedsSyncAdapter mSyncAdapter;
+
+ @Override
+ public synchronized SyncAdapter getSyncAdapter() {
+ if (mSyncAdapter == null) {
+ mSyncAdapter = new SubscribedFeedsSyncAdapter(getContext(), this);
+ }
+ return mSyncAdapter;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/providers/subscribedfeeds/SubscribedFeedsSyncAdapter.java b/src/com/android/providers/subscribedfeeds/SubscribedFeedsSyncAdapter.java
new file mode 100644
index 0000000..355b95e
--- /dev/null
+++ b/src/com/android/providers/subscribedfeeds/SubscribedFeedsSyncAdapter.java
@@ -0,0 +1,244 @@
+/*
+** Copyright 2007, 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,
+** See the License for the specific language governing permissions and
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** limitations under the License.
+*/
+
+package com.android.providers.subscribedfeeds;
+
+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.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.parser.ParseException;
+import com.google.wireless.gdata.subscribedfeeds.client.SubscribedFeedsClient;
+import com.google.wireless.gdata.subscribedfeeds.data.FeedUrl;
+import com.google.wireless.gdata.subscribedfeeds.data.SubscribedFeedsEntry;
+import com.google.wireless.gdata.subscribedfeeds.parser.xml.XmlSubscribedFeedsGDataParserFactory;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SyncContext;
+import android.content.SyncResult;
+import android.content.SyncStats;
+import android.content.SyncableContentProvider;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.provider.SubscribedFeeds;
+import android.util.Log;
+
+/**
+ * Implements a SyncAdapter for SubscribedFeeds
+ */
+public class SubscribedFeedsSyncAdapter extends AbstractGDataSyncAdapter {
+ private final SubscribedFeedsClient mSubscribedFeedsClient;
+
+ private final static String FEED_URL = "https://android.clients.google.com/gsync/sub";
+
+ private static final String ROUTINGINFO_PARAMETER = "routinginfo";
+
+ protected SubscribedFeedsSyncAdapter(Context context, SyncableContentProvider provider) {
+ super(context, provider);
+ mSubscribedFeedsClient =
+ new SubscribedFeedsClient(
+ new AndroidGDataClient(context.getContentResolver()),
+ new XmlSubscribedFeedsGDataParserFactory(
+ new AndroidXmlParserFactory()));
+ }
+
+ @Override
+ protected GDataServiceClient getGDataServiceClient() {
+ return mSubscribedFeedsClient;
+ }
+
+ protected boolean handleAllDeletedUnavailable(GDataSyncData syncData, String feed) {
+ Log.w(TAG, "subscribed feeds don't use tombstones");
+
+ // this should never happen, but if it does pretend that we are able to handle it.
+ return true;
+ }
+
+ /*
+ * Takes the entry, casts it to a SubscribedFeedsEntry and executes the
+ * appropriate actions on the ContentProvider to represent the entry.
+ */
+ @Override
+ protected void updateProvider(Feed feed,
+ long syncLocalId,
+ boolean forceDelete, Entry baseEntry,
+ ContentProvider provider,
+ Object syncInfo) throws ParseException {
+
+ final SubscribedFeedsEntry entry = (SubscribedFeedsEntry) baseEntry;
+ final String editUri = entry.getEditUri();
+
+ if (forceDelete) {
+ final String serverId =
+ editUri.substring(editUri.lastIndexOf('/') + 1);
+ // Store the info about the subscribed feed
+ ContentValues values = new ContentValues();
+ values.put(SubscribedFeeds.Feeds._SYNC_ID, serverId);
+ provider.insert(SubscribedFeeds.Feeds.DELETED_CONTENT_URI, values);
+ return;
+ }
+
+ final String serverId = entry.getId().substring(entry.getId().lastIndexOf('/') + 1);
+ final String version = editUri.substring(editUri.lastIndexOf('/') + 1);
+
+ // Store the info about the person
+ ContentValues values = new ContentValues();
+ values.put(SubscribedFeeds.Feeds.FEED, entry.getSubscribedFeed().getFeed());
+ values.put(SubscribedFeeds.Feeds._SYNC_ACCOUNT, getAccount());
+ values.put(SubscribedFeeds.Feeds._SYNC_ID, serverId);
+ values.put(SubscribedFeeds.Feeds._SYNC_DIRTY, "0");
+ values.put(SubscribedFeeds.Feeds._SYNC_LOCAL_ID, syncLocalId);
+ values.put(SubscribedFeeds.Feeds._SYNC_TIME, version);
+ values.put(SubscribedFeeds.Feeds._SYNC_VERSION, version);
+ provider.insert(SubscribedFeeds.Feeds.CONTENT_URI, values);
+ }
+
+ @Override
+ protected String getFeedUrl(String account) {
+ return FEED_URL;
+ }
+
+ protected Class getFeedEntryClass() {
+ return SubscribedFeedsEntry.class;
+ }
+
+ @Override
+ protected void updateQueryParameters(QueryParams params) {
+ params.setParamValue(ROUTINGINFO_PARAMETER, getRoutingInfoForAccount(getAccount()));
+ }
+
+ @Override
+ protected Entry newEntry() {
+ return new SubscribedFeedsEntry();
+ }
+
+ /* (non-Javadoc)
+ * @see android.content.SyncAdapter#getServerDiffs
+ */
+ @Override
+ public void getServerDiffs(SyncContext context, SyncData syncData,
+ SyncableContentProvider tempProvider,
+ Bundle extras, Object syncInfo, SyncResult syncResult) {
+ tempProvider.setContainsDiffs(false /* the server returns all records, not just diffs */);
+ super.getServerDiffs(context, syncData, tempProvider, extras, syncInfo, syncResult);
+ }
+
+ public void onAccountsChanged(String[] accounts) {
+ // no need to do anything
+ }
+
+ @Override
+ protected Cursor getCursorForTable(ContentProvider cp, Class entryClass) {
+ if (entryClass != SubscribedFeedsEntry.class) {
+ throw new IllegalArgumentException("unexpected entry class, " + entryClass.getName());
+ }
+ return cp.query(SubscribedFeeds.Feeds.CONTENT_URI, null, null, null, null);
+ }
+
+ @Override
+ protected Cursor getCursorForDeletedTable(ContentProvider cp, Class entryClass) {
+ if (entryClass != SubscribedFeedsEntry.class) {
+ throw new IllegalArgumentException("unexpected entry class, " + entryClass.getName());
+ }
+ return cp.query(SubscribedFeeds.Feeds.DELETED_CONTENT_URI, null, null, null, null);
+ }
+
+ @Override
+ protected String cursorToEntry(SyncContext context,
+ Cursor c, Entry baseEntry, Object syncInfo) throws ParseException {
+ final String account = c.getString(c.getColumnIndex(SubscribedFeeds.Feeds._SYNC_ACCOUNT));
+ final String service = c.getString(c.getColumnIndex(SubscribedFeeds.Feeds.SERVICE));
+ final String id = c.getString(c.getColumnIndex(SubscribedFeeds.Feeds._SYNC_ID));
+ final String feed = c.getString(c.getColumnIndex(SubscribedFeeds.Feeds.FEED));
+
+ String authToken;
+ try {
+ authToken =
+ GoogleLoginServiceBlockingHelper.getAuthToken(getContext(), account, service);
+ } catch (GoogleLoginServiceBlockingHelper.AuthenticationException e) {
+ Log.e("Sync", "caught exception while attempting to get an " +
+ "authtoken for account " + account +
+ ", service " + service + ": " + e.toString());
+ throw new ParseException(e.getMessage(), e);
+ } catch (GoogleLoginServiceNotFoundException e) {
+ Log.e("Sync", "caught exception while attempting to get an " +
+ "authtoken for account " + account +
+ ", service " + service + ": " + e.toString());
+ throw new ParseException(e.getMessage(), e);
+ }
+
+ FeedUrl subscribedFeedUrl = new FeedUrl(feed, service, authToken);
+ SubscribedFeedsEntry entry = (SubscribedFeedsEntry) baseEntry;
+ if (id != null) {
+ entry.setId(getFeedUrl(account) + "/" + id);
+ entry.setEditUri(entry.getId());
+ }
+ entry.setRoutingInfo(getRoutingInfoForAccount(account));
+ entry.setSubscribedFeed(subscribedFeedUrl);
+ entry.setClientToken(subscribedFeedUrl.getFeed());
+
+ String createUrl = null;
+ if (id == null) {
+ createUrl = getFeedUrl(account);
+ }
+ return createUrl;
+ }
+
+ @Override
+ protected void deletedCursorToEntry(SyncContext context, Cursor c, Entry entry) {
+ final String id = c.getString(c.getColumnIndexOrThrow(SubscribedFeeds.Feeds._SYNC_ID));
+ entry.setId(id);
+ entry.setEditUri(getFeedUrl(getAccount()) + "/" + id);
+ }
+
+ /**
+ * Returns a string that can be used by the GSyncSubscriptionServer to
+ * route messages back to this account via GTalkService.
+ * TODO: this should eventually move into a general place so that others
+ * can use it.
+ *
+ * @param account the account whose routing info we want
+ * @return the GSyncSubscriptionServer routing string for this account
+ */
+ public String getRoutingInfoForAccount(String account) {
+ long androidId;
+ try {
+ androidId = GoogleLoginServiceBlockingHelper.getAndroidId(getContext());
+ } catch (GoogleLoginServiceNotFoundException e) {
+ Log.e(TAG, "Could not get routing info for account", e);
+ return null;
+ }
+ return Uri.parse("gtalk://" + account
+ + "#" + Settings.getGTalkDeviceId(androidId)).toString();
+ }
+
+ @Override
+ protected boolean hasTooManyDeletions(SyncStats stats) {
+ // allow subscribed feeds to delete any number of entries,
+ // since the client is the authority on which entries
+ // should exist.
+ return false;
+ }
+}
diff --git a/src/com/android/settings/SyncActivityTooManyDeletes.java b/src/com/android/settings/SyncActivityTooManyDeletes.java
new file mode 100644
index 0000000..3195ea3
--- /dev/null
+++ b/src/com/android/settings/SyncActivityTooManyDeletes.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2007 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.settings;
+
+import com.android.providers.subscribedfeeds.R;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * Presents multiple options for handling the case where a sync was aborted because there
+ * were too many pending deletes. One option is to force the delete, another is to rollback
+ * the deletes, the third is to do nothing.
+ */
+public class SyncActivityTooManyDeletes extends Activity
+ implements AdapterView.OnItemClickListener {
+
+ private long mNumDeletes;
+ private String mAccount;
+ private String mProvider;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle extras = getIntent().getExtras();
+ if (extras == null) {
+ finish();
+ return;
+ }
+
+ mNumDeletes = extras.getLong("numDeletes");
+ mAccount = extras.getString("account");
+ mProvider = extras.getString("provider");
+
+ // the order of these must match up with the constants for position used in onItemClick
+ CharSequence[] options = new CharSequence[]{
+ getResources().getText(R.string.sync_really_delete),
+ getResources().getText(R.string.sync_undo_deletes),
+ getResources().getText(R.string.sync_do_nothing)
+ };
+
+ ListAdapter adapter = new ArrayAdapter<CharSequence>(this,
+ android.R.layout.simple_list_item_1,
+ android.R.id.text1,
+ options);
+
+ ListView listView = new ListView(this);
+ listView.setAdapter(adapter);
+ listView.setItemsCanFocus(true);
+ listView.setOnItemClickListener(this);
+
+ TextView textView = new TextView(this);
+ CharSequence tooManyDeletesDescFormat =
+ getResources().getText(R.string.sync_too_many_deletes_desc);
+ textView.setText(String.format(tooManyDeletesDescFormat.toString(),
+ mNumDeletes, mProvider, mAccount));
+
+ final LinearLayout ll = new LinearLayout(this);
+ ll.setOrientation(LinearLayout.VERTICAL);
+ final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+ ll.addView(textView, lp);
+ ll.addView(listView, lp);
+ setContentView(ll);
+ }
+
+ public void onItemClick(AdapterView parent, View view, int position, long id) {
+ // the contants for position correspond to the items options array in onCreate()
+ if (position == 0) startSyncReallyDelete();
+ else if (position == 1) startSyncUndoDeletes();
+ finish();
+ }
+
+ private void startSyncReallyDelete() {
+ Uri uri = Uri.parse("content://" + mProvider);
+ Bundle extras = new Bundle();
+ extras.putString(ContentResolver.SYNC_EXTRAS_ACCOUNT, mAccount);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_FORCE, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
+ getContentResolver().startSync(uri, extras);
+ }
+
+ private void startSyncUndoDeletes() {
+ Uri uri = Uri.parse("content://" + mProvider);
+ Bundle extras = new Bundle();
+ extras.putString(ContentResolver.SYNC_EXTRAS_ACCOUNT, mAccount);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_FORCE, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
+ getContentResolver().startSync(uri, extras);
+ }
+}
diff --git a/src/com/android/settings/SyncSettings.java b/src/com/android/settings/SyncSettings.java
new file mode 100644
index 0000000..ec2c365
--- /dev/null
+++ b/src/com/android/settings/SyncSettings.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2008 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.settings;
+
+import com.android.providers.subscribedfeeds.R;
+
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.pim.DateFormat;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.provider.Sync;
+import android.provider.SubscribedFeeds;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Observable;
+import java.util.Observer;
+
+public class SyncSettings
+ extends PreferenceActivity
+ implements Observer {
+
+ List<String> mProviderNames;
+ List<ProviderInfo> mProviderInfos;
+
+ CheckBoxPreference mAutoSyncCheckBox;
+ TextView mErrorInfoView;
+
+ java.text.DateFormat mDateFormat;
+ java.text.DateFormat mTimeFormat;
+
+ private static final String SYNC_CONNECTION_SETTING_CHANGED
+ = "com.android.sync.SYNC_CONN_STATUS_CHANGED";
+
+ private static final String SYNC_KEY_PREFIX = "sync_";
+ private static final String SYNC_CHECKBOX_KEY = "autoSyncCheckBox";
+
+ Sync.Settings.QueryMap mSyncSettings;
+
+ private static final int MENU_SYNC_NOW_ID = Menu.FIRST;
+ private static final int MENU_SYNC_CANCEL_ID = Menu.FIRST + 1;
+
+ private Sync.Active.QueryMap mActiveSyncQueryMap = null;
+ private Sync.Status.QueryMap mStatusSyncQueryMap = null;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.sync_settings_list_content);
+ addPreferencesFromResource(R.xml.sync_settings);
+
+ mErrorInfoView = (TextView)findViewById(R.id.sync_settings_error_info);
+ mErrorInfoView.setVisibility(View.GONE);
+ mErrorInfoView.setCompoundDrawablesWithIntrinsicBounds(
+ getResources().getDrawable(R.drawable.ic_list_syncerror), null, null, null);
+
+ mDateFormat = DateFormat.getDateFormat(this);
+ mTimeFormat = DateFormat.getTimeFormat(this);
+
+ mStatusSyncQueryMap = new Sync.Status.QueryMap(getContentResolver(),
+ false /* don't keep updated yet, we will change this in onResume()/onPause() */,
+ null /* use this thread's handler for notifications */);
+ mStatusSyncQueryMap.addObserver(mSyncSuccessesObserver);
+
+ mActiveSyncQueryMap = new Sync.Active.QueryMap(getContentResolver(),
+ false /* don't keep updated yet, we will change this in onResume()/onPause() */,
+ null /* use this thread's handler for notifications */);
+ mActiveSyncQueryMap.addObserver(mSyncActiveObserver);
+
+ mSyncSettings = new Sync.Settings.QueryMap(getContentResolver(),
+ true /* keep updated */, null);
+ mSyncSettings.addObserver(this);
+
+ mAutoSyncCheckBox = (CheckBoxPreference) findPreference(SYNC_CHECKBOX_KEY);
+ initProviders();
+ initUI();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, MENU_SYNC_NOW_ID, 0, getString(R.string.sync_menu_sync_now))
+ .setIcon(R.drawable.ic_menu_refresh);
+ menu.add(0, MENU_SYNC_CANCEL_ID, 0, getString(R.string.sync_menu_sync_cancel))
+ .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ final boolean syncActive = mActiveSyncQueryMap.getActiveSyncInfo() != null;
+ menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive);
+ menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_SYNC_NOW_ID:
+ startSyncForEnabledProviders();
+ return true;
+ case MENU_SYNC_CANCEL_ID:
+ cancelSyncForEnabledProviders();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void initProviders() {
+ mProviderNames = new ArrayList<String>();
+ mProviderInfos = new ArrayList<ProviderInfo>();
+
+ try {
+ ActivityThread.getPackageManager().querySyncProviders(mProviderNames,
+ mProviderInfos);
+ } catch (RemoteException e) {
+ }
+ /*
+ for (int i = 0; i < mProviderNames.size(); i++) {
+ Log.i("SyncProviders", mProviderNames.get(i));
+ }
+ */
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mActiveSyncQueryMap.setKeepUpdated(true);
+ mStatusSyncQueryMap.setKeepUpdated(true);
+ onSyncStateUpdated();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mActiveSyncQueryMap.setKeepUpdated(false);
+ mStatusSyncQueryMap.setKeepUpdated(false);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mSyncSettings != null) {
+ mSyncSettings.close();
+ mSyncSettings = null;
+ }
+ if (mActiveSyncQueryMap != null) {
+ mActiveSyncQueryMap.close();
+ mActiveSyncQueryMap = null;
+ }
+ if (mStatusSyncQueryMap != null) {
+ mStatusSyncQueryMap.close();
+ mStatusSyncQueryMap = null;
+ }
+ }
+
+ private void initUI() {
+ // Set the Auto Sync toggle state
+ CheckBoxPreference autoSync = (CheckBoxPreference) findPreference(SYNC_CHECKBOX_KEY);
+ autoSync.setChecked(mSyncSettings.getListenForNetworkTickles());
+
+ // Find individual sync provider's states and initialize the toggles
+ for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
+ Preference pref = getPreferenceScreen().getPreference(i);
+ if (pref.hasKey() && pref.getKey().startsWith(SYNC_KEY_PREFIX)) {
+ CheckBoxPreference toggle = (CheckBoxPreference) pref;
+ String providerName = toggle.getKey().substring(SYNC_KEY_PREFIX.length());
+ boolean enabled =
+ mSyncSettings.getSyncProviderAutomatically(providerName);
+ toggle.setChecked(enabled);
+ }
+ }
+
+ }
+
+ public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) {
+ CheckBoxPreference togglePreference = (CheckBoxPreference) preference;
+ String key = preference.getKey();
+ if (key.equals(SYNC_CHECKBOX_KEY)) {
+ boolean oldListenForTickles = mSyncSettings.getListenForNetworkTickles();
+ boolean listenForTickles = togglePreference.isChecked();
+ if (oldListenForTickles != listenForTickles) {
+ mSyncSettings.setListenForNetworkTickles(listenForTickles);
+ Intent intent = new Intent();
+ intent.setAction(SYNC_CONNECTION_SETTING_CHANGED);
+ sendBroadcast(intent);
+ if (listenForTickles) {
+ startSyncForEnabledProviders();
+ }
+ }
+ if (!listenForTickles) {
+ cancelSyncForEnabledProviders();
+ }
+ } else if (key.startsWith(SYNC_KEY_PREFIX)) {
+ String providerName = key.substring(SYNC_KEY_PREFIX.length());
+ boolean syncOn = togglePreference.isChecked();
+
+ boolean oldSyncState = mSyncSettings.getSyncProviderAutomatically(providerName);
+ if (syncOn != oldSyncState) {
+ mSyncSettings.setSyncProviderAutomatically(providerName, syncOn);
+ if (syncOn) {
+ startSync(providerName);
+ } else {
+ cancelSync(providerName);
+ }
+ }
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ private void startSyncForEnabledProviders() {
+ cancelOrStartSyncForEnabledProviders(true /* start them */);
+ }
+
+ private void cancelSyncForEnabledProviders() {
+ cancelOrStartSyncForEnabledProviders(false /* cancel them */);
+ }
+
+ private void cancelOrStartSyncForEnabledProviders(boolean startSync) {
+ for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
+ Preference pref = getPreferenceScreen().getPreference(i);
+ if (pref.hasKey() && pref.getKey().startsWith(SYNC_KEY_PREFIX)) {
+ CheckBoxPreference toggle = (CheckBoxPreference) pref;
+ if (!toggle.isChecked()) {
+ continue;
+ }
+ final String authority = toggle.getKey().substring(SYNC_KEY_PREFIX.length());
+ if (startSync) {
+ startSync(authority);
+ } else {
+ cancelSync(authority);
+ }
+ }
+ }
+
+ // treat SubscribedFeeds as an enabled provider
+ final String authority = SubscribedFeeds.Feeds.CONTENT_URI.getAuthority();
+ if (startSync) {
+ startSync(authority);
+ } else {
+ cancelSync(authority);
+ }
+ }
+
+ private void startSync(String providerName) {
+ Uri uriToSync = (providerName != null) ? Uri.parse("content://" + providerName) : null;
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_FORCE, true);
+ getContentResolver().startSync(uriToSync, extras);
+ }
+
+ private void cancelSync(String authority) {
+ getContentResolver().cancelSync(Uri.parse("content://" + authority));
+ }
+
+ private Observer mSyncSuccessesObserver = new Observer() {
+ public void update(Observable o, Object arg) {
+ onSyncStateUpdated();
+ }
+ };
+
+ private Observer mSyncActiveObserver = new Observer() {
+ public void update(Observable o, Object arg) {
+ onSyncStateUpdated();
+ }
+ };
+
+ /**
+ * Returns the status row that matches the authority. If there are multiples accounts for
+ * the authority, the row with the latest LAST_SUCCESS_TIME column is returned.
+ * @param authority the authority whose row should be selected
+ * @return the ContentValues for the authority, or null if none exists
+ */
+ private ContentValues getStatusByAuthority(String authority) {
+ ContentValues row = null;
+ Map<String, ContentValues> rows = mStatusSyncQueryMap.getRows();
+ for (ContentValues values : rows.values()) {
+ if (values.getAsString(Sync.Status.AUTHORITY).equals(authority)) {
+ if (row == null) {
+ row = values;
+ continue;
+ }
+ final Long curTime = row.getAsLong(Sync.Status.LAST_SUCCESS_TIME);
+ if (curTime == null) {
+ row = values;
+ continue;
+ }
+ final Long newTime = values.getAsLong(Sync.Status.LAST_SUCCESS_TIME);
+ if (newTime == null) continue;
+ if (newTime > curTime) {
+ row = values;
+ }
+ }
+ }
+ return row;
+ }
+
+ boolean isAuthorityPending(String authority) {
+ Map<String, ContentValues> rows = mStatusSyncQueryMap.getRows();
+ for (ContentValues values : rows.values()) {
+ if (values.getAsString(Sync.Status.AUTHORITY).equals(authority)
+ && values.getAsLong(Sync.Status.PENDING) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void onSyncStateUpdated() {
+ // iterate over all the preferences, setting the state properly for each
+ Date date = new Date();
+ ContentValues activeSyncValues = mActiveSyncQueryMap.getActiveSyncInfo();
+ boolean syncIsFailing = false;
+ for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
+ Preference pref = getPreferenceScreen().getPreference(i);
+ final String prefKey = pref.getKey();
+ if (!TextUtils.isEmpty(prefKey) && prefKey.startsWith(SYNC_KEY_PREFIX)) {
+ String authority = prefKey.substring(SYNC_KEY_PREFIX.length());
+ SyncStateCheckBoxPreference toggle = (SyncStateCheckBoxPreference)pref;
+ ContentValues status = getStatusByAuthority(authority);
+
+ boolean syncEnabled = mSyncSettings.getSyncProviderAutomatically(authority);
+
+ boolean authorityIsPending = isAuthorityPending(authority);
+ boolean activelySyncing = activeSyncValues != null
+ && activeSyncValues.getAsString(Sync.Active.AUTHORITY).equals(authority);
+ boolean lastSyncFailed = status != null
+ && status.getAsString(Sync.Status.LAST_FAILURE_MESG) != null
+ && status.getAsLong(Sync.Status.LAST_FAILURE_MESG)
+ != Sync.Status.ERROR_SYNC_ALREADY_IN_PROGRESS;
+ if (!syncEnabled) lastSyncFailed = false;
+ if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
+ syncIsFailing = true;
+ }
+ final Long successEndTime =
+ status == null ? null : status.getAsLong(Sync.Status.LAST_SUCCESS_TIME);
+ if (successEndTime != null) {
+ date.setTime(successEndTime);
+ final String timeString = mDateFormat.format(date) + " "
+ + mTimeFormat.format(date);
+ toggle.setSummary(timeString);
+ } else {
+ toggle.setSummary("");
+ }
+ toggle.setActive(activelySyncing);
+ toggle.setPending(authorityIsPending);
+ toggle.setFailed(lastSyncFailed);
+ }
+ }
+ mErrorInfoView.setVisibility(syncIsFailing ? View.VISIBLE : View.GONE);
+ }
+
+ /** called when the sync settings change */
+ public void update(Observable o, Object arg) {
+ onSyncStateUpdated();
+ }
+}
diff --git a/src/com/android/settings/SyncStateCheckBoxPreference.java b/src/com/android/settings/SyncStateCheckBoxPreference.java
new file mode 100644
index 0000000..387c37d
--- /dev/null
+++ b/src/com/android/settings/SyncStateCheckBoxPreference.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2008 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.settings;
+
+import com.android.providers.subscribedfeeds.R;
+
+import android.content.Context;
+import android.graphics.drawable.AnimationDrawable;
+import android.preference.CheckBoxPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+public class SyncStateCheckBoxPreference extends CheckBoxPreference {
+
+ private boolean mIsActive = false;
+ private boolean mIsPending = false;
+ private boolean mFailed = false;
+
+ public SyncStateCheckBoxPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setWidgetLayoutResource(R.layout.preference_widget_sync_toggle);
+ }
+
+ @Override
+ public void onBindView(View view) {
+ super.onBindView(view);
+ ImageView syncActiveView = (ImageView) view.findViewById(R.id.sync_active);
+ View syncPendingView = view.findViewById(R.id.sync_pending);
+ View syncFailedView = view.findViewById(R.id.sync_failed);
+
+ syncActiveView.setVisibility(mIsActive ? View.VISIBLE : View.GONE);
+ AnimationDrawable anim = (AnimationDrawable) syncActiveView.getDrawable();
+ boolean showError;
+ boolean showPending;
+ if (mIsActive) {
+ anim.start();
+ showPending = false;
+ showError = false;
+ } else {
+ anim.stop();
+ if (mIsPending) {
+ showPending = true;
+ showError = false;
+ } else {
+ showPending = false;
+ showError = mFailed;
+ }
+ }
+
+ syncFailedView.setVisibility(showError ? View.VISIBLE : View.GONE);
+ syncPendingView.setVisibility((showPending && !mIsActive) ? View.VISIBLE : View.GONE);
+ }
+
+ /**
+ * Set whether the sync is active.
+ * @param isActive whether or not the sync is active
+ */
+ public void setActive(boolean isActive) {
+ mIsActive = isActive;
+ notifyChanged();
+ }
+
+ /**
+ * Set whether a sync is pending.
+ * @param isPending whether or not the sync is pending
+ */
+ public void setPending(boolean isPending) {
+ mIsPending = isPending;
+ notifyChanged();
+ }
+
+ /**
+ * Set whether the corresponding sync failed.
+ * @param failed whether or not the sync failed
+ */
+ public void setFailed(boolean failed) {
+ mFailed = failed;
+ notifyChanged();
+ }
+}