poniedziałek, 27 maja 2013

How to obtain XAuth access token using Signpost and HttpURLConnection in Android

Goal

Use Signpost library and Android HttpURLConnection to obtain access token which is defined by XAuth authorizations mechanism.
If you are interested with use of Android Apache library then you may be interested with this and skip
the rest of this post.

Preconditions

You should already understand XAuth workflow and Android networking. Knowing Signpost is not a must but a plus.
Server side is not in scope of this post. I assume it is already properly setup and responding.

Solution

Let's create the following method where HttpURLConnection is already initialized. Go through the code and read carefully my comments.
 
public String[] getToken(HttpURLConnection connection) throws IOException,
  OAuthMessageSignerException, OAuthExpectationFailedException,
  OAuthCommunicationException {
 // set request method
 connection.setRequestMethod("POST");
 /**
  * Indicate that this connection allow input and output. We want send
  * data and retrieve some.
  */
 connection.setDoInput(true);
 connection.setDoOutput(true);
 /**
  * Create consumer using class from Signpost. DefaultOAuthConsumer
  * implementation was designed for HttpURLConnection.
  */
 DefaultOAuthConsumer consumer = new DefaultOAuthConsumer(CONSUMER_KEY,
   CONSUMER_SECRET);
 /**
  * Consumer require POST body to be provided to properly sign request.
  * In case HttpURLConnection we can set data by writing it to connection
  * OutputSteam. When we do it data is automatically streamed to the
  * server. If we call sign() before data it won't be included in signing
  * and if we call sign() after request being streamed it will be to
  * late. To workaround this problem we can use consumer's additional
  * parameters, exactly the same as POST body.
  */
 HttpParameters additionalParameters = new HttpParameters();
 additionalParameters.put("x_auth_username", 
                        URLEncoder.encode(USER_KEY, HTTP.UTF_8));
 additionalParameters.put("x_auth_password", 
                        URLEncoder.encode(USER_PASS, HTTP.UTF_8));
 additionalParameters.put("x_auth_mode", "client_auth");
 consumer.setAdditionalParameters(additionalParameters);
 /**
  * Sign request. There is no magic in there. Singpost takes request
  * parameters, method name and URL. Then use to sign it and create
  * signature. As a result our request gets new header parameter
  * Authorization and the rest of the request is not modified.
  */
 consumer.sign(connection);
 /**
  * Now prepare real POST body and start streaming, request is already
  * properly signed with this values.
  */
 List<BasicNameValuePair> params = Arrays.asList(new BasicNameValuePair(
   "x_auth_username", USER_KEY), new BasicNameValuePair(
   "x_auth_password", USER_PASS), new BasicNameValuePair(
   "x_auth_mode", "client_auth"));
 /**
  * URLEncodedUtils is an utility class from Apache framework but we can
  * use it to keep things simple and avoid reinventing the wheel.
  */
 String postBody = URLEncodedUtils.format(params, HTTP.UTF_8);
 connection.setFixedLengthStreamingMode(postBody.getBytes().length);
 /**
  * IMPORTANT: It is crucial to set Content-Type after singing. Singpost
  * will look for it and when it's found it will try to get request body
  * which would be null. You can have a look at Signpost's
  * HttpURLConnectionRequestAdapter, method getMessagePayload() used for
  * obtaining body always returns null. Apache adapter on the other hand
  * implements this method.
  */
 connection.setRequestProperty("Content-Type",
   "application/x-www-form-urlencoded");
 PrintWriter out = new PrintWriter(connection.getOutputStream());
 out.print(postBody);
 out.close();
 int statusCode = connection.getResponseCode();
 if (statusCode != HttpURLConnection.HTTP_OK) {
  // handle unexpected status codes here
  return null;
 }
 // read response data
 InputStream in = connection.getInputStream();
        InputStreamReader reader = new InputStreamReader(in, HTTP.UTF_8);
 char[] buf = new char[4096];
 StringBuffer buffer = new StringBuffer();
 int i = 0;
 while ((i = reader.read(buf)) != -1) {
  buffer.append(buf, 0, i);
 }
 in.close();
 String responseBody = new String(buffer);
 // our response is an array of key value pairs
 return TextUtils.split(responseBody, "&");
}

Your result can look for example like this:
oauth_token=[generated token value on the server]
oauth_token_secret=[generated token secret on the server]

Later you can use it with Signpost consumer like this:
DefaultOAuthConsumer consumer = new DefaultOAuthConsumer(CONSUMER_KEY,
  CONSUMER_SECRET);
consumer.setTokenWithSecret(ACCESS_TOKEN, TOKEN_SECRET);
consumer.sign(connection);

That's it!

piątek, 17 maja 2013

Troubleshooting: Play audio from internal storage using Android MediaPlayer

On some Android devices (in my case Android 2.3.3) playing audio from internal storage cause an error. This happens due to the fact that MediaPlayer is treated as 3rd party application and has no access permission to your application internal storage.

As example the following code illustrate storing audio file:

FileOutputStream outStream = null;
String filename = null;
// some code
...
outStream = context.openFileOutput(filename, Context.MODE_PRIVATE);

Please note that we use MODE_PRIVATE disallowing other 3rd party apps to access your application data.

To play audio we call the following:

Context context = null;
String filename = null;
//some code
...
MediaPlayer mediaPlayer = new MediaPlayer();
File file = getContext().getFileStreamPath(filename);
Uri uri = Uri.fromFile(file);
mediaPlayer.setDataSource(context, uri);
mediaPlayer.prepare();

On my devices with Android 4.0+ it works just fine but on Android 2.3.3 an error is thrown.
In example above MediaPlayer is trying to access your application file space itself, which is not allowed because of MODE_PRIVATE.

To solve this problem we could easily set MODE_WORLD_READABLE but this mode is deprecated since API 17 and it is highly insecure. In that case not only MediaPlayer will gain access but also other applications.

A better and preferred solution is to provide the Media Player stream instead of the file path. As a result we omit attempt to access to our file from another application(MediaPlayer) and also enable audio to be played.

//some code
...
FileInputStream inputStream = getContext().openFileInput(mediaId);
mediaPlayer.setDataSource(inputStream.getFD());
inputStream.close();

Please note that we call close() method after setting data source.