/*
* Copyright (C) 2010 yvolk (Yuri Volkov), http://yurivolkov.com
*
* 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.xorcode.andtweet.net;
import com.xorcode.andtweet.AndTweetService;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.json.JSONArray;
import org.json.JSONObject;
import android.content.SharedPreferences;
import android.net.Uri;
import android.util.Log;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
/**
* Handles connection to the Twitter REST API using OAuth
* Based on "BLOA" example, Copyright 2010 - Brion N. Emde
*
* @see <a
* href="http://github.com/brione/Brion-Learns-OAuth">Brion-Learns-OAuth</a>
*/
public class ConnectionOAuth extends Connection {
private static final String TAG = ConnectionOAuth.class.getSimpleName();
public static final String USER_TOKEN = "user_token";
public static final String USER_SECRET = "user_secret";
public static final String REQUEST_TOKEN = "request_token";
public static final String REQUEST_SECRET = "request_secret";
public static final String REQUEST_SUCCEEDED = "request_succeeded";
public static final String TWITTER_REQUEST_TOKEN_URL = "https://api.twitter.com/oauth/request_token";
public static final String TWITTER_ACCESS_TOKEN_URL = "https://api.twitter.com/oauth/access_token";
public static final String TWITTER_AUTHORIZE_URL = "https://api.twitter.com/oauth/authorize";
private OAuthConsumer mConsumer = null;
/**
* Saved User token
*/
private String mToken;
/**
* Saved User secret
*/
private String mSecret;
private HttpClient mClient;
public ConnectionOAuth(SharedPreferences sp) {
super(sp);
HttpParams parameters = new BasicHttpParams();
HttpProtocolParams.setVersion(parameters, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(parameters, HTTP.DEFAULT_CONTENT_CHARSET);
HttpProtocolParams.setUseExpectContinue(parameters, false);
HttpConnectionParams.setTcpNoDelay(parameters, true);
HttpConnectionParams.setSocketBufferSize(parameters, 8192);
SchemeRegistry schReg = new SchemeRegistry();
schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
ClientConnectionManager tsccm = new ThreadSafeClientConnManager(parameters, schReg);
mClient = new DefaultHttpClient(tsccm, parameters);
mConsumer = new CommonsHttpOAuthConsumer(OAuthKeys.TWITTER_CONSUMER_KEY,
OAuthKeys.TWITTER_CONSUMER_SECRET);
loadSavedKeys(sp);
}
private void loadSavedKeys(SharedPreferences sp) {
// We look for saved user keys
if (sp.contains(ConnectionOAuth.USER_TOKEN) && sp.contains(ConnectionOAuth.USER_SECRET)) {
mToken = sp.getString(ConnectionOAuth.USER_TOKEN, null);
mSecret = sp.getString(ConnectionOAuth.USER_SECRET, null);
if (!(mToken == null || mSecret == null)) {
mConsumer.setTokenWithSecret(mToken, mSecret);
}
}
}
/**
* @param token null means to clear the old values
* @param secret
*/
public void saveAuthInformation(SharedPreferences sp, String token, String secret) {
synchronized (sp) {
SharedPreferences.Editor editor = sp.edit();
if (token == null) {
editor.remove(ConnectionOAuth.USER_TOKEN);
Log.d(TAG, "Clearing OAuth Token");
} else {
editor.putString(ConnectionOAuth.USER_TOKEN, token);
Log.d(TAG, "Saving OAuth Token: " + token);
}
if (secret == null) {
editor.remove(ConnectionOAuth.USER_SECRET);
Log.d(TAG, "Clearing OAuth Secret");
} else {
editor.putString(ConnectionOAuth.USER_SECRET, secret);
Log.d(TAG, "Saving OAuth Secret: " + secret);
}
editor.commit();
// Keys changed so we have to reload them
loadSavedKeys(sp);
}
}
@Override
public void clearAuthInformation(SharedPreferences sp) {
saveAuthInformation(sp, null, null);
}
@Override
public JSONObject createFavorite(long statusId) throws ConnectionException {
StringBuilder url = new StringBuilder(FAVORITES_CREATE_BASE_URL);
url.append(String.valueOf(statusId));
url.append(EXTENSION);
HttpPost post = new HttpPost(url.toString());
return postRequest(post);
}
@Override
public JSONObject destroyFavorite(long statusId) throws ConnectionException {
StringBuilder url = new StringBuilder(FAVORITES_DESTROY_BASE_URL);
url.append(String.valueOf(statusId));
url.append(EXTENSION);
HttpPost post = new HttpPost(url.toString());
return postRequest(post);
}
@Override
public JSONObject destroyStatus(long statusId) throws ConnectionException {
StringBuilder url = new StringBuilder(STATUSES_DESTROY_URL);
url.append(String.valueOf(statusId));
url.append(EXTENSION);
HttpPost post = new HttpPost(url.toString());
return postRequest(post);
}
@Override
public JSONArray getDirectMessages(long sinceId, int limit) throws ConnectionException {
String url = DIRECT_MESSAGES_URL;
return getTimeline(url, sinceId, 0, limit, 0);
}
@Override
public JSONArray getFriendsTimeline(long sinceId, int limit) throws ConnectionException {
String url = STATUSES_FRIENDS_TIMELINE_URL;
return getTimeline(url, sinceId, 0, limit, 0);
}
private JSONObject getRequest(HttpGet get) throws ConnectionException {
JSONObject jso = null;
boolean ok = false;
try {
mConsumer.sign(get);
String response = mClient.execute(get, new BasicResponseHandler());
jso = new JSONObject(response);
// Log.d(TAG, "authenticatedQuery: " + jso.toString(2));
ok = true;
} catch (Exception e) {
e.printStackTrace();
throw new ConnectionException(e.getLocalizedMessage());
}
if (!ok) {
jso = null;
}
return jso;
}
private JSONObject getUrl(String url) throws ConnectionException {
HttpGet get = new HttpGet(url);
return getRequest(get);
}
/**
* Universal method for several Timelines...
*
* @param url URL predefined for this timeline
* @param sinceId
* @param maxId
* @param limit
* @param page
* @return
* @throws ConnectionException
*/
private JSONArray getTimeline(String url, long sinceId, long maxId, int limit, int page)
throws ConnectionException {
setSinceId(sinceId);
setLimit(limit);
boolean ok = false;
JSONArray jArr = null;
try {
Uri sUri = Uri.parse(url);
Uri.Builder builder = sUri.buildUpon();
if (getSinceId() != 0) {
builder.appendQueryParameter("since_id", String.valueOf(getSinceId()));
} else if (maxId != 0) { // these are mutually exclusive
builder.appendQueryParameter("max_id", String.valueOf(maxId));
}
if (getLimit() != 0) {
builder.appendQueryParameter("count", String.valueOf(getLimit()));
}
if (page != 0) {
builder.appendQueryParameter("page", String.valueOf(page));
}
HttpGet get = new HttpGet(builder.build().toString());
mConsumer.sign(get);
String response = mClient.execute(get, new BasicResponseHandler());
jArr = new JSONArray(response);
ok = (jArr != null);
} catch (NullPointerException e) {
// It looks like a bug in the library, but we have to catch it
Log.e(TAG, "NullPointerException was caught, URL='" + url + "'");
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
throw new ConnectionException(e.getLocalizedMessage());
}
if (Log.isLoggable(AndTweetService.APPTAG, Log.DEBUG)) {
Log.d(TAG, "getTimeline '" + url + "' "
+ (ok ? "OK, " + jArr.length() + " statuses" : "FAILED"));
}
return jArr;
}
@Override
public JSONArray getMentionsTimeline(long sinceId, int limit) throws ConnectionException {
String url = STATUSES_MENTIONS_TIMELINE_URL;
return getTimeline(url, sinceId, 0, limit, 0);
}
@Override
public JSONObject rateLimitStatus() throws ConnectionException {
return getUrl(ACCOUNT_RATE_LIMIT_STATUS_URL);
}
private JSONObject postRequest(HttpPost post) throws ConnectionException {
JSONObject jso = null;
boolean ok = false;
try {
// Maybe we'll need this:
// post.setParams(...);
// sign the request to authenticate
mConsumer.sign(post);
String response = mClient.execute(post, new BasicResponseHandler());
jso = new JSONObject(response);
ok = true;
} catch (Exception e) {
// We don't catch other exceptions because in fact it's vary difficult to tell
// what was a real cause of it. So let's make code clearer.
e.printStackTrace();
throw new ConnectionException(e.getLocalizedMessage());
}
if (!ok) {
jso = null;
}
return jso;
}
@Override
public JSONObject updateStatus(String message, long inReplyToId)
throws ConnectionException {
HttpPost post = new HttpPost(STATUSES_UPDATE_URL);
LinkedList<BasicNameValuePair> out = new LinkedList<BasicNameValuePair>();
out.add(new BasicNameValuePair("status", message));
if (inReplyToId > 0) {
out.add(new BasicNameValuePair("in_reply_to_status_id", String.valueOf(inReplyToId)));
}
try {
post.setEntity(new UrlEncodedFormEntity(out, HTTP.UTF_8));
} catch (UnsupportedEncodingException e) {
Log.e(TAG, e.toString());
}
return postRequest(post);
}
/**
* @see com.xorcode.andtweet.net.Connection#getCredentialsPresent()
*/
@Override
public boolean getCredentialsPresent(SharedPreferences sp) {
boolean yes = false;
if (sp.contains(ConnectionOAuth.USER_TOKEN) && sp.contains(ConnectionOAuth.USER_SECRET)) {
mToken = sp.getString(ConnectionOAuth.USER_TOKEN, null);
mSecret = sp.getString(ConnectionOAuth.USER_SECRET, null);
if (!(mToken == null || mSecret == null)) {
yes = true;
}
}
return yes;
}
@Override
public JSONObject verifyCredentials() throws ConnectionException {
return getUrl(ACCOUNT_VERIFY_CREDENTIALS_URL);
}
}