1*90c8c64dSAndroid Build Coastguard Worker /* 2*90c8c64dSAndroid Build Coastguard Worker * Copyright 2013 The Android Open Source Project 3*90c8c64dSAndroid Build Coastguard Worker * 4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*90c8c64dSAndroid Build Coastguard Worker * 8*90c8c64dSAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*90c8c64dSAndroid Build Coastguard Worker * 10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License. 15*90c8c64dSAndroid Build Coastguard Worker */ 16*90c8c64dSAndroid Build Coastguard Worker 17*90c8c64dSAndroid Build Coastguard Worker package com.example.android.basicsyncadapter; 18*90c8c64dSAndroid Build Coastguard Worker 19*90c8c64dSAndroid Build Coastguard Worker import android.accounts.Account; 20*90c8c64dSAndroid Build Coastguard Worker import android.annotation.TargetApi; 21*90c8c64dSAndroid Build Coastguard Worker import android.content.AbstractThreadedSyncAdapter; 22*90c8c64dSAndroid Build Coastguard Worker import android.content.ContentProviderClient; 23*90c8c64dSAndroid Build Coastguard Worker import android.content.ContentProviderOperation; 24*90c8c64dSAndroid Build Coastguard Worker import android.content.ContentResolver; 25*90c8c64dSAndroid Build Coastguard Worker import android.content.Context; 26*90c8c64dSAndroid Build Coastguard Worker import android.content.OperationApplicationException; 27*90c8c64dSAndroid Build Coastguard Worker import android.content.SyncResult; 28*90c8c64dSAndroid Build Coastguard Worker import android.database.Cursor; 29*90c8c64dSAndroid Build Coastguard Worker import android.net.Uri; 30*90c8c64dSAndroid Build Coastguard Worker import android.os.Build; 31*90c8c64dSAndroid Build Coastguard Worker import android.os.Bundle; 32*90c8c64dSAndroid Build Coastguard Worker import android.os.RemoteException; 33*90c8c64dSAndroid Build Coastguard Worker import android.util.Log; 34*90c8c64dSAndroid Build Coastguard Worker 35*90c8c64dSAndroid Build Coastguard Worker import com.example.android.basicsyncadapter.net.FeedParser; 36*90c8c64dSAndroid Build Coastguard Worker import com.example.android.basicsyncadapter.provider.FeedContract; 37*90c8c64dSAndroid Build Coastguard Worker 38*90c8c64dSAndroid Build Coastguard Worker import org.xmlpull.v1.XmlPullParserException; 39*90c8c64dSAndroid Build Coastguard Worker 40*90c8c64dSAndroid Build Coastguard Worker import java.io.IOException; 41*90c8c64dSAndroid Build Coastguard Worker import java.io.InputStream; 42*90c8c64dSAndroid Build Coastguard Worker import java.net.HttpURLConnection; 43*90c8c64dSAndroid Build Coastguard Worker import java.net.MalformedURLException; 44*90c8c64dSAndroid Build Coastguard Worker import java.net.URL; 45*90c8c64dSAndroid Build Coastguard Worker import java.text.ParseException; 46*90c8c64dSAndroid Build Coastguard Worker import java.util.ArrayList; 47*90c8c64dSAndroid Build Coastguard Worker import java.util.HashMap; 48*90c8c64dSAndroid Build Coastguard Worker import java.util.List; 49*90c8c64dSAndroid Build Coastguard Worker 50*90c8c64dSAndroid Build Coastguard Worker /** 51*90c8c64dSAndroid Build Coastguard Worker * Define a sync adapter for the app. 52*90c8c64dSAndroid Build Coastguard Worker * 53*90c8c64dSAndroid Build Coastguard Worker * <p>This class is instantiated in {@link SyncService}, which also binds SyncAdapter to the system. 54*90c8c64dSAndroid Build Coastguard Worker * SyncAdapter should only be initialized in SyncService, never anywhere else. 55*90c8c64dSAndroid Build Coastguard Worker * 56*90c8c64dSAndroid Build Coastguard Worker * <p>The system calls onPerformSync() via an RPC call through the IBinder object supplied by 57*90c8c64dSAndroid Build Coastguard Worker * SyncService. 58*90c8c64dSAndroid Build Coastguard Worker */ 59*90c8c64dSAndroid Build Coastguard Worker class SyncAdapter extends AbstractThreadedSyncAdapter { 60*90c8c64dSAndroid Build Coastguard Worker public static final String TAG = "SyncAdapter"; 61*90c8c64dSAndroid Build Coastguard Worker 62*90c8c64dSAndroid Build Coastguard Worker /** 63*90c8c64dSAndroid Build Coastguard Worker * URL to fetch content from during a sync. 64*90c8c64dSAndroid Build Coastguard Worker * 65*90c8c64dSAndroid Build Coastguard Worker * <p>This points to the Android Developers Blog. (Side note: We highly recommend reading the 66*90c8c64dSAndroid Build Coastguard Worker * Android Developer Blog to stay up to date on the latest Android platform developments!) 67*90c8c64dSAndroid Build Coastguard Worker */ 68*90c8c64dSAndroid Build Coastguard Worker private static final String FEED_URL = "http://android-developers.blogspot.com/atom.xml"; 69*90c8c64dSAndroid Build Coastguard Worker 70*90c8c64dSAndroid Build Coastguard Worker /** 71*90c8c64dSAndroid Build Coastguard Worker * Network connection timeout, in milliseconds. 72*90c8c64dSAndroid Build Coastguard Worker */ 73*90c8c64dSAndroid Build Coastguard Worker private static final int NET_CONNECT_TIMEOUT_MILLIS = 15000; // 15 seconds 74*90c8c64dSAndroid Build Coastguard Worker 75*90c8c64dSAndroid Build Coastguard Worker /** 76*90c8c64dSAndroid Build Coastguard Worker * Network read timeout, in milliseconds. 77*90c8c64dSAndroid Build Coastguard Worker */ 78*90c8c64dSAndroid Build Coastguard Worker private static final int NET_READ_TIMEOUT_MILLIS = 10000; // 10 seconds 79*90c8c64dSAndroid Build Coastguard Worker 80*90c8c64dSAndroid Build Coastguard Worker /** 81*90c8c64dSAndroid Build Coastguard Worker * Content resolver, for performing database operations. 82*90c8c64dSAndroid Build Coastguard Worker */ 83*90c8c64dSAndroid Build Coastguard Worker private final ContentResolver mContentResolver; 84*90c8c64dSAndroid Build Coastguard Worker 85*90c8c64dSAndroid Build Coastguard Worker /** 86*90c8c64dSAndroid Build Coastguard Worker * Project used when querying content provider. Returns all known fields. 87*90c8c64dSAndroid Build Coastguard Worker */ 88*90c8c64dSAndroid Build Coastguard Worker private static final String[] PROJECTION = new String[] { 89*90c8c64dSAndroid Build Coastguard Worker FeedContract.Entry._ID, 90*90c8c64dSAndroid Build Coastguard Worker FeedContract.Entry.COLUMN_NAME_ENTRY_ID, 91*90c8c64dSAndroid Build Coastguard Worker FeedContract.Entry.COLUMN_NAME_TITLE, 92*90c8c64dSAndroid Build Coastguard Worker FeedContract.Entry.COLUMN_NAME_LINK, 93*90c8c64dSAndroid Build Coastguard Worker FeedContract.Entry.COLUMN_NAME_PUBLISHED}; 94*90c8c64dSAndroid Build Coastguard Worker 95*90c8c64dSAndroid Build Coastguard Worker // Constants representing column positions from PROJECTION. 96*90c8c64dSAndroid Build Coastguard Worker public static final int COLUMN_ID = 0; 97*90c8c64dSAndroid Build Coastguard Worker public static final int COLUMN_ENTRY_ID = 1; 98*90c8c64dSAndroid Build Coastguard Worker public static final int COLUMN_TITLE = 2; 99*90c8c64dSAndroid Build Coastguard Worker public static final int COLUMN_LINK = 3; 100*90c8c64dSAndroid Build Coastguard Worker public static final int COLUMN_PUBLISHED = 4; 101*90c8c64dSAndroid Build Coastguard Worker 102*90c8c64dSAndroid Build Coastguard Worker /** 103*90c8c64dSAndroid Build Coastguard Worker * Constructor. Obtains handle to content resolver for later use. 104*90c8c64dSAndroid Build Coastguard Worker */ SyncAdapter(Context context, boolean autoInitialize)105*90c8c64dSAndroid Build Coastguard Worker public SyncAdapter(Context context, boolean autoInitialize) { 106*90c8c64dSAndroid Build Coastguard Worker super(context, autoInitialize); 107*90c8c64dSAndroid Build Coastguard Worker mContentResolver = context.getContentResolver(); 108*90c8c64dSAndroid Build Coastguard Worker } 109*90c8c64dSAndroid Build Coastguard Worker 110*90c8c64dSAndroid Build Coastguard Worker /** 111*90c8c64dSAndroid Build Coastguard Worker * Constructor. Obtains handle to content resolver for later use. 112*90c8c64dSAndroid Build Coastguard Worker */ 113*90c8c64dSAndroid Build Coastguard Worker @TargetApi(Build.VERSION_CODES.HONEYCOMB) SyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs)114*90c8c64dSAndroid Build Coastguard Worker public SyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) { 115*90c8c64dSAndroid Build Coastguard Worker super(context, autoInitialize, allowParallelSyncs); 116*90c8c64dSAndroid Build Coastguard Worker mContentResolver = context.getContentResolver(); 117*90c8c64dSAndroid Build Coastguard Worker } 118*90c8c64dSAndroid Build Coastguard Worker 119*90c8c64dSAndroid Build Coastguard Worker /** 120*90c8c64dSAndroid Build Coastguard Worker * Called by the Android system in response to a request to run the sync adapter. The work 121*90c8c64dSAndroid Build Coastguard Worker * required to read data from the network, parse it, and store it in the content provider is 122*90c8c64dSAndroid Build Coastguard Worker * done here. Extending AbstractThreadedSyncAdapter ensures that all methods within SyncAdapter 123*90c8c64dSAndroid Build Coastguard Worker * run on a background thread. For this reason, blocking I/O and other long-running tasks can be 124*90c8c64dSAndroid Build Coastguard Worker * run <em>in situ</em>, and you don't have to set up a separate thread for them. 125*90c8c64dSAndroid Build Coastguard Worker . 126*90c8c64dSAndroid Build Coastguard Worker * 127*90c8c64dSAndroid Build Coastguard Worker * <p>This is where we actually perform any work required to perform a sync. 128*90c8c64dSAndroid Build Coastguard Worker * {@link android.content.AbstractThreadedSyncAdapter} guarantees that this will be called on a non-UI thread, 129*90c8c64dSAndroid Build Coastguard Worker * so it is safe to peform blocking I/O here. 130*90c8c64dSAndroid Build Coastguard Worker * 131*90c8c64dSAndroid Build Coastguard Worker * <p>The syncResult argument allows you to pass information back to the method that triggered 132*90c8c64dSAndroid Build Coastguard Worker * the sync. 133*90c8c64dSAndroid Build Coastguard Worker */ 134*90c8c64dSAndroid Build Coastguard Worker @Override onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult)135*90c8c64dSAndroid Build Coastguard Worker public void onPerformSync(Account account, Bundle extras, String authority, 136*90c8c64dSAndroid Build Coastguard Worker ContentProviderClient provider, SyncResult syncResult) { 137*90c8c64dSAndroid Build Coastguard Worker Log.i(TAG, "Beginning network synchronization"); 138*90c8c64dSAndroid Build Coastguard Worker try { 139*90c8c64dSAndroid Build Coastguard Worker final URL location = new URL(FEED_URL); 140*90c8c64dSAndroid Build Coastguard Worker InputStream stream = null; 141*90c8c64dSAndroid Build Coastguard Worker 142*90c8c64dSAndroid Build Coastguard Worker try { 143*90c8c64dSAndroid Build Coastguard Worker Log.i(TAG, "Streaming data from network: " + location); 144*90c8c64dSAndroid Build Coastguard Worker stream = downloadUrl(location); 145*90c8c64dSAndroid Build Coastguard Worker updateLocalFeedData(stream, syncResult); 146*90c8c64dSAndroid Build Coastguard Worker // Makes sure that the InputStream is closed after the app is 147*90c8c64dSAndroid Build Coastguard Worker // finished using it. 148*90c8c64dSAndroid Build Coastguard Worker } finally { 149*90c8c64dSAndroid Build Coastguard Worker if (stream != null) { 150*90c8c64dSAndroid Build Coastguard Worker stream.close(); 151*90c8c64dSAndroid Build Coastguard Worker } 152*90c8c64dSAndroid Build Coastguard Worker } 153*90c8c64dSAndroid Build Coastguard Worker } catch (MalformedURLException e) { 154*90c8c64dSAndroid Build Coastguard Worker Log.e(TAG, "Feed URL is malformed", e); 155*90c8c64dSAndroid Build Coastguard Worker syncResult.stats.numParseExceptions++; 156*90c8c64dSAndroid Build Coastguard Worker return; 157*90c8c64dSAndroid Build Coastguard Worker } catch (IOException e) { 158*90c8c64dSAndroid Build Coastguard Worker Log.e(TAG, "Error reading from network: " + e.toString()); 159*90c8c64dSAndroid Build Coastguard Worker syncResult.stats.numIoExceptions++; 160*90c8c64dSAndroid Build Coastguard Worker return; 161*90c8c64dSAndroid Build Coastguard Worker } catch (XmlPullParserException e) { 162*90c8c64dSAndroid Build Coastguard Worker Log.e(TAG, "Error parsing feed: " + e.toString()); 163*90c8c64dSAndroid Build Coastguard Worker syncResult.stats.numParseExceptions++; 164*90c8c64dSAndroid Build Coastguard Worker return; 165*90c8c64dSAndroid Build Coastguard Worker } catch (ParseException e) { 166*90c8c64dSAndroid Build Coastguard Worker Log.e(TAG, "Error parsing feed: " + e.toString()); 167*90c8c64dSAndroid Build Coastguard Worker syncResult.stats.numParseExceptions++; 168*90c8c64dSAndroid Build Coastguard Worker return; 169*90c8c64dSAndroid Build Coastguard Worker } catch (RemoteException e) { 170*90c8c64dSAndroid Build Coastguard Worker Log.e(TAG, "Error updating database: " + e.toString()); 171*90c8c64dSAndroid Build Coastguard Worker syncResult.databaseError = true; 172*90c8c64dSAndroid Build Coastguard Worker return; 173*90c8c64dSAndroid Build Coastguard Worker } catch (OperationApplicationException e) { 174*90c8c64dSAndroid Build Coastguard Worker Log.e(TAG, "Error updating database: " + e.toString()); 175*90c8c64dSAndroid Build Coastguard Worker syncResult.databaseError = true; 176*90c8c64dSAndroid Build Coastguard Worker return; 177*90c8c64dSAndroid Build Coastguard Worker } 178*90c8c64dSAndroid Build Coastguard Worker Log.i(TAG, "Network synchronization complete"); 179*90c8c64dSAndroid Build Coastguard Worker } 180*90c8c64dSAndroid Build Coastguard Worker 181*90c8c64dSAndroid Build Coastguard Worker /** 182*90c8c64dSAndroid Build Coastguard Worker * Read XML from an input stream, storing it into the content provider. 183*90c8c64dSAndroid Build Coastguard Worker * 184*90c8c64dSAndroid Build Coastguard Worker * <p>This is where incoming data is persisted, committing the results of a sync. In order to 185*90c8c64dSAndroid Build Coastguard Worker * minimize (expensive) disk operations, we compare incoming data with what's already in our 186*90c8c64dSAndroid Build Coastguard Worker * database, and compute a merge. Only changes (insert/update/delete) will result in a database 187*90c8c64dSAndroid Build Coastguard Worker * write. 188*90c8c64dSAndroid Build Coastguard Worker * 189*90c8c64dSAndroid Build Coastguard Worker * <p>As an additional optimization, we use a batch operation to perform all database writes at 190*90c8c64dSAndroid Build Coastguard Worker * once. 191*90c8c64dSAndroid Build Coastguard Worker * 192*90c8c64dSAndroid Build Coastguard Worker * <p>Merge strategy: 193*90c8c64dSAndroid Build Coastguard Worker * 1. Get cursor to all items in feed<br/> 194*90c8c64dSAndroid Build Coastguard Worker * 2. For each item, check if it's in the incoming data.<br/> 195*90c8c64dSAndroid Build Coastguard Worker * a. YES: Remove from "incoming" list. Check if data has mutated, if so, perform 196*90c8c64dSAndroid Build Coastguard Worker * database UPDATE.<br/> 197*90c8c64dSAndroid Build Coastguard Worker * b. NO: Schedule DELETE from database.<br/> 198*90c8c64dSAndroid Build Coastguard Worker * (At this point, incoming database only contains missing items.)<br/> 199*90c8c64dSAndroid Build Coastguard Worker * 3. For any items remaining in incoming list, ADD to database. 200*90c8c64dSAndroid Build Coastguard Worker */ updateLocalFeedData(final InputStream stream, final SyncResult syncResult)201*90c8c64dSAndroid Build Coastguard Worker public void updateLocalFeedData(final InputStream stream, final SyncResult syncResult) 202*90c8c64dSAndroid Build Coastguard Worker throws IOException, XmlPullParserException, RemoteException, 203*90c8c64dSAndroid Build Coastguard Worker OperationApplicationException, ParseException { 204*90c8c64dSAndroid Build Coastguard Worker final FeedParser feedParser = new FeedParser(); 205*90c8c64dSAndroid Build Coastguard Worker final ContentResolver contentResolver = getContext().getContentResolver(); 206*90c8c64dSAndroid Build Coastguard Worker 207*90c8c64dSAndroid Build Coastguard Worker Log.i(TAG, "Parsing stream as Atom feed"); 208*90c8c64dSAndroid Build Coastguard Worker final List<FeedParser.Entry> entries = feedParser.parse(stream); 209*90c8c64dSAndroid Build Coastguard Worker Log.i(TAG, "Parsing complete. Found " + entries.size() + " entries"); 210*90c8c64dSAndroid Build Coastguard Worker 211*90c8c64dSAndroid Build Coastguard Worker 212*90c8c64dSAndroid Build Coastguard Worker ArrayList<ContentProviderOperation> batch = new ArrayList<ContentProviderOperation>(); 213*90c8c64dSAndroid Build Coastguard Worker 214*90c8c64dSAndroid Build Coastguard Worker // Build hash table of incoming entries 215*90c8c64dSAndroid Build Coastguard Worker HashMap<String, FeedParser.Entry> entryMap = new HashMap<String, FeedParser.Entry>(); 216*90c8c64dSAndroid Build Coastguard Worker for (FeedParser.Entry e : entries) { 217*90c8c64dSAndroid Build Coastguard Worker entryMap.put(e.id, e); 218*90c8c64dSAndroid Build Coastguard Worker } 219*90c8c64dSAndroid Build Coastguard Worker 220*90c8c64dSAndroid Build Coastguard Worker // Get list of all items 221*90c8c64dSAndroid Build Coastguard Worker Log.i(TAG, "Fetching local entries for merge"); 222*90c8c64dSAndroid Build Coastguard Worker Uri uri = FeedContract.Entry.CONTENT_URI; // Get all entries 223*90c8c64dSAndroid Build Coastguard Worker Cursor c = contentResolver.query(uri, PROJECTION, null, null, null); 224*90c8c64dSAndroid Build Coastguard Worker assert c != null; 225*90c8c64dSAndroid Build Coastguard Worker Log.i(TAG, "Found " + c.getCount() + " local entries. Computing merge solution..."); 226*90c8c64dSAndroid Build Coastguard Worker 227*90c8c64dSAndroid Build Coastguard Worker // Find stale data 228*90c8c64dSAndroid Build Coastguard Worker int id; 229*90c8c64dSAndroid Build Coastguard Worker String entryId; 230*90c8c64dSAndroid Build Coastguard Worker String title; 231*90c8c64dSAndroid Build Coastguard Worker String link; 232*90c8c64dSAndroid Build Coastguard Worker long published; 233*90c8c64dSAndroid Build Coastguard Worker while (c.moveToNext()) { 234*90c8c64dSAndroid Build Coastguard Worker syncResult.stats.numEntries++; 235*90c8c64dSAndroid Build Coastguard Worker id = c.getInt(COLUMN_ID); 236*90c8c64dSAndroid Build Coastguard Worker entryId = c.getString(COLUMN_ENTRY_ID); 237*90c8c64dSAndroid Build Coastguard Worker title = c.getString(COLUMN_TITLE); 238*90c8c64dSAndroid Build Coastguard Worker link = c.getString(COLUMN_LINK); 239*90c8c64dSAndroid Build Coastguard Worker published = c.getLong(COLUMN_PUBLISHED); 240*90c8c64dSAndroid Build Coastguard Worker FeedParser.Entry match = entryMap.get(entryId); 241*90c8c64dSAndroid Build Coastguard Worker if (match != null) { 242*90c8c64dSAndroid Build Coastguard Worker // Entry exists. Remove from entry map to prevent insert later. 243*90c8c64dSAndroid Build Coastguard Worker entryMap.remove(entryId); 244*90c8c64dSAndroid Build Coastguard Worker // Check to see if the entry needs to be updated 245*90c8c64dSAndroid Build Coastguard Worker Uri existingUri = FeedContract.Entry.CONTENT_URI.buildUpon() 246*90c8c64dSAndroid Build Coastguard Worker .appendPath(Integer.toString(id)).build(); 247*90c8c64dSAndroid Build Coastguard Worker if ((match.title != null && !match.title.equals(title)) || 248*90c8c64dSAndroid Build Coastguard Worker (match.link != null && !match.link.equals(link)) || 249*90c8c64dSAndroid Build Coastguard Worker (match.published != published)) { 250*90c8c64dSAndroid Build Coastguard Worker // Update existing record 251*90c8c64dSAndroid Build Coastguard Worker Log.i(TAG, "Scheduling update: " + existingUri); 252*90c8c64dSAndroid Build Coastguard Worker batch.add(ContentProviderOperation.newUpdate(existingUri) 253*90c8c64dSAndroid Build Coastguard Worker .withValue(FeedContract.Entry.COLUMN_NAME_TITLE, match.title) 254*90c8c64dSAndroid Build Coastguard Worker .withValue(FeedContract.Entry.COLUMN_NAME_LINK, match.link) 255*90c8c64dSAndroid Build Coastguard Worker .withValue(FeedContract.Entry.COLUMN_NAME_PUBLISHED, match.published) 256*90c8c64dSAndroid Build Coastguard Worker .build()); 257*90c8c64dSAndroid Build Coastguard Worker syncResult.stats.numUpdates++; 258*90c8c64dSAndroid Build Coastguard Worker } else { 259*90c8c64dSAndroid Build Coastguard Worker Log.i(TAG, "No action: " + existingUri); 260*90c8c64dSAndroid Build Coastguard Worker } 261*90c8c64dSAndroid Build Coastguard Worker } else { 262*90c8c64dSAndroid Build Coastguard Worker // Entry doesn't exist. Remove it from the database. 263*90c8c64dSAndroid Build Coastguard Worker Uri deleteUri = FeedContract.Entry.CONTENT_URI.buildUpon() 264*90c8c64dSAndroid Build Coastguard Worker .appendPath(Integer.toString(id)).build(); 265*90c8c64dSAndroid Build Coastguard Worker Log.i(TAG, "Scheduling delete: " + deleteUri); 266*90c8c64dSAndroid Build Coastguard Worker batch.add(ContentProviderOperation.newDelete(deleteUri).build()); 267*90c8c64dSAndroid Build Coastguard Worker syncResult.stats.numDeletes++; 268*90c8c64dSAndroid Build Coastguard Worker } 269*90c8c64dSAndroid Build Coastguard Worker } 270*90c8c64dSAndroid Build Coastguard Worker c.close(); 271*90c8c64dSAndroid Build Coastguard Worker 272*90c8c64dSAndroid Build Coastguard Worker // Add new items 273*90c8c64dSAndroid Build Coastguard Worker for (FeedParser.Entry e : entryMap.values()) { 274*90c8c64dSAndroid Build Coastguard Worker Log.i(TAG, "Scheduling insert: entry_id=" + e.id); 275*90c8c64dSAndroid Build Coastguard Worker batch.add(ContentProviderOperation.newInsert(FeedContract.Entry.CONTENT_URI) 276*90c8c64dSAndroid Build Coastguard Worker .withValue(FeedContract.Entry.COLUMN_NAME_ENTRY_ID, e.id) 277*90c8c64dSAndroid Build Coastguard Worker .withValue(FeedContract.Entry.COLUMN_NAME_TITLE, e.title) 278*90c8c64dSAndroid Build Coastguard Worker .withValue(FeedContract.Entry.COLUMN_NAME_LINK, e.link) 279*90c8c64dSAndroid Build Coastguard Worker .withValue(FeedContract.Entry.COLUMN_NAME_PUBLISHED, e.published) 280*90c8c64dSAndroid Build Coastguard Worker .build()); 281*90c8c64dSAndroid Build Coastguard Worker syncResult.stats.numInserts++; 282*90c8c64dSAndroid Build Coastguard Worker } 283*90c8c64dSAndroid Build Coastguard Worker Log.i(TAG, "Merge solution ready. Applying batch update"); 284*90c8c64dSAndroid Build Coastguard Worker mContentResolver.applyBatch(FeedContract.CONTENT_AUTHORITY, batch); 285*90c8c64dSAndroid Build Coastguard Worker mContentResolver.notifyChange( 286*90c8c64dSAndroid Build Coastguard Worker FeedContract.Entry.CONTENT_URI, // URI where data was modified 287*90c8c64dSAndroid Build Coastguard Worker null, // No local observer 288*90c8c64dSAndroid Build Coastguard Worker false); // IMPORTANT: Do not sync to network 289*90c8c64dSAndroid Build Coastguard Worker // This sample doesn't support uploads, but if *your* code does, make sure you set 290*90c8c64dSAndroid Build Coastguard Worker // syncToNetwork=false in the line above to prevent duplicate syncs. 291*90c8c64dSAndroid Build Coastguard Worker } 292*90c8c64dSAndroid Build Coastguard Worker 293*90c8c64dSAndroid Build Coastguard Worker /** 294*90c8c64dSAndroid Build Coastguard Worker * Given a string representation of a URL, sets up a connection and gets an input stream. 295*90c8c64dSAndroid Build Coastguard Worker */ downloadUrl(final URL url)296*90c8c64dSAndroid Build Coastguard Worker private InputStream downloadUrl(final URL url) throws IOException { 297*90c8c64dSAndroid Build Coastguard Worker HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 298*90c8c64dSAndroid Build Coastguard Worker conn.setReadTimeout(NET_READ_TIMEOUT_MILLIS /* milliseconds */); 299*90c8c64dSAndroid Build Coastguard Worker conn.setConnectTimeout(NET_CONNECT_TIMEOUT_MILLIS /* milliseconds */); 300*90c8c64dSAndroid Build Coastguard Worker conn.setRequestMethod("GET"); 301*90c8c64dSAndroid Build Coastguard Worker conn.setDoInput(true); 302*90c8c64dSAndroid Build Coastguard Worker // Starts the query 303*90c8c64dSAndroid Build Coastguard Worker conn.connect(); 304*90c8c64dSAndroid Build Coastguard Worker return conn.getInputStream(); 305*90c8c64dSAndroid Build Coastguard Worker } 306*90c8c64dSAndroid Build Coastguard Worker } 307