| /* |
| * 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); |
| } |
| }; |
| } |