FileDocCategorySizeDatePackage
SubscribedFeedsSyncAdapter.javaAPI DocAndroid 1.5 API9903Wed May 06 22:42:48 BST 2009com.android.providers.subscribedfeeds

SubscribedFeedsSyncAdapter.java

/*
** 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;
import android.text.TextUtils;

/**
 * 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),
                    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.
     */
    protected void updateProvider(Feed feed, Long syncLocalId,
            Entry baseEntry, ContentProvider provider, Object syncInfo)
            throws ParseException {
        ContentValues values = new ContentValues();
        values.put(SubscribedFeeds.Feeds._SYNC_ACCOUNT, getAccount());
        values.put(SubscribedFeeds.Feeds._SYNC_LOCAL_ID, syncLocalId);
        final SubscribedFeedsEntry entry = (SubscribedFeedsEntry) baseEntry;
        final String id = entry.getId();
        final String editUri = entry.getEditUri();
        String version = null;
        if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(editUri)) {
            final String serverId = id.substring(id.lastIndexOf('/') + 1);
            version = editUri.substring(editUri.lastIndexOf('/') + 1);
            values.put(SubscribedFeeds.Feeds._SYNC_ID, serverId);
            values.put(SubscribedFeeds.Feeds._SYNC_VERSION, version);
        }
        if (baseEntry.isDeleted()) {
            provider.insert(SubscribedFeeds.Feeds.DELETED_CONTENT_URI, values);
        } else {
            values.put(SubscribedFeeds.Feeds.FEED, entry.getSubscribedFeed().getFeed());
            if (!TextUtils.isEmpty(version)) {
                values.put(SubscribedFeeds.Feeds._SYNC_TIME, version);
            }
            values.put(SubscribedFeeds.Feeds._SYNC_DIRTY, "0");
            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;
    }
}