Android Open Source - Rss-App-for-tumblr Fetcher Service






From Project

Back to project page Rss-App-for-tumblr.

License

The source code is released under:

GNU General Public License

If you think the Android project Rss-App-for-tumblr listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/**
 * Ace Shooting/*from  ww w  . j a v  a 2s .  co  m*/
 *
 * Copyright (c) 2014 Ace Shooting
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

package com.aceshooting.rssapp.service;

import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.IBinder;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.Xml;

import com.aceshooting.rssapp.BASE64;
import com.aceshooting.rssapp.MainTabActivity;
import com.aceshooting.rssapp.R;
import com.aceshooting.rssapp.Strings;
import com.aceshooting.rssapp.handler.RSSHandler;
import com.aceshooting.rssapp.provider.FeedData;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;

public class FetcherService extends IntentService {
  private static final int FETCHMODE_DIRECT = 1;
  
  private static final int FETCHMODE_REENCODE = 2;
  
  private static final String KEY_USERAGENT = "User-agent";
  
  private static final String VALUE_USERAGENT = "Mozilla/5.0";
  
  private static final String CHARSET = "charset=";
  
  private static final String COUNT = "COUNT(*)";
  
  private static final String CONTENT_TYPE_TEXT_HTML = "text/html";
  
  private static final String HREF = "href=\"";
  
  private static final String HTML_BODY = "<body";
  
  private static final String ENCODING = "encoding=\"";
  
  private static final String SERVICENAME = "RssFetcherService";
  
  private static final String ZERO = "0";
  
  private static final String GZIP = "gzip";
  
  /* Allow different positions of the "rel" attribute w.r.t. the "href" attribute */
  private static final Pattern feedLinkPattern = Pattern.compile("[.]*<link[^>]* ((rel=alternate|rel=\"alternate\")[^>]* href=\"[^\"]*\"|href=\"[^\"]*\"[^>]* (rel=alternate|rel=\"alternate\"))[^>]*>", Pattern.CASE_INSENSITIVE);
  
  /* Case insensitive */
  private static final Pattern feedIconPattern = Pattern.compile("[.]*<link[^>]* (rel=(\"shortcut icon\"|\"icon\"|icon)[^>]* href=\"[^\"]*\"|href=\"[^\"]*\"[^>]* rel=(\"shortcut icon\"|\"icon\"|icon))[^>]*>", Pattern.CASE_INSENSITIVE);
  
  private NotificationManager notificationManager;
  
  private static SharedPreferences preferences = null;
  
  private static Proxy proxy;
  
  private boolean destroyed;
  
  private RSSHandler handler;
  
  public FetcherService() {
    super(SERVICENAME);
    destroyed = false;
    HttpURLConnection.setFollowRedirects(true);
  }
  
  @Override
  public synchronized void onHandleIntent(Intent intent) {
    if (preferences == null) {
      try {
        preferences = PreferenceManager.getDefaultSharedPreferences(createPackageContext(Strings.PACKAGE, 0));
      } catch (NameNotFoundException e) {
        preferences = PreferenceManager.getDefaultSharedPreferences(FetcherService.this);
      }
    }
    
    if (intent.getBooleanExtra(Strings.SCHEDULED, false)) {
      SharedPreferences.Editor editor = preferences.edit();
      editor.putLong(Strings.PREFERENCE_LASTSCHEDULEDREFRESH, SystemClock.elapsedRealtime());
      editor.commit();
    }
    
    ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    
    final NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
    
    if (networkInfo != null && networkInfo.getState() == NetworkInfo.State.CONNECTED && intent != null) {
      if (preferences.getBoolean(Strings.SETTINGS_PROXYENABLED, false) && (networkInfo.getType() == ConnectivityManager.TYPE_WIFI || !preferences.getBoolean(Strings.SETTINGS_PROXYWIFIONLY, false))) {
        try {
          proxy = new Proxy(ZERO.equals(preferences.getString(Strings.SETTINGS_PROXYTYPE, ZERO)) ? Proxy.Type.HTTP : Proxy.Type.SOCKS, new InetSocketAddress(preferences.getString(Strings.SETTINGS_PROXYHOST, Strings.EMPTY), Integer.parseInt(preferences.getString(Strings.SETTINGS_PROXYPORT, Strings.DEFAULTPROXYPORT))));
        } catch (Exception e) {
          proxy = null;
        }
      } else {
        proxy = null;
      }
      
      int newCount = refreshFeeds(FetcherService.this, intent.getStringExtra(Strings.FEEDID), networkInfo, intent.getBooleanExtra(Strings.SETTINGS_OVERRIDEWIFIONLY, false));
      
      if (newCount > 0) {
        if (preferences.getBoolean(Strings.SETTINGS_NOTIFICATIONSENABLED, false)) {
          Cursor cursor = getContentResolver().query(FeedData.EntryColumns.CONTENT_URI, new String[] {COUNT}, new StringBuilder(FeedData.EntryColumns.READDATE).append(Strings.DB_ISNULL).toString(), null, null);
          
          cursor.moveToFirst();
          newCount = cursor.getInt(0);
          cursor.close();
          
          String text = new StringBuilder().append(newCount).append(' ').append(getString(R.string.newentries)).toString();
          
          Notification notification = new Notification(R.drawable.ic_statusbar_rss, text, System.currentTimeMillis());
          
          Intent notificationIntent = new Intent(FetcherService.this, MainTabActivity.class);
          
          PendingIntent contentIntent = PendingIntent.getActivity(FetcherService.this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
          
          if (preferences.getBoolean(Strings.SETTINGS_NOTIFICATIONSVIBRATE, false)) {
            notification.defaults = Notification.DEFAULT_VIBRATE;
          }
          notification.flags = Notification.FLAG_AUTO_CANCEL | Notification.FLAG_SHOW_LIGHTS;
          notification.ledARGB = 0xffffffff;
          notification.ledOnMS = 300;
          notification.ledOffMS = 1000;
          
          String ringtone = preferences.getString(Strings.SETTINGS_NOTIFICATIONSRINGTONE, null);
          
          if (ringtone != null && ringtone.length() > 0) {
            notification.sound = Uri.parse(ringtone);
          }
          notification.setLatestEventInfo(FetcherService.this, getString(R.string.rss_feeds), text, contentIntent);
          notificationManager.notify(0, notification);
        } else {
          notificationManager.cancel(0);
        }
      }
    }
  }
  
  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }
  
  @Override
  public void onCreate() {
    super.onCreate();
    notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
  }
  
  @Override
  public void onDestroy() {
    if (MainTabActivity.INSTANCE != null) {
      MainTabActivity.INSTANCE.internalSetProgressBarIndeterminateVisibility(false);
    }
    destroyed = true;
    if (handler != null) {
      handler.cancel();
    }
    super.onDestroy();
  }
  
  private int refreshFeeds(Context context, String feedId, NetworkInfo networkInfo, boolean overrideWifiOnly) {
    String selection = null;
    
    if (!overrideWifiOnly && networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
      selection = new StringBuilder(FeedData.FeedColumns.WIFIONLY).append("=0 or ").append(FeedData.FeedColumns.WIFIONLY).append(" IS NULL").toString(); // "IS NOT 1" does not work on 2.1
    }
    
    Cursor cursor = context.getContentResolver().query(feedId == null ? FeedData.FeedColumns.CONTENT_URI : FeedData.FeedColumns.CONTENT_URI(feedId), null, selection, null, null); // no managed query here
    
    int urlPosition = cursor.getColumnIndex(FeedData.FeedColumns.URL);
    
    int idPosition = cursor.getColumnIndex(FeedData.FeedColumns._ID);
    
    int lastUpdatePosition = cursor.getColumnIndex(FeedData.FeedColumns.REALLASTUPDATE);
    
    int titlePosition = cursor.getColumnIndex(FeedData.FeedColumns.NAME);
    
    int fetchmodePosition = cursor.getColumnIndex(FeedData.FeedColumns.FETCHMODE);
    
    int iconPosition = cursor.getColumnIndex(FeedData.FeedColumns.ICON);
    
    int imposeUseragentPosition = cursor.getColumnIndex(FeedData.FeedColumns.IMPOSE_USERAGENT);
    
    boolean followHttpHttpsRedirects = preferences.getBoolean(Strings.SETTINGS_HTTPHTTPSREDIRECTS, false);
    
    int result = 0;
    
    if (handler == null) {
      handler = new RSSHandler(context);
    }
    handler.setEfficientFeedParsing(preferences.getBoolean(Strings.SETTINGS_EFFICIENTFEEDPARSING, true));
    handler.setFetchImages(preferences.getBoolean(Strings.SETTINGS_FETCHPICTURES, false));
    
    while(!destroyed && cursor.moveToNext()) {
      String id = cursor.getString(idPosition);
      
      boolean imposeUserAgent = !cursor.isNull(imposeUseragentPosition) && cursor.getInt(imposeUseragentPosition) == 1;
      
      HttpURLConnection connection = null;
      
      try {
        String feedUrl = cursor.getString(urlPosition);
        
        connection = setupConnection(feedUrl, imposeUserAgent, followHttpHttpsRedirects);
        
        String redirectHost = connection.getURL().getHost(); // Feed icon should be fetched from target site, not from feedburner, so we're tracking all redirections
        
        String contentType = connection.getContentType();
        
        int fetchMode = cursor.getInt(fetchmodePosition);
        
        String iconUrl = null;
        
        handler.init(new Date(cursor.getLong(lastUpdatePosition)), id, cursor.getString(titlePosition), feedUrl);
        if (fetchMode == 0) {
          if (contentType != null && contentType.startsWith(CONTENT_TYPE_TEXT_HTML)) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(getConnectionInputStream(connection)));
            
            String line = null;
            
            String newFeedUrl = null;
            
            while ((line = reader.readLine()) != null) {
              if (line.indexOf(HTML_BODY) > -1) {
                break;
              } else {
                if (newFeedUrl == null) {
                  Matcher matcher = feedLinkPattern.matcher(line);
                  
                  if (matcher.find()) { // not "while" as only one link is needed
                    newFeedUrl = getHref(matcher.group(), feedUrl);
                  }
                }
                if (iconUrl == null) {
                  Matcher matcher = feedIconPattern.matcher(line);
                  
                  if (matcher.find()) { // not "while" as only one link is needed
                    iconUrl = getHref(matcher.group(), feedUrl);
                  }
                }
                if (newFeedUrl != null && iconUrl != null) {
                  break;
                }
              }
            }
            
            if (newFeedUrl != null) {
              redirectHost = connection.getURL().getHost();
              connection.disconnect();
              connection = setupConnection(newFeedUrl, imposeUserAgent, followHttpHttpsRedirects);
              contentType = connection.getContentType();
              handler.initFeedBaseUrl(newFeedUrl);
              
              ContentValues values = new ContentValues();
              
              values.put(FeedData.FeedColumns.URL, newFeedUrl);
              context.getContentResolver().update(FeedData.FeedColumns.CONTENT_URI(id), values, null, null);
            }
          }
          
          if (contentType != null) {
            int index = contentType.indexOf(CHARSET);
            
            if (index > -1) {
              int index2 = contentType.indexOf(';', index);
              
              try {
                Xml.findEncodingByName(index2 > -1 ?contentType.substring(index+8, index2) : contentType.substring(index+8));
                fetchMode = FETCHMODE_DIRECT;
              } catch (UnsupportedEncodingException usee) {
                fetchMode = FETCHMODE_REENCODE;
              }
            } else {
              fetchMode = FETCHMODE_REENCODE;
            }
            
          } else {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getConnectionInputStream(connection)));
            
            char[] chars = new char[20];
            
            int length = bufferedReader.read(chars);
            
            String xmlDescription = new String(chars, 0, length);
            
            redirectHost = connection.getURL().getHost();
            connection.disconnect();
            connection = setupConnection(connection.getURL(), imposeUserAgent, followHttpHttpsRedirects);
            
            int start = xmlDescription != null ?  xmlDescription.indexOf(ENCODING) : -1;
            
            if (start > -1) {
              try {
                Xml.findEncodingByName(xmlDescription.substring(start+10, xmlDescription.indexOf('"', start+11)));
                fetchMode = FETCHMODE_DIRECT;
              } catch (UnsupportedEncodingException usee) {
                fetchMode = FETCHMODE_REENCODE;
              }
            } else {
              fetchMode = FETCHMODE_DIRECT; // absolutely no encoding information found
            }
          }
          
          ContentValues values = new ContentValues();
          
          values.put(FeedData.FeedColumns.FETCHMODE, fetchMode);
          context.getContentResolver().update(FeedData.FeedColumns.CONTENT_URI(id), values, null, null);
        }
        
        /* check and optionally find favicon */
        byte[] iconBytes = cursor.getBlob(iconPosition);
        
        if (iconBytes == null) {
          if (iconUrl == null) {
            String baseUrl = new StringBuilder(connection.getURL().getProtocol()).append(Strings.PROTOCOL_SEPARATOR).append(redirectHost).toString();
            HttpURLConnection iconURLConnection = setupConnection(new URL(baseUrl), imposeUserAgent, followHttpHttpsRedirects);
            try {
              BufferedReader reader = new BufferedReader(new InputStreamReader(getConnectionInputStream(iconURLConnection)));
              String line = null;
              while ((line = reader.readLine()) != null) {
                if (line.indexOf(HTML_BODY) > -1) {
                  break;
                } else {
                  Matcher matcher = feedIconPattern.matcher(line);
                  if (matcher.find()) { // not "while" as only one link is needed
                    iconUrl = getHref(matcher.group(), baseUrl);
                    if (iconUrl != null) {
                      break;
                    }
                  }
                }
              }
            } catch (Exception e) {
            } finally {
              iconURLConnection.disconnect();
            }

            if (iconUrl == null) {
              iconUrl = new StringBuilder(baseUrl).append(Strings.FILE_FAVICON).toString();
            }
          }
          HttpURLConnection iconURLConnection = setupConnection(new URL(iconUrl), imposeUserAgent, followHttpHttpsRedirects);
          
          try {
            iconBytes = getBytes(getConnectionInputStream(iconURLConnection));
            ContentValues values = new ContentValues();
            
            values.put(FeedData.FeedColumns.ICON, iconBytes);
            context.getContentResolver().update(FeedData.FeedColumns.CONTENT_URI(id), values, null, null);
          } catch (Exception e) {
            ContentValues values = new ContentValues();
            
            values.put(FeedData.FeedColumns.ICON, new byte[0]); // no icon found or error
            context.getContentResolver().update(FeedData.FeedColumns.CONTENT_URI(id), values, null, null);
          } finally {
            iconURLConnection.disconnect();
          }
          
        }
        switch (fetchMode) {
          default:
          case FETCHMODE_DIRECT: {
            if (contentType != null) {
              int index = contentType.indexOf(CHARSET);
              
              int index2 = contentType.indexOf(';', index);
              
              InputStream inputStream = getConnectionInputStream(connection);
              
              handler.setInputStream(inputStream);
              Xml.parse(inputStream, Xml.findEncodingByName(index2 > -1 ?contentType.substring(index+8, index2) : contentType.substring(index+8)), handler);
            } else {
              InputStreamReader reader = new InputStreamReader(getConnectionInputStream(connection));
              
              handler.setReader(reader);
              Xml.parse(reader, handler);
            }
            break;
          }
          case FETCHMODE_REENCODE: {
            ByteArrayOutputStream ouputStream = new ByteArrayOutputStream();
            
            InputStream inputStream = getConnectionInputStream(connection);
            
            byte[] byteBuffer = new byte[4096];
            
            int n;
            
            while ( (n = inputStream.read(byteBuffer)) > 0 ) {
              ouputStream.write(byteBuffer, 0, n);
            }
            
            String xmlText = ouputStream.toString();
            
            int start = xmlText != null ?  xmlText.indexOf(ENCODING) : -1;
            
            if (start > -1) {
              Xml.parse(new StringReader(new String(ouputStream.toByteArray(), xmlText.substring(start+10, xmlText.indexOf('"', start+11)))), handler);
            } else {
              // use content type
              if (contentType != null) {
                
                int index = contentType.indexOf(CHARSET);
                
                if (index > -1) {
                  int index2 = contentType.indexOf(';', index);
                  
                  try {
                    StringReader reader = new StringReader(new String(ouputStream.toByteArray(), index2 > -1 ?contentType.substring(index+8, index2) : contentType.substring(index+8)));
                    
                    handler.setReader(reader);
                    Xml.parse(reader, handler);
                  } catch (Exception e) {
                    
                  }
                } else {
                  StringReader reader = new StringReader(new String(ouputStream.toByteArray()));
                  
                  handler.setReader(reader);
                  Xml.parse(reader, handler);
                  
                }
              }
            }
            break;
          }
        }
        connection.disconnect();
      } catch (FileNotFoundException e) {
        if (!handler.isDone() && !handler.isCancelled()) {
          ContentValues values = new ContentValues();
          values.put(FeedData.FeedColumns.FETCHMODE, 0); // resets the fetchmode to determine it again later
          values.put(FeedData.FeedColumns.ERROR, context.getString(R.string.error_feederror));
          context.getContentResolver().update(FeedData.FeedColumns.CONTENT_URI(id), values, null, null);
        }
      } catch (Throwable e) {
        if (!handler.isDone() && !handler.isCancelled()) {
          ContentValues values = new ContentValues();
          values.put(FeedData.FeedColumns.FETCHMODE, 0); // resets the fetchmode to determine it again later
          values.put(FeedData.FeedColumns.ERROR, e.getMessage());
          context.getContentResolver().update(FeedData.FeedColumns.CONTENT_URI(id), values, null, null);
        }
      } finally {
        if (connection != null) {
          connection.disconnect();
        }
      }
      result += handler.getNewCount();
    }
    cursor.close();
    
    if (result > 0) {
      context.sendBroadcast(new Intent(Strings.ACTION_UPDATEWIDGET).putExtra(Strings.COUNT, result));
    }
    return result;
  }
    
  private static String getHref(String line, String baseUrl) {
    int posStart = line.indexOf(HREF);
    
    if (posStart > -1) {
      String url = line.substring(posStart+6, line.indexOf('"', posStart+10)).replace(Strings.AMP_SG, Strings.AMP);
      
      if (url.startsWith(Strings.SLASH)) {
        int index = baseUrl.indexOf('/', 8);
        
        if (index > -1) {
          url = baseUrl.substring(0, index)+url;
        } else {
          url = baseUrl+url;
        }
      } else if (!url.startsWith(Strings.HTTP) && !url.startsWith(Strings.HTTPS)) {
        url = new StringBuilder(baseUrl).append('/').append(url).toString();
      }
      return url;
    } else {
      return null;
    }
  }
  
  private static final HttpURLConnection setupConnection(String url, boolean imposeUseragent, boolean followHttpHttpsRedirects) throws IOException, NoSuchAlgorithmException, KeyManagementException {
    return setupConnection(new URL(url), imposeUseragent, followHttpHttpsRedirects);
  }
  
  private static final HttpURLConnection setupConnection(URL url, boolean imposeUseragent, boolean followHttpHttpsRedirects) throws IOException, NoSuchAlgorithmException, KeyManagementException {
    return setupConnection(url, imposeUseragent, followHttpHttpsRedirects, 0);
  }
  
  private static final HttpURLConnection setupConnection(URL url, boolean imposeUseragent, boolean followHttpHttpsRedirects, int cycle) throws IOException, NoSuchAlgorithmException, KeyManagementException {
    HttpURLConnection connection = proxy == null ? (HttpURLConnection) url.openConnection() : (HttpURLConnection) url.openConnection(proxy);
    
    connection.setDoInput(true);
    connection.setDoOutput(false);
    if (imposeUseragent) {
      connection.setRequestProperty(KEY_USERAGENT, VALUE_USERAGENT); // some feeds need this to work properly
    }
    connection.setConnectTimeout(30000);
    connection.setReadTimeout(30000);
    connection.setUseCaches(false);
    
    if (url.getUserInfo() != null) {
      connection.setRequestProperty("Authorization", "Basic "+BASE64.encode(url.getUserInfo().getBytes()));
    }
    connection.setRequestProperty("connection", "close"); // Workaround for android issue 7786
    connection.setRequestProperty("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
    connection.connect();
    
    String location = connection.getHeaderField("Location");
    
    if (location != null && (url.getProtocol().equals(Strings._HTTP) && location.startsWith(Strings.HTTPS) || url.getProtocol().equals(Strings._HTTPS) && location.startsWith(Strings.HTTP))) {
      // if location != null, the system-automatic redirect has failed which indicates a protocol change
      if (followHttpHttpsRedirects) {
        connection.disconnect();
          
        if (cycle < 5) {
          return setupConnection(new URL(location), imposeUseragent, followHttpHttpsRedirects, cycle+1);
        } else {
          throw new IOException("Too many redirects.");
        }
      } else {
        throw new IOException("https<->http redirect - enable in settings");
      }
    }
    return connection;
  }
  
  public static byte[] getBytes(InputStream inputStream) throws IOException {
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    
    byte[] buffer = new byte[4096];
    
    int n;
    
    while ((n = inputStream.read(buffer)) > 0) {
      output.write(buffer, 0, n);
    }
    
    byte[] result  = output.toByteArray();
    
    output.close();
    inputStream.close();
    return result;
  }
  
  /**
   * This is a small wrapper for getting the properly encoded inputstream if is is gzip compressed
   * and not properly recognized.
   */
  private static InputStream getConnectionInputStream(HttpURLConnection connection) throws IOException {
    InputStream inputStream = connection.getInputStream();
    
    if (GZIP.equals(connection.getContentEncoding()) && !(inputStream instanceof GZIPInputStream)) {
      return new GZIPInputStream(inputStream);
    } else {
      return inputStream;
    }
  }
}




Java Source Code List

com.aceshooting.rssapp.Animations.java
com.aceshooting.rssapp.ApplicationPreferencesActivity.java
com.aceshooting.rssapp.BASE64.java
com.aceshooting.rssapp.BootCompletedBroadcastReceiver.java
com.aceshooting.rssapp.CompatibilityHelper.java
com.aceshooting.rssapp.EmptyActivity.java
com.aceshooting.rssapp.EntriesListActivity.java
com.aceshooting.rssapp.EntriesListAdapter.java
com.aceshooting.rssapp.EntryActivity.java
com.aceshooting.rssapp.FeedConfigActivity.java
com.aceshooting.rssapp.MainTabActivity.java
com.aceshooting.rssapp.MyApplication.java
com.aceshooting.rssapp.RSSOverviewListAdapter.java
com.aceshooting.rssapp.RSSOverview.java
com.aceshooting.rssapp.RefreshBroadcastReceiver.java
com.aceshooting.rssapp.Requeryable.java
com.aceshooting.rssapp.SimpleTask.java
com.aceshooting.rssapp.Strings.java
com.aceshooting.rssapp.handler.PictureFilenameFilter.java
com.aceshooting.rssapp.handler.RSSHandler.java
com.aceshooting.rssapp.provider.FeedDataContentProvider.java
com.aceshooting.rssapp.provider.FeedData.java
com.aceshooting.rssapp.provider.OPML.java
com.aceshooting.rssapp.service.FetcherService.java
com.aceshooting.rssapp.service.RefreshService.java
com.aceshooting.rssapp.widget.ColorPickerDialogPreference.java
com.aceshooting.rssapp.widget.SparseRSSAppWidgetProvider.java
com.aceshooting.rssapp.widget.WidgetConfigActivity.java