1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.threadsample; 18 19 import android.app.IntentService; 20 import android.content.ContentValues; 21 import android.content.Intent; 22 import android.database.Cursor; 23 24 import org.apache.http.HttpStatus; 25 import org.xmlpull.v1.XmlPullParserException; 26 27 import java.io.IOException; 28 import java.net.HttpURLConnection; 29 import java.net.MalformedURLException; 30 import java.net.URL; 31 import java.net.URLConnection; 32 import java.util.Date; 33 import java.util.Vector; 34 35 /** 36 * This service pulls RSS content from a web site URL contained in the incoming Intent (see 37 * onHandleIntent()). As it runs, it broadcasts its status using LocalBroadcastManager; any 38 * component that wants to see the status should implement a subclass of BroadcastReceiver and 39 * register to receive broadcast Intents with category = CATEGORY_DEFAULT and action 40 * Constants.BROADCAST_ACTION. 41 * 42 */ 43 public class RSSPullService extends IntentService { 44 // Used to write to the system log from this class. 45 public static final String LOG_TAG = "RSSPullService"; 46 47 // Defines and instantiates an object for handling status updates. 48 private BroadcastNotifier mBroadcaster = new BroadcastNotifier(this); 49 50 /** 51 * An IntentService must always have a constructor that calls the super constructor. The 52 * string supplied to the super constructor is used to give a name to the IntentService's 53 * background thread. 54 */ RSSPullService()55 public RSSPullService() { 56 57 super("RSSPullService"); 58 } 59 60 /** 61 * In an IntentService, onHandleIntent is run on a background thread. As it 62 * runs, it broadcasts its current status using the LocalBroadcastManager. 63 * @param workIntent The Intent that starts the IntentService. This Intent contains the 64 * URL of the web site from which the RSS parser gets data. 65 */ 66 @Override onHandleIntent(Intent workIntent)67 protected void onHandleIntent(Intent workIntent) { 68 // Gets a URL to read from the incoming Intent's "data" value 69 String localUrlString = workIntent.getDataString(); 70 71 // Creates a projection to use in querying the modification date table in the provider. 72 final String[] dateProjection = new String[] 73 { 74 DataProviderContract.ROW_ID, 75 DataProviderContract.DATA_DATE_COLUMN 76 }; 77 78 // A URL that's local to this method 79 URL localURL; 80 81 // A cursor that's local to this method. 82 Cursor cursor = null; 83 84 /* 85 * A block that tries to connect to the Picasa featured picture URL passed as the "data" 86 * value in the incoming Intent. The block throws exceptions (see the end of the block). 87 */ 88 try { 89 90 // Convert the incoming data string to a URL. 91 localURL = new URL(localUrlString); 92 93 /* 94 * Tries to open a connection to the URL. If an IO error occurs, this throws an 95 * IOException 96 */ 97 URLConnection localURLConnection = localURL.openConnection(); 98 99 // If the connection is an HTTP connection, continue 100 if ((localURLConnection instanceof HttpURLConnection)) { 101 102 // Broadcasts an Intent indicating that processing has started. 103 mBroadcaster.broadcastIntentWithState(Constants.STATE_ACTION_STARTED); 104 105 // Casts the connection to a HTTP connection 106 HttpURLConnection localHttpURLConnection = (HttpURLConnection) localURLConnection; 107 108 // Sets the user agent for this request. 109 localHttpURLConnection.setRequestProperty("User-Agent", Constants.USER_AGENT); 110 111 /* 112 * Queries the content provider to see if this URL was read previously, and when. 113 * The content provider throws an exception if the URI is invalid. 114 */ 115 cursor = getContentResolver().query( 116 DataProviderContract.DATE_TABLE_CONTENTURI, 117 dateProjection, 118 null, 119 null, 120 null); 121 122 // Flag to indicate that new metadata was retrieved 123 boolean newMetadataRetrieved; 124 125 /* 126 * Tests to see if the table contains a modification date for the URL 127 */ 128 if (null != cursor && cursor.moveToFirst()) { 129 130 // Find the URL's last modified date in the content provider 131 long storedModifiedDate = 132 cursor.getLong(cursor.getColumnIndex( 133 DataProviderContract.DATA_DATE_COLUMN) 134 ) 135 ; 136 137 /* 138 * If the modified date isn't 0, sets another request property to ensure that 139 * data is only downloaded if it has changed since the last recorded 140 * modification date. Formats the date according to the RFC1123 format. 141 */ 142 if (0 != storedModifiedDate) { 143 localHttpURLConnection.setRequestProperty( 144 "If-Modified-Since", 145 org.apache.http.impl.cookie.DateUtils.formatDate( 146 new Date(storedModifiedDate), 147 org.apache.http.impl.cookie.DateUtils.PATTERN_RFC1123)); 148 } 149 150 // Marks that new metadata does not need to be retrieved 151 newMetadataRetrieved = false; 152 153 } else { 154 155 /* 156 * No modification date was found for the URL, so newmetadata has to be 157 * retrieved. 158 */ 159 newMetadataRetrieved = true; 160 161 } 162 163 // Reports that the service is about to connect to the RSS feed 164 mBroadcaster.broadcastIntentWithState(Constants.STATE_ACTION_CONNECTING); 165 166 // Gets a response code from the RSS server 167 int responseCode = localHttpURLConnection.getResponseCode(); 168 169 switch (responseCode) { 170 171 // If the response is OK 172 case HttpStatus.SC_OK: 173 174 // Gets the last modified data for the URL 175 long lastModifiedDate = localHttpURLConnection.getLastModified(); 176 177 // Reports that the service is parsing 178 mBroadcaster.broadcastIntentWithState(Constants.STATE_ACTION_PARSING); 179 180 /* 181 * Instantiates a pull parser and uses it to parse XML from the RSS feed. 182 * The mBroadcaster argument send a broadcaster utility object to the 183 * parser. 184 */ 185 RSSPullParser localPicasaPullParser = new RSSPullParser(); 186 187 localPicasaPullParser.parseXml( 188 localURLConnection.getInputStream(), 189 mBroadcaster); 190 191 // Reports that the service is now writing data to the content provider. 192 mBroadcaster.broadcastIntentWithState(Constants.STATE_ACTION_WRITING); 193 194 // Gets image data from the parser 195 Vector<ContentValues> imageValues = localPicasaPullParser.getImages(); 196 197 // Stores the number of images 198 int imageVectorSize = imageValues.size(); 199 200 // Creates one ContentValues for each image 201 ContentValues[] imageValuesArray = new ContentValues[imageVectorSize]; 202 203 imageValuesArray = imageValues.toArray(imageValuesArray); 204 205 /* 206 * Stores the image data in the content provider. The content provider 207 * throws an exception if the URI is invalid. 208 */ 209 getContentResolver().bulkInsert( 210 DataProviderContract.PICTUREURL_TABLE_CONTENTURI, imageValuesArray); 211 212 // Creates another ContentValues for storing date information 213 ContentValues dateValues = new ContentValues(); 214 215 // Adds the URL's last modified date to the ContentValues 216 dateValues.put(DataProviderContract.DATA_DATE_COLUMN, lastModifiedDate); 217 218 if (newMetadataRetrieved) { 219 220 // No previous metadata existed, so insert the data 221 getContentResolver().insert( 222 DataProviderContract.DATE_TABLE_CONTENTURI, 223 dateValues 224 ); 225 226 } else { 227 228 // Previous metadata existed, so update it. 229 getContentResolver().update( 230 DataProviderContract.DATE_TABLE_CONTENTURI, 231 dateValues, 232 DataProviderContract.ROW_ID + "=" + 233 cursor.getString(cursor.getColumnIndex( 234 DataProviderContract.ROW_ID)), null); 235 } 236 break; 237 238 } 239 240 // Reports that the feed retrieval is complete. 241 mBroadcaster.broadcastIntentWithState(Constants.STATE_ACTION_COMPLETE); 242 } 243 244 // Handles possible exceptions 245 } catch (MalformedURLException localMalformedURLException) { 246 247 localMalformedURLException.printStackTrace(); 248 249 } catch (IOException localIOException) { 250 251 localIOException.printStackTrace(); 252 253 } catch (XmlPullParserException localXmlPullParserException) { 254 255 localXmlPullParserException.printStackTrace(); 256 257 } finally { 258 259 // If an exception occurred, close the cursor to prevent memory leaks. 260 if (null != cursor) { 261 cursor.close(); 262 } 263 } 264 } 265 266 } 267