--- layout: post status: publish published: true title: Mendeley Android client; first preview and OAuth problem wordpress_id: 490 wordpress_url: http://www.martineve.com/?p=490 date: !binary |- MjAxMC0xMi0yMCAyMjowMDo0NyArMDEwMA== date_gmt: !binary |- MjAxMC0xMi0yMCAyMjowMDo0NyArMDEwMA== categories: - Technology - Android - Academia tags: - Android - Mendeley - mendroid comments: - id: 5986 author: Mr. Gunn author_email: william.gunn@mendeley.com author_url: '' date: !binary |- MjAxMC0xMi0yMCAyMjoxMDozMyArMDEwMA== date_gmt: !binary |- MjAxMC0xMi0yMCAyMjoxMDozMyArMDEwMA== content: Hi Martin! Glad to see you're taking a crack at this. You might want to check the mendeley mailing list on google groups for more help on this, but I've passed your message along as well - id: 5989 author: Jason Hoyt author_email: jason.hoyt@mendeley.com author_url: http://mendeley.com/about-us/ date: !binary |- MjAxMC0xMi0yMCAyMzoyNzoxNiArMDEwMA== date_gmt: !binary |- MjAxMC0xMi0yMCAyMzoyNzoxNiArMDEwMA== content: ! "Callbacks are supported with the \"jsonp=YourFunctionName\" argument at the end of any method.\r\n\r\nCheers!" - id: 5991 author: Martin Paul Eve author_email: martin@martineve.com author_url: '' date: !binary |- MjAxMC0xMi0yMSAwNjo1MjozMyArMDEwMA== date_gmt: !binary |- MjAxMC0xMi0yMSAwNjo1MjozMyArMDEwMA== content: ! "Thanks to you both; appreciate the feedback and will check the Google Groups. In the meantime, if the API team get a chance to look and respond while I'm away, I'll continue work when back after Xmas.\r\n\r\nCheers! (and happy holidays!)" - id: 6001 author: Ian Mulvany author_email: ian.mulvany@mendeley.com author_url: http://www.mendeley.com/profiles/ian-mulvany/ date: !binary |- MjAxMC0xMi0yMiAxNzowNzowNiArMDEwMA== date_gmt: !binary |- MjAxMC0xMi0yMiAxNzowNzowNiArMDEwMA== content: ! "There are plenty of people in the Mendeley office with andriod phones that would love to see this! \r\n\r\nBTW, I love Gravity's Rainbow, and am rather a fan of Beckett too, so it's really nice to see someone who is bridging the divide between the literary and digital." - id: 6002 author: Martin Paul Eve author_email: martin@martineve.com author_url: '' date: !binary |- MjAxMC0xMi0yMiAxNzozNzowNyArMDEwMA== date_gmt: !binary |- MjAxMC0xMi0yMiAxNzozNzowNyArMDEwMA== content: Thanks Ian - looking forward to working on this in the new year - any news from the API team? - id: 6050 author: Clemens author_email: clemens.lombriser@gmail.com author_url: '' date: !binary |- MjAxMS0wMS0wMiAyMTowMjo1MCArMDEwMA== date_gmt: !binary |- MjAxMS0wMS0wMiAyMTowMjo1MCArMDEwMA== content: ! "I also just started a small Android using the signpost library to log into Mendely. Currently it just reads out my collections and displays them into a ListActivity. The code is available here:\r\nhttps://code.google.com/p/droidbiblio" - id: 6051 author: Martin Paul Eve author_email: martin@martineve.com author_url: '' date: !binary |- MjAxMS0wMS0wMiAyMTowOTozNiArMDEwMA== date_gmt: !binary |- MjAxMS0wMS0wMiAyMTowOTozNiArMDEwMA== content: ! "Hi Clemens,\r\n\r\nMany thanks for this; sounds good -- I'll give Signpost a shot and report back!" ---

In the last day before I head off for a bit of a Christmas break, I decided to take up a recent proposition to start work on an Android client for Mendeley. So far, in a couple of hours, I've got as far as implementing the OAuth provider, so you can start the application, log in to Mendeley and it will ask you to authorize the application to access your Mendeley account.

Mendroid screenshot

Well, that's the theory... there's a problem, though. Currently the OAuth system will not work with the Android browser. I took the same URL and request token, accessing the auth URL in one instance with a desktop client and one instance with an android phone (tried on device -- HTC Wildfire -- and emulator). The desktop PC can get an access code, the Android device cannot.

Apologies for the quality of that YouTube video, but it does demonstrate the point. As an additional debug procedure, I also ran the working desktop URL to the phone via Chrome to Phone. Same result.

Is Mendeley's API doing something strange based on user agent? Also: does Mendeley's API support a callback URL? (this is kinda crucial for an Android app!)

To try and get some feedback, here's the code it's running for login at present. This code is GPL v3 licensed and I have removed my own keys.

{% highlight java %} /* mendroid: An Android Mendeley client Copyright 2010 Martin Paul Eve This file is part of Mendroid. Mendroid is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Mendroid is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Mendroid. If not, see . */ package com.martineve.mendroid; import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Map; import net.oauth.OAuth; import net.oauth.OAuthAccessor; import net.oauth.OAuthConsumer; import net.oauth.OAuthException; import net.oauth.OAuthMessage; import net.oauth.OAuthServiceProvider; import net.oauth.client.OAuthClient; import net.oauth.client.httpclient4.HttpClient4; import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.text.Html; import android.text.method.LinkMovementMethod; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class MendeleyDroidLogin extends Activity implements OnClickListener { OAuthAccessor client; String access_token; String request_token; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button login_button = (Button) findViewById(R.id.login_button); login_button.setOnClickListener(this); // setup the link in the credits box ;) TextView credits = (TextView) findViewById(R.id.login_page_credits); credits.setText(Html.fromHtml("Copyright Martin Paul Eve, 2011")); credits.setMovementMethod(LinkMovementMethod.getInstance()); } protected void onResume() { // extract the OAUTH access token if it exists Uri uri = this.getIntent().getData(); if(uri != null) { ProgressDialog dialog = ProgressDialog.show(this, "", "Logging in to Mendeley, please wait...", true); access_token = uri.getQueryParameter("oauth_token"); access_token = convertToAccessToken(client.requestToken); // setDefaultAccessToken(access_token, this); // keep this in the database dialog.cancel(); } super.onResume(); } public String convertToAccessToken(String request_token) { ArrayList> params = new ArrayList>(); OAuthClient oclient = new OAuthClient(new HttpClient4()); OAuthAccessor accessor = client; params.add(new OAuth.Parameter("oauth_token", request_token)); try { OAuthMessage omessage = oclient.invoke(accessor, "POST", accessor.consumer.serviceProvider.accessTokenURL, params); return omessage.getParameter("oauth_token"); } catch (Exception ioe) { return ""; } } public void onClick(View v) { // get the request token ProgressDialog dialog = ProgressDialog.show(this, "", "Communicating with Mendeley, please wait...", true); client = defaultClient(); dialog.cancel(); Intent i = new Intent(Intent.ACTION_VIEW); i.setData(Uri.parse(client.consumer.serviceProvider.userAuthorizationURL + "?oauth_token=" + client.requestToken + "&oauth_callback=" + client.consumer.callbackURL)); startActivity(i); } OAuthServiceProvider defaultProvider() { return new OAuthServiceProvider("http://www.mendeley.com/oauth/request_token/", "http://www.mendeley.com/oauth/authorize/", "http://www.mendeley.com/oauth/access_token/"); } OAuthAccessor defaultClient() { String callbackUrl = "martineve-mendroid:///"; OAuthServiceProvider provider = defaultProvider(); String consumerKey = "CONSUMER_KEY_HERE"; String consumerSecret = "CONSUMER_SECRET_HERE"; OAuthConsumer consumer = new OAuthConsumer(callbackUrl, consumerKey, consumerSecret, provider); OAuthAccessor accessor = new OAuthAccessor(consumer); OAuthClient oaclient = new OAuthClient(new HttpClient4()); try { oaclient.getRequestToken(accessor); request_token = accessor.requestToken; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (OAuthException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (URISyntaxException e) { // TODO Auto-generated catch block e.printStackTrace(); } return accessor; } } {% endhighlight %}

For now, I'm off on holiday, but if anyone had any thoughts, I'd love to hear them. Merry Christmas all!