Recently I was testing OAuth on two different Android emulators. One of them was running Android Jelly Bean and second Gingerbread.
I was testing obtaining token from the server with simple junit. Everything was working like a charm on Jelly Bean but Gingerbread was receiving from the server response 401 - Unauthorized.
Response from the server was meaningless without any additional description. Fortunately, I was able to debug the server and I have found out that timestamp verification failed. It turned out that OAuth Jersey library accepts requests with max age 300000 milis(5min). Moreover, Gingerbread emulator had wrong date, it was too old.
Setting date on emulator to current date - same as on the server - fixed the problem.
It's really time consuming issue if you don't know the reason, so my suggestion is to update your testing checklist before you jump into AOuth testing.
3city Developer's Blog
poniedziałek, 3 czerwca 2013
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.
Let's create the following method where HttpURLConnection is already initialized. Go through the code and read carefully my comments.
Your result can look for example like this:
Later you can use it with Signpost consumer like this:
That's it!
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.
SolutionLet'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]
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:
Please note that we use MODE_PRIVATE disallowing other 3rd party apps to access your application data.
To play audio we call the following:
On my devices with Android 4.0+ it works just fine but on Android 2.3.3 an error is thrown.
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.
Please note that we call
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.
Subskrybuj:
Posty
(
Atom
)