/*
* Copyright (C) 2012 - 2013 Niall 'Rivernile' Scott
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors or contributors be held liable for
* any damages arising from the use of this software.
*
* The aforementioned copyright holder(s) hereby grant you a
* non-transferrable right to use this software for any purpose (including
* commercial applications), and to modify it and redistribute it, subject to
* the following conditions:
*
* 1. This notice may not be removed or altered from any file it appears in.
*
* 2. Any modifications made to this software, except those defined in
* clause 3 of this agreement, must be released under this license, and
* the source code of any modifications must be made available on a
* publically accessible (and locateable) website, or sent to the
* original author of this software.
*
* 3. Software modifications that do not alter the functionality of the
* software but are simply adaptations to a specific environment are
* exempt from clause 2.
*/
package uk.org.rivernile.edinburghbustracker.android.twitter;
import android.content.Context;
import android.text.Html;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Random;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import uk.org.rivernile.android.utils.SimpleResultLoader;
import uk.org.rivernile.edinburghbustracker.android.ApiKey;
/**
* This Loader handles fetching data from Twitter to display as news items
* inside the application. This Loader does not expect any arguments.
*
* @author Niall Scott
* @see SimpleResultLoader
*/
public class TwitterUpdatesLoader
extends SimpleResultLoader<TwitterLoaderResult> {
/** Error code for when no data is available. */
public static final byte ERROR_NODATA = 0;
/** Error code for when there was a problem parsing the incoming data. */
public static final byte ERROR_PARSEERR = 1;
/** Error code for an IO error, such as a socket error or reading error. */
public static final byte ERROR_IOERR = 2;
/** There was a problem parsing the URL. Should never happen. */
public static final byte ERROR_URLERR = 3;
/**
* Error code for when the URL the client thought it was requesting data
* from differs from the URL it is receiving data from.
*/
public static final byte ERROR_URLMISMATCH = 4;
private static final String REQUEST_URL = "http://edinb.us/api/" +
"TwitterStatuses?appName=MBE&key=";
private static final Random random = new Random(System.currentTimeMillis());
/**
* Create a new TwitterUpdatesLoader.
*
* @param context A Context object.
*/
public TwitterUpdatesLoader(final Context context) {
super(context);
}
/**
* {@inheritDoc}
*/
@Override
public TwitterLoaderResult loadInBackground() {
final ArrayList<TwitterNewsItem> items =
new ArrayList<TwitterNewsItem>();
final StringBuilder urlBuilder = new StringBuilder(REQUEST_URL);
urlBuilder.append(ApiKey.getHashedKey());
urlBuilder.append("&random=").append(random.nextInt());
byte error;
try {
// Create URL object.
final URL u = new URL(urlBuilder.toString());
// Open the connection to the HTTP server.
final HttpURLConnection con = (HttpURLConnection)u.openConnection();
// Buffer the input.
final BufferedReader in = new BufferedReader(new InputStreamReader(
con.getInputStream()));
// Check to see if the URL we've connected to is what we expected.
if(u.getHost().equals(con.getURL().getHost())) {
// All text will be put in to a StringBuilder.
final StringBuilder sb = new StringBuilder();
String lineIn;
// Keep accepting input until there is no more.
while((lineIn = in.readLine()) != null) {
sb.append(lineIn);
}
// Parse the JSON.
error = parseJSON(items, sb.toString());
} else {
error = ERROR_URLMISMATCH;
}
// Remember to release resources.
in.close();
con.disconnect();
} catch(MalformedURLException e) {
error = ERROR_URLERR;
} catch(IOException e) {
error = ERROR_IOERR;
} catch(JSONException e) {
error = ERROR_PARSEERR;
}
// Error codes are >= 0, so if the error code matches this, create an
// error object, otherwise create a result object.
if(error < 0) {
return new TwitterLoaderResult(items);
} else {
return new TwitterLoaderResult(error);
}
}
/**
* Parse the JSON which was received from Twitter.
*
* @param items The ArrayList to put all TwitterNewsItems in to.
* @param jsonString The source JSON String.
* @throws JSONException If a parsing error occurs.
*/
private byte parseJSON(final ArrayList<TwitterNewsItem> items,
final String jsonString) throws JSONException {
// Make sure that the JSON String exists.
if(jsonString == null || jsonString.length() == 0) {
return ERROR_NODATA;
}
// Twitter starts off as a JSON Array.
final JSONArray ja = new JSONArray(jsonString);
// Get the number of items.
final int len = ja.length();
JSONObject joTweet, joUser;
for(int i = 0; i < len; i++) {
// Get the tweet object.
joTweet = ja.getJSONObject(i);
// Get the user object.
joUser = joTweet.getJSONObject("user");
// Create the TwitterNewsItem and add to the ArrayList.
items.add(new TwitterNewsItem(
Html.fromHtml(joTweet.getString("text")).toString(),
joUser.getString("name"),
joTweet.getString("created_at")));
}
return -1;
}
}