/* * 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.example.codelab.rssexample; import android.app.Notification; import android.app.NotificationManager; import android.app.Service; import android.content.Intent; import android.content.SharedPreferences; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Bundle; import android.database.Cursor; import android.content.ContentResolver; import android.os.Handler; import android.text.TextUtils; import java.io.BufferedReader; import java.net.URL; import java.net.MalformedURLException; import java.lang.StringBuilder; import java.io.InputStreamReader; import java.io.IOException; import java.util.GregorianCalendar; import java.text.SimpleDateFormat; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.regex.Matcher; import java.text.ParseException; public class RssService extends Service implements Runnable{ private Logger mLogger = Logger.getLogger(this.getPackageName()); public static final String REQUERY_KEY = "Requery_All"; // Sent to tell us force a requery. public static final String RSS_URL = "RSS_URL"; // Sent to tell us to requery a specific item. private NotificationManager mNM; private Cursor mCur; // RSS content provider cursor. private GregorianCalendar mLastCheckedTime; // Time we last checked our feeds. private final String LAST_CHECKED_PREFERENCE = "last_checked"; static final int UPDATE_FREQUENCY_IN_MINUTES = 60; private Handler mHandler; // Handler to trap our update reminders. private final int NOTIFY_ID = 1; // Identifies our service icon in the icon tray. @Override protected void onCreate(){ // Display an icon to show that the service is running. mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); Intent clickIntent = new Intent(Intent.ACTION_MAIN); clickIntent.setClassName(MyRssReader5.class.getName()); Notification note = new Notification(this, R.drawable.rss_icon, "RSS Service", clickIntent, null); mNM.notify(NOTIFY_ID, note); mHandler = new Handler(); // Create the intent that will be launched if the user clicks the // icon on the status bar. This will launch our RSS Reader app. Intent intent = new Intent(MyRssReader.class); // Get a cursor over the RSS items. ContentResolver rslv = getContentResolver(); mCur = rslv.query(RssContentProvider.CONTENT_URI, null, null, null, null); // Load last updated value. // We store last updated value in preferences. SharedPreferences pref = getSharedPreferences("", 0); mLastCheckedTime = new GregorianCalendar(); mLastCheckedTime.setTimeInMillis(pref.getLong(LAST_CHECKED_PREFERENCE, 0)); //BEGIN_INCLUDE(5_1) // Need to run ourselves on a new thread, because // we will be making resource-intensive HTTP calls. // Our run() method will check whether we need to requery // our sources. Thread thr = new Thread(null, this, "rss_service_thread"); thr.start(); //END_INCLUDE(5_1) mLogger.info("RssService created"); } //BEGIN_INCLUDE(5_3) // A cheap way to pass a message to tell the service to requery. @Override protected void onStart(Intent intent, int startId){ super.onStart(startId, arguments); Bundle arguments = intent.getExtras(); if(arguments != null) { if(arguments.containsKey(REQUERY_KEY)) { queryRssItems(); } if(arguments.containsKey(RSS_URL)) { // Typically called after adding a new RSS feed to the list. queryItem(arguments.getString(RSS_URL)); } } } //END_INCLUDE(5_3) // When the service is destroyed, get rid of our persistent icon. @Override protected void onDestroy(){ mNM.cancel(NOTIFY_ID); } // Determines whether the next scheduled check time has passed. // Loads this value from a stored preference. If it has (or if no // previous value has been stored), it will requery all RSS feeds; // otherwise, it will post a delayed reminder to check again after // now - next_check_time milliseconds. public void queryIfPeriodicRefreshRequired() { GregorianCalendar nextCheckTime = new GregorianCalendar(); nextCheckTime = (GregorianCalendar) mLastCheckedTime.clone(); nextCheckTime.add(GregorianCalendar.MINUTE, UPDATE_FREQUENCY_IN_MINUTES); mLogger.info("last checked time:" + mLastCheckedTime.toString() + " Next checked time: " + nextCheckTime.toString()); if(mLastCheckedTime.before(nextCheckTime)) { queryRssItems(); } else { // Post a message to query again when we get to the next check time. long timeTillNextUpdate = mLastCheckedTime.getTimeInMillis() - GregorianCalendar.getInstance().getTimeInMillis(); mHandler.postDelayed(this, 1000 * 60 * UPDATE_FREQUENCY_IN_MINUTES); } } // Query all feeds. If the new feed has a newer pubDate than the previous, // then update it. void queryRssItems(){ mLogger.info("Querying Rss feeds..."); // The cursor might have gone stale. Requery to be sure. // We need to call next() after a requery to get to the // first record. mCur.requery(); while (mCur.next()){ // Get the URL for the feed from the cursor. int urlColumnIndex = mCur.getColumnIndex(RssContentProvider.URL); String url = mCur.getString(urlColumnIndex); queryItem(url); } // Reset the global "last checked" time mLastCheckedTime.setTimeInMillis(System.currentTimeMillis()); // Post a message to query again in [update_frequency] minutes mHandler.postDelayed(this, 1000 * 60 * UPDATE_FREQUENCY_IN_MINUTES); } // Query an individual RSS feed. Returns true if successful, false otherwise. private boolean queryItem(String url) { try { URL wrappedUrl = new URL(url); String rssFeed = readRss(wrappedUrl); mLogger.info("RSS Feed " + url + ":\n " + rssFeed); if(TextUtils.isEmpty(rssFeed)) { return false; } // Parse out the feed update date, and compare to the current version. // If feed update time is newer, or zero (if never updated, for new // items), then update the content, date, and hasBeenRead fields. // lastUpdated = <rss><channel><pubDate>value</pubDate></channel></rss>. // If that value doesn't exist, the current date is used. GregorianCalendar feedPubDate = parseRssDocPubDate(rssFeed); GregorianCalendar lastUpdated = new GregorianCalendar(); int lastUpdatedColumnIndex = mCur.getColumnIndex(RssContentProvider.LAST_UPDATED); lastUpdated.setTimeInMillis(mCur.getLong(lastUpdatedColumnIndex)); if(lastUpdated.getTimeInMillis() == 0 || lastUpdated.before(feedPubDate) && !TextUtils.isEmpty(rssFeed)) { // Get column indices. int contentColumnIndex = mCur.getColumnIndex(RssContentProvider.CONTENT); int updatedColumnIndex = mCur.getColumnIndex(RssContentProvider.HAS_BEEN_READ); // Update values. mCur.updateString(contentColumnIndex, rssFeed); mCur.updateLong(lastUpdatedColumnIndex, feedPubDate.getTimeInMillis()); mCur.updateInt(updatedColumnIndex, 0); mCur.commitUpdates(); } } catch (MalformedURLException ex) { mLogger.warning("Error in queryItem: Bad url"); return false; } return true; } // BEGIN_INCLUDE(5_2) // Get the <pubDate> content from a feed and return a // GregorianCalendar version of the date. // If the element doesn't exist or otherwise can't be // found, return a date of 0 to force a refresh. private GregorianCalendar parseRssDocPubDate(String xml){ GregorianCalendar cal = new GregorianCalendar(); cal.setTimeInMillis(0); String patt ="<[\\s]*pubDate[\\s]*>(.+?)</pubDate[\\s]*>"; Pattern p = Pattern.compile(patt); Matcher m = p.matcher(xml); try { if(m.find()) { mLogger.info("pubDate: " + m.group()); SimpleDateFormat pubDate = new SimpleDateFormat(); cal.setTime(pubDate.parse(m.group(1))); } } catch(ParseException ex) { mLogger.warning("parseRssDocPubDate couldn't find a <pubDate> tag. Returning default value."); } return cal; } // Read the submitted RSS page. String readRss(URL url){ String html = "<html><body><h2>No data</h2></body></html>"; try { mLogger.info("URL is:" + url.toString()); BufferedReader inStream = new BufferedReader(new InputStreamReader(url.openStream()), 1024); String line; StringBuilder rssFeed = new StringBuilder(); while ((line = inStream.readLine()) != null){ rssFeed.append(line); } html = rssFeed.toString(); } catch(IOException ex) { mLogger.warning("Couldn't open an RSS stream"); } return html; } //END_INCLUDE(5_2) // Callback we send to ourself to requery all feeds. public void run() { queryIfPeriodicRefreshRequired(); } // Required by Service. We won't implement it here, but need to // include this basic code. @Override public IBinder onBind(Intent intent){ return mBinder; } // This is the object that receives RPC calls from clients.See // RemoteService for a more complete example. private final IBinder mBinder = new Binder() { @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) { return super.onTransact(code, data, reply, flags); } }; }