Android & Twitter - OAuth Authentication Tutorial

I’ve seen a lot of questions about connecting Android apps with Twitter using OAuth and thought I would write up a walkthrough of how it can be done. The example will be done using Twitter4J which is a great library for connecting Java apps with Twitter and provides a simple interface to connect to the public Twitter Web Services.

First thing you will need to do is to register your new app (even though you haven’t made it yet!). Do this by heading to https://dev.twitter.com/apps/new (you will need an existing Twitter account sign up). You should complete the form with the relevant details for your app:


You should fill in your details for the application such as name, description, URL – but the only key things to remember to do here are as follows:

1) Make sure you select “Browser” as your application type – even though we are creating an app, we will authenticate using the browser and a callback URL.

2) Tick read & write permissions

Once you have registered you will be provided with a “Consumer Key” and a “Consumer Secret” – make note of these for later – we will need to include these in the app.


Now let’s get started with the app! I will assume for this tutorial that you are familiar with the basics of Android apps so won’t go into details about how to setup the UI and what an Activity or Application is.
The first thing we will do is create an Activity that will prompt the user to login to Twitter and authorize the app to connect to Twitter to make updates. Let’s start with the onCreate method:


@Override
public void onCreate(Bundle savedInstanceState) {
 System.setProperty("http.keepAlive", "false");
 super.onCreate(savedInstanceState);
 setContentView(R.layout.main_oauth);
 
 //check for saved log in details..
 checkForSavedLogin();

 //set consumer and provider on teh Application service
 getConsumerProvider();
 
 //Define login button and listener
 buttonLogin = (Button)findViewById(R.id.ButtonLogin);
 buttonLogin.setOnClickListener(new OnClickListener() {  
  public void onClick(View v) {
   askOAuth();
  }
 });
}

The first line we set the http keepAlive property – this is important so the app manages the connection correctly without reseting. The next important thing is our call to the checkForSavedLogin() method – this is a method that checks whether the user has previously authorised the app to access twitter, in which case we can just proceed as usual to the default twitter timeline activity (we will have a look at that method next). Otherwise we just initialise the Consumer/Provider and store them in our Application class so they are available to all Activities throughout the application. Then finally we inflate the button on the page and set a listener to call the askOAuth() method on click.


Now let’s jump in to the checkForSavedLogin() to see whats going on there:

private void checkForSavedLogin() {
 // Get Access Token and persist it
 AccessToken a = getAccessToken();
 if (a==null) return; //if there are no credentials stored then return to usual activity

 // initialize Twitter4J
 twitter = new TwitterFactory().getInstance();
 twitter.setOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
 twitter.setOAuthAccessToken(a);
 ((TwitterApplication)getApplication()).setTwitter(twitter);
 
 startFirstActivity();
 finish();
}



This method calls a method getAccessToken() – all this does is check in the user SharedPreferences to see whether we have stored the users AccessToken previously – if there is no token found it just returns, so the user is left on the authentication activity waiting input.

However, if a token is found, then we initialise Twitter4J Twitter object and store that in our Application class (again, so we can use it throughout the application).
Finally we call startFirstActivity(), which simply calls the default Twitter timeline activity we have and finishes the authentication activity as it is not needed.

So that is all good, if the user has previously authenticated then no probs – however, if no saved token is found the user still needs to authenticate so we need to handle the user action (remember we are calling the askOAuth() method from within our button listener).



Let’s have a look at that method – this is where the authentication is all done:

private void askOAuth() {
 try {
  consumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
  provider = new DefaultOAuthProvider("http://twitter.com/oauth/request_token", "http://twitter.com/oauth/access_token", "http://twitter.com/oauth/authorize");
  String authUrl = provider.retrieveRequestToken(consumer, CALLBACK_URL);
  Toast.makeText(this, "Please authorize this app!", Toast.LENGTH_LONG).show();
  setConsumerProvider();
  startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl)));
 } catch (Exception e) {
  Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
 }
}

First thing we do here is to initialise the Consumer and Provider objects – we are using the Commons OAuth Consumer/Provider (so you need to make sure you copy the following JARs in to your /assets/ directory and then add them to your build path: “signpost-commonshttp” and “signpost-core”).

You will see that the consumer is initialised using to static Strings CONSUMER_KEY and CONSUMER_SECRET – these need to be set to be the key and secret Twitter provided when you registered the app – you should make sure you keep these secret and not reveal them to people.

The provider is then just initialised using the relevant URLs that Twitter provides to authenticate againse.

Next, we call the retrieveRequestToken() method on the provider object, this takes two arguments: the consumer, and the CALLBACK_URL (another static string) we will come back to what this is later.

We then call startActivity with a new Intent using the authenticate URL – this is what will forward the user to the Twitter authentication service for them to securely login and authorize the app to make updates for them.



So now, we have a simple authentication activity, that if the user has never logged on before will forward them on to the Twitter OAuth service – next we need to handle the response from Twitter and if a successful response received, then store the Access Token for future use. This is all done in the onResume() method:

@Override
protected void onResume() {
 super.onResume();
 if (this.getIntent()!=null && this.getIntent().getData()!=null){
  Uri uri = this.getIntent().getData();
  if (uri != null && uri.toString().startsWith(CALLBACK_URL)) {
   String verifier = uri.getQueryParameter(oauth.signpost.OAuth.OAUTH_VERIFIER);
   try {
    // this will populate token and token_secret in consumer
    provider.retrieveAccessToken(consumer, verifier);

    // Get Access Token and persist it
    AccessToken a = new AccessToken(consumer.getToken(), consumer.getTokenSecret());
    storeAccessToken(a);

    // initialize Twitter4J
    twitter = new TwitterFactory().getInstance();
    twitter.setOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
    twitter.setOAuthAccessToken(a);
    ((TwitterApplication)getApplication()).setTwitter(twitter);
    //Log.e("Login", "Twitter Initialised");
    
    startFirstActivity();

   } catch (Exception e) {
    //Log.e(APP, e.getMessage());
    e.printStackTrace();
    Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
   }
  }
 }
}


First thing, the method checks that the intent that has called the onResume is the expected intent, and then pulls out the verifying string from the callback URI – it then uses this string to retrieve the access token. Once we have called the retrieveAccessToken() method, we can initialise an AccessToken and store it. Next we initialise the Twitter4J object with the CONSUMER_KEY and SECRET again and then set the Access Token – once this has all been setup we just start the first activity as normal.
Once the user has authenticated they will be forwarded to the their friends timeline!

Now, the only thing remaining, is that we need to tell the app that once Twitter has finished authenticating that it needs to return to our Authentication Activity to call the above onResume method, or else it won’t be able to continue. To do this, Twitter will attempt to use the CALLBACK_URL we saw earlier. If you remember, when we requested the token from Twitter we passed in the CALLBACK_URL. I have previously defined this as follows:

private String CALLBACK_URL =           "callback://tweeter";

The name of the callback is up to you (I have named it “tweeter”), what is important is that this matches the callback you define in your Manifest XML. For the welcome activity (our Authentication activity) it needs to be defined as follows:


 
  
  
 
 
  
  
  
  
 



As you can see, we have defined an intent-filter with a scheme called “callback” and we provide the host name as “tweeter” – matching that used to call the Twitter service.

And thats it! The user has been authenticated and returned to our application correctly, so it can now continue to read timelines, post tweets, retweet, etc.



The complete source code of the application is available here (NOW ON GITHUB!), please note that I have removed my own CONSUMER_KEY/SECRET details, so to make this work you will need to register your app and populate with your own details to see it work in your emulator/device.

71 comments:

  1. Nice tutorial and i did grab some concepts from it,thanks for it.But one question,I couldn't get to the source code of this tutorial.Every time I click on the source code ,I am redirected to blogger website or is it your scheme of do it by yourself.
    Anyway ,if you could provide the source,it would be grateful to lot of newbies like us.

    ReplyDelete
    Replies
    1. Nice tutorial and working fine everything

      Delete
  2. apologies! Not sure what happened there.. I have updated the link now. (I also keep links to all the tutorial source code in the right hand side menu)

    ReplyDelete
  3. hi robbo, thanks for the effort for doing this. I am charles a student and we were given a course work to build twitter app for android enabled mobile phone , i av toiled for 3 days without making a head way, i stumbled on ur blog post which i think might help me and i tried to download the source code and try using it by inserting my keys inn appropriate place and my call back url in manifest and authactivity but when i ran it on my emulator and try to click the button it told me that communication with the service provider failed. what could be the cause of this because i want to confirm that it's working before starting to follow the example. please i need ur help on this , kindly reply thanks

    ReplyDelete
  4. are you getting any info in the logcat? if you can post any warning/errors you get there then it might help debug it.

    One common issue is if your emulator isnt connecting to the web correctly - this will give you an error in the logcat along the lines of "unable to connect to host twitter.com...". Another strange issue I have intermittently experienced where the emulator just wont connect to the web and the emulator appears with no bars in the phone signal indicator (top right of the device screen) and a little cross by it.

    ReplyDelete
  5. the logcat is below. Hope it will say much about what's happening. Thanks alot


    06-21 13:44:33.232: ERROR/AndroidRuntime(277): ERROR: thread attach failed
    06-21 13:44:36.493: ERROR/AndroidRuntime(285): ERROR: thread attach failed
    06-21 13:44:54.482: ERROR/gralloc(58): [unregister] handle 0x328420 still locked (state=40000001)
    06-21 13:45:09.522: ERROR/gralloc(58): [unregister] handle 0x37b448 still locked (state=40000001)
    06-21 13:45:24.022: ERROR/gralloc(58): [unregister] handle 0x499610 still locked (state=40000001)
    06-21 13:47:21.722: ERROR/gralloc(58): [unregister] handle 0x493b30 still locked (state=40000001)

    ReplyDelete
  6. hmm, is there anything else logged? If you clear the logs once the app is loaded and then press the button and copy the logs what else do you see?

    ReplyDelete
  7. The type of error it was showing after pressing the button is :


    06-21 17:39:51.926: ERROR/gralloc(59): [unregister] handle 0x4a2f20 still locked (state=40000001)
    06-21 17:43:07.815: ERROR/gralloc(59): [unregister] handle 0x120c78 still locked (state=40000001)

    ReplyDelete
  8. hmm,can you paste the entire log so I can see the context of the error? Prob easiest to use http://pastebin.com/

    ReplyDelete
  9. 06-27 01:00:54.199: WARN/System.err(527): oauth.signpost.exception.OAuthNotAuthorizedException: Authorization failed (server replied with a 401). This can happen if the consumer key was not correct or the signatures did not match.

    Why i m getting this error. Though my consumer key and other twitter credential are ok? please assist

    ReplyDelete
  10. Hi, Me too facing the same problem...can u pin point the exact places for changing the CONSUMER KEY/SECRET ?

    ReplyDelete
  11. About the call_back Url during register, what should we enter? Sorry Im a newbie to web thing...any sites that I can enter for that one? Hope you can help me on this...

    ReplyDelete
  12. CONSUMER_KEY, CONSUMER_SECRET should be defined as class membe variables for the authentication class - in my source code you should see them declared as private static Strings at the top.

    The callback URL is not important whilst registering your twitter application - this is different to the callback we use within the application to resume the appropriate activity - so in the twitter registration page you can put any/none url you like.

    When you are having problems getting it working, are you just using my source code direct and updating the KEY/SERET or are you writing it from the guide?

    ReplyDelete
  13. Nice Android-Twitter tutorial.....

    ReplyDelete
  14. I imported this tutorial but I obteint: Communication with the service provider failed: received authentication challenge is null, or comes to know about this problem

    ReplyDelete
  15. hi robbo, thanks alot. i've figured out what the problem was, it was my emulator that was having a problem. when i tried running it on device , it ran and it's working fine ..good work. I have a question again , most third party application that i see don't have a an option for their users to sign out.why is it so, is there anything that i can do if i want to make my application to allow users to sign out or is it a deliberate attempt from twitter not to allow the third party application to have an option of signing out? another question is in the application that i'm building for my course work , i used twitter4j 2.1.1 snap shot, and another recent library is out , i think 2.2.4...what effect can this have on my application. Thanks.

    ReplyDelete
  16. Ya, I juz wanna try it so I act direct use ur source code, dont worry I wont copy ur code, but since I dont have any clue to getting start thats why I refering to ur source code. I juz change the KEY/SECRET only and thats happen, I using my phone to test it. Maybe is the KEY is wrong because I didnt use callback_url since I dont have any hosting ><, the default is client. Other than that, I really dont have any idea. I just starting to make android apps so many things I dont really understand. Hope you dont mind. =)

    ReplyDelete
  17. Is working now =D, my problem should be I didnt register the callback url in twitter thats y cant connect to server. Thx!

    Can I ask you something? have you tried connect using Xauth? I juz wanna understand the different between Oauth and Xauth in twitter. From wad I know nw is Oauth will skip the authorize using browser that part, but I wish to see some demo so I can know wads the real different.

    Anyway, Thx u so much!!

    ReplyDelete
  18. @androidtwitterclientproject - I have not seen the release note for the latest Twitter4J library, but I should think unless they have changed the core API it should not cause you any problems to upgrade. The IDE will instantly highlight any changes if there are any, so I would just drop it in to the project in replacement of the existing one and have a go.

    With regards the signout, this is not usually done just because normally a mobile device is normally just one user, so there is no need to signout, as that would mean re-authenticating through Twitter every time they used it. Even the official Twitter client does not offer easy signout.

    @Squall, I have not looked in to the XAuth so not very well versed in the detailed differences im afraid, but glad it could still be of help!

    ReplyDelete
  19. HI,

    I used the same code which u have given, but not able to authenticate.

    ERROR: In Toast "communication with service provider failed: host is unresolved : api, twitter.com:443"


    Guide me where I am getting struck.

    I inserted keys in the code also.

    ReplyDelete
  20. Sounds like a problem making a connection to the twitter servers. This is normally either

    1) an issue with your internet connectivity on your computer/debuggin device - see my comment from june 21st, but can you test if any other app on the device/emulator can connect to the internet?

    2) an issue with the twitter servers - this is less likely, but can be checked by just browsing to twitter.com

    ReplyDelete
  21. Thanks Great Android-Twitter tutorial i want to flow you on twitter/Facebook

    ReplyDelete
  22. @Sahir, glad it could be useful - I am on twitter but dont really use it that much (only follow others rather than tweet myself). I am on google+ as +rob hinds although dont really post tech stuff on there that much right now..

    ReplyDelete
  23. Hi, i'm trying to find the complete source code of this example, but i can't find it

    Please can you give me a link to download the fullsource code?

    I really need it.

    Thanks

    ReplyDelete
  24. Thanks for spotting that - Seems like blogger (that propably means me!) messed up the link and directed it to the blogger home page. I have now fixed the link to the GitHub repo.

    For reference, all source code links are also listed in the menu on the right of the blog.

    ReplyDelete
  25. Thanks a lot, great job!!!

    One more question, can your code be used on a commercial app without any restrictions or problems? (including your .png images)

    ReplyDelete
  26. You are free to use all the code as you please - with regards the images, i cant remember exactly where I got the speech bubble image from, so cant guarantee that one, that said, simple speech bubble images are easy to come by if you go to one of the free vector graphic sites:

    http://vector4free.com/vectors

    http://www.vecteezy.com/

    ReplyDelete
  27. Good day

    Is there a way to display the account and not the timeline, as we can see the account from twitter for example.
    Thanks

    ReplyDelete
  28. Hi i have run above code and and i use some keys from my application but it is showing me Communication with service provider is failed :http://twitter.com/oauth/request_token

    can you tell me why it is showing this

    ReplyDelete
  29. vikram, It happened to me like 500 times, and I searched everywhere. There might be 2 reasons for that to happen:
    1) Your Timezone is not set correctly

    2) You didn't enter a valid URL as Callback URL. (Even if your application is not gonna use that valid Callback URL, you need to enter one. I didn't enter anything and that error came up all the time).

    ReplyDelete
  30. I checked the twitter site, and it seems that they've changed all the urls to:

    Request token URL https://api.twitter.com/oauth/request_token
    Authorize URL https://api.twitter.com/oauth/authorize
    Access token URL https://api.twitter.com/oauth/access_token


    Maybe I'm reading into this wrong, but I changed those in my app and am still getting the communication failure. Any advice would be greatly appreciated.

    ReplyDelete
  31. The above poster fixed my issue, It was the need for a callback URL. Thanks (And sorry for the double post!)

    ReplyDelete
  32. communication witth the service provider failed:Recived authentication challenge is null y m getting this answer please reply soon

    ReplyDelete
  33. Thanx, but what callback url do you set on your developer setting on twitter

    ReplyDelete
  34. HI robbo ,

    i have downlaod your soucre code and run it working properly in emulator but device shows error that error message is "communication with the service provider failled:Received authentication challenge is null" what can i do? i hope you help me.

    ReplyDelete
    Replies
    1. Have you changed your consumer key and secret on Constants?

      Delete
  35. Nice information, I have an application structured with a tab view and wanted a twitter tab based on a public twitter account. Your examples helped me create the activity etc to make my tab work well. I have some additional functionality to add to the loading, but this was a great starting point, thanks - Ron

    ReplyDelete
  36. Hi..i have written similar code which works in emulator but in device,it shows error message -- "Authorization failed (server replied with a 401). This can happen if the consumer key was not correct or the signatures did not match."...

    can you give some suggestion...what is happening???....

    ReplyDelete
  37. Thanks for sharing :) . Learned a lot of stuff here.
    But there are few differences since Twitter changed a few things (Application type is not there anymore). Had to learn them the hard way.
    I have written a similar blog post with the current changes, you can find it here : http://blog.tarams.com/?p=212

    ReplyDelete
  38. Isabel Marant and her husband Jér? me Dreyfuss have made style their work, designing kkyyttssddfsafafda a cult clothing line (her) and up-and-coming accessories line.tory burch kendrick haircalf driver

    ReplyDelete
  39. VerifEyed is the world leading technology capable of determining whether digital images are original or modified (e.g., Photoshoped). VerifEyed divides the world of digital images into two groups: those having a genuineness verification (trustworthy) and the others.

    image authentication software
    image verificiation software
    image forensic software

    ReplyDelete
  40. This comment has been removed by the author.

    ReplyDelete
  41. isabel marant manly suede and leather knee boots Carrier developing styles coming from traditional are really using theirselves with creating far more remarkable shoes to perform the specific moyen wants regarding consumers.

    ReplyDelete
  42. sdfsdfsdfdsfsdfsdrrww
    A lot of people explained that they can chosen these kind of skinny jeans in Elle Macpherson. That they feel that your Isabel Marant skinny jeans go well with Drew’s fashion adequately plus the tie up absorb dyes moves adequately using your ex bohemian fashion, nevertheless your ex various other equipment create your clothing way too occupied, along with these kind of skinny jeans are generally excellent automatically along with needs to be the concentration in the clothing! Probably these are appropriate, isabel marant cleane boots nevertheless every single gentleman features the hobbyhorse, My spouse and i even now like these kind of Isabel Marant skinny jeans in Drew Barrumore along with your ex set is ideal many people feel.

    ReplyDelete
  43. Hi robbo,

    I have tried this code but iam getting force close when i click in the authenticate button. here is my logcat error.
    http://pastebin.com/AND0kigi
    Please help me in doing this.

    ReplyDelete
  44. Thanks For sharing this article.

    Hope It should be Helping for you.

    http://devappandroid.com/android-beginner/oauth-authentication-tutorial-android-twitter

    ReplyDelete
  45. Can anyone tell which jar,s should i import,i imported twitter4j,but some of the imports were not resolved
    import oauth.signpost.OAuthProvider;
    import oauth.signpost.basic.DefaultOAuthProvider;
    import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
    import twitter4j.http.AccessToken;
    plz help !!!

    ReplyDelete
    Replies
    1. I had the same problem. You can get them here (the latest jar files)

      http://code.google.com/p/oauth-signpost/downloads/list

      and link them on Eclipse (Project->Properties->java build path-> add external jars...)

      Good Luck :D

      Delete
  46. Twitters authentication and the codes which you given are great help to me, well thanks for everything.

    Android App Developer

    ReplyDelete
  47. Hi SRIKANTH. I got the same forced close. Try moving the jar files from assets to libs:

    Then fix your build path:
    properties -> java build path

    Worked fine after that. Cheers.

    ReplyDelete
  48. @Esteban Villalobos: Confirmed -> you need to add some valid "Callback URL" on the twitter page for the app or you'll get: "failed :http://twitter.com/oauth/request_token"

    ReplyDelete
  49. Amazing information on Android Apps Development. Thanks for sharing this information, keep posting....

    ReplyDelete
  50. i have dowload and import your code and also i made changes in keys
    but when i click on authenticate with twitter It show Unfortunatly tweeter has stopped message and exit from app
    can you please help me

    ReplyDelete
  51. i downloaded and import in eclipse and i changed keys but when i click on button exit from app
    any one can Help me

    ReplyDelete
  52. This comment has been removed by the author.

    ReplyDelete
  53. I cannot get the following import to work:

    import twitter4j.http.AccessToken

    I've inmported all the jars in the twitter4j-android-3.0.1 package, to no help.

    ReplyDelete
  54. The import twitter4j.http.AccessToken doesn't seem to exist anywhere...

    ReplyDelete
  55. Hi. Thanks for very grand tutorials!
    I have a big problem...
    in this line : consumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET); get a error
    this is a error http://prntscr.com/lgzec

    key and secret is 100% correct.

    Very thanks for your time......

    ReplyDelete
  56. This comment has been removed by the author.

    ReplyDelete
  57. this code working fine on emulator 2.1 but greater then not working
    or on real device 2.3 , 4.0 also not working any solution.

    ReplyDelete
  58. Sir a problem occur in getConsumerProvider method and a classCastException occur in aouth method

    ReplyDelete
  59. showing Force close for api 1.1, how to resolve this...

    ReplyDelete
  60. Dear my sweet hearted friend

    private void askOAuth() {
    try {
    consumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
    provider = new DefaultOAuthProvider("https://api.twitter.com/oauth/request_token", "https://api.twitter.com/oauth/access_token", "https://api.twitter.com/oauth/authorize");
    String authUrl = provider.retrieveRequestToken(consumer, CALLBACK_URL);
    Toast.makeText(this, "Please authorize this app!", Toast.LENGTH_LONG).show();
    setConsumerProvider();
    startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl)));
    } catch (Exception e) {
    Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
    }
    }


    this code errror with api V 1.1, please help me ?

    ReplyDelete
  61. Great blog! We offer affordable yet professional bespoke website App Development Leeds
    and software solutions to businesses in the Leeds area and throughout the UK.

    ReplyDelete
  62. Hi, i was unable to proceed because definition of these two methods is not found here:
    1. storeAccessToken()
    2. setConsumerProvider

    ReplyDelete
  63. I download and changed CONSUMER_KEY/SECRET.While run application gettin Exception like this on my logcat

    05-05 16:19:40.447: E/AndroidRuntime(10665): java.lang.NoClassDefFoundError: oauth.signpost.commonshttp.CommonsHttpOAuthConsumer
    05-05 16:19:40.447: E/AndroidRuntime(10665): at com.tmm.android.twitter.AuthActivity.askOAuth(AuthActivity.java:106)

    ReplyDelete
  64. Hi @robbo,
    Thank you for the nice tutorial and git code.
    I am able to authenticate my application to twitter, however I am not able to fetch tweets into the application.
    The error in my log is as follows:-

    05-24 20:58:44.588: E/Twitter(1374): Error retrieving tweets
    05-24 20:58:44.588: E/Twitter(1374): 401:Authentication credentials were missing or incorrect.
    05-24 20:58:44.588: E/Twitter(1374): {"errors":[{"message":"The Twitter REST API v1 is no longer active. Please migrate to API v1.1. https://dev.twitter.com/docs/api/1.1/overview.","code":64}]}

    Thus it seems to be issue that the code above refers to Twitter REST API v1 while we have to access v1.1. Can you help in solving this error? How can i fetch data from Twitter REST API v1.1.

    Thank you & Kind Regards,
    Manisha Luthra

    ReplyDelete