AI2 Component  (Version nb184)
Twitter.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright 2009-2011 Google, All Rights reserved
3 // Copyright 2011-2012 MIT, All rights reserved
4 // Released under the Apache License, Version 2.0
5 // http://www.apache.org/licenses/LICENSE-2.0
6 
7 package com.google.appinventor.components.runtime;
8 
9 import java.util.ArrayList;
10 import java.util.Collections;
11 import java.util.List;
12 import java.io.File;
13 
14 import twitter4j.DirectMessage;
15 import twitter4j.IDs;
16 import twitter4j.Query;
17 import twitter4j.Status;
18 import twitter4j.StatusUpdate;
19 import twitter4j.TwitterException;
20 import twitter4j.TwitterFactory;
21 import twitter4j.User;
22 import twitter4j.auth.AccessToken;
23 import twitter4j.auth.RequestToken;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.SharedPreferences;
27 import android.net.Uri;
28 import android.os.Handler;
29 import android.util.Log;
30 
49 
74 @DesignerComponent(version = YaVersion.TWITTER_COMPONENT_VERSION, description = "A non-visible component that enables communication "
75  + "with <a href=\"http://www.twitter.com\" target=\"_blank\">Twitter</a>. "
76  + "Once a user has logged into their Twitter account (and the authorization has been confirmed successful by the "
77  + "<code>IsAuthorized</code> event), many more operations are available:<ul>"
78  + "<li> Searching Twitter for tweets or labels (<code>SearchTwitter</code>)</li>\n"
79  + "<li> Sending a Tweet (<code>Tweet</code>)"
80  + " </li>\n"
81  + "<li> Sending a Tweet with an Image (<code>TweetWithImage</code>)"
82  + " </li>\n"
83  + "<li> Directing a message to a specific user "
84  + " (<code>DirectMessage</code>)</li>\n "
85  + "<li> Receiving the most recent messages directed to the logged-in user "
86  + " (<code>RequestDirectMessages</code>)</li>\n "
87  + "<li> Following a specific user (<code>Follow</code>)</li>\n"
88  + "<li> Ceasing to follow a specific user (<code>StopFollowing</code>)</li>\n"
89  + "<li> Getting a list of users following the logged-in user "
90  + " (<code>RequestFollowers</code>)</li>\n "
91  + "<li> Getting the most recent messages of users followed by the "
92  + " logged-in user (<code>RequestFriendTimeline</code>)</li>\n "
93  + "<li> Getting the most recent mentions of the logged-in user "
94  + " (<code>RequestMentions</code>)</li></ul></p>\n "
95  + "<p>You must obtain a Consumer Key and Consumer Secret for Twitter authorization "
96  + " specific to your app from http://twitter.com/oauth_clients/new",
97  category = ComponentCategory.SOCIAL, nonVisible = true, iconName = "images/twitter.png")
98 @SimpleObject
99 @UsesPermissions(permissionNames = "android.permission.INTERNET")
100 @UsesLibraries(libraries = "twitter4j.jar," + "twitter4jmedia.jar")
101 @UsesActivities(activities = {
102  @ActivityElement(name = "com.google.appinventor.components.runtime.WebViewActivity",
103  configChanges = "orientation|keyboardHidden",
104  screenOrientation = "behind",
105  intentFilters = {
106  @IntentFilterElement(actionElements = {
107  @ActionElement(name = "android.intent.action.MAIN")
108  })
109  })
110 })
111 public final class Twitter extends AndroidNonvisibleComponent implements
113  private static final String ACCESS_TOKEN_TAG = "TwitterOauthAccessToken";
114  private static final String ACCESS_SECRET_TAG = "TwitterOauthAccessSecret";
115  private static final String MAX_CHARACTERS = "160";
116  private static final String URL_HOST = "twitter";
117  private static final String CALLBACK_URL = Form.APPINVENTOR_URL_SCHEME
118  + "://" + URL_HOST;
119  private static final String WEBVIEW_ACTIVITY_CLASS = WebViewActivity.class
120  .getName();
121 
122  // the following fields should only be accessed from the UI thread
123  private String consumerKey = "";
124  private String consumerSecret = "";
125  private String TwitPic_API_Key = "";
126  private final List<String> mentions;
127  private final List<String> followers;
128  private final List<List<String>> timeline;
129  private final List<String> directMessages;
130  private final List<String> searchResults;
131 
132  // the following final fields are not synchronized -- twitter4j is thread
133  // safe as of 2.2.6
134  private twitter4j.Twitter twitter;
135  private RequestToken requestToken;
136  private AccessToken accessToken;
137  private String userName = "";
138  private final SharedPreferences sharedPreferences;
139  private final int requestCode;
140  private final ComponentContainer container;
141  private final Handler handler;
142 
143  // TODO(sharon): twitter4j apparently has an asynchronous interface
144  // (AsynchTwitter).
145  // We should consider whether it has any advantages over AsynchUtil.
146 
168  private static final String MAX_MENTIONS_RETURNED = "20";
169 
170  public Twitter(ComponentContainer container) {
171  super(container.$form());
172  this.container = container;
173  handler = new Handler();
174 
175  mentions = new ArrayList<String>();
176  followers = new ArrayList<String>();
177  timeline = new ArrayList<List<String>>();
178  directMessages = new ArrayList<String>();
179  searchResults = new ArrayList<String>();
180 
181  sharedPreferences = container.$context().getSharedPreferences("Twitter",
182  Context.MODE_PRIVATE);
183  accessToken = retrieveAccessToken();
184 
185  requestCode = form.registerForActivityResult(this);
186  }
187 
191  // @Deprecated
192  // [lyn, 2015/12/30] Removed @Deprecated annotation for this method, which was deprecated in AI1
193  // by setting userVisible = false. The @Deprecated annotation should only be used for
194  // events/methods/properties deprecated in AI2. The problem with using it for methods deprecated
195  // in AI1 is that the names of such methods no longer exist in OdeMessages.java, but the
196  // AI2 bad blocks mechanism (which uses the @Deprecated annotation) requires the method names
197  // to exist and be translatable so that they can appear in a block marked bad.
198  @SimpleFunction(userVisible = false, description = "Twitter's API no longer supports login via username and "
199  + "password. Use the Authorize call instead.")
200  public void Login(String username, String password) {
201  form.dispatchErrorOccurredEvent(this, "Login",
203  }
204 
205  @SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The user name of the authorized user. Empty if "
206  + "there is no authorized user.")
207  public String Username() {
208  return userName;
209  }
210 
215  public String ConsumerKey() {
216  return consumerKey;
217  }
218 
225  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "")
226  @SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The the consumer key to be used when authorizing with Twitter via OAuth.")
227  public void ConsumerKey(String consumerKey) {
228  this.consumerKey = consumerKey;
229  }
230 
235  public String ConsumerSecret() {
236  return consumerSecret;
237  }
238 
245  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "")
246  @SimpleProperty(description="The consumer secret to be used when authorizing with Twitter via OAuth")
247  public void ConsumerSecret(String consumerSecret) {
248  this.consumerSecret = consumerSecret;
249  }
250 
254  @Deprecated
255  @SimpleProperty( // [lyn 2015/12/30] removed userVisible = false, which is superseded by @Deprecated
256  category = PropertyCategory.BEHAVIOR)
257  public String TwitPic_API_Key() {
258  return TwitPic_API_Key;
259  }
260 
268  @Deprecated
269  // Hide the deprecated property from the Designer
270  //@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "")
271  @SimpleProperty( // [lyn 2015/12/30] removed userVisible = false, which is superseded by @Deprecated
272  category = PropertyCategory.BEHAVIOR,
273  description="The API Key for image uploading, provided by TwitPic.")
274  public void TwitPic_API_Key(String TwitPic_API_Key) {
275  this.TwitPic_API_Key = TwitPic_API_Key;
276  }
277 
284  @SimpleEvent(description = "This event is raised after the program calls "
285  + "<code>Authorize</code> if the authorization was successful. "
286  + "It is also called after a call to <code>CheckAuthorized</code> "
287  + "if we already have a valid access token. "
288  + "After this event has been raised, any other method for this "
289  + "component can be called.")
290  public void IsAuthorized() {
291  EventDispatcher.dispatchEvent(this, "IsAuthorized");
292  }
293 
298  @SimpleFunction(description = "Redirects user to login to Twitter via the Web browser using "
299  + "the OAuth protocol if we don't already have authorization.")
300  public void Authorize() {
301  if (consumerKey.length() == 0 || consumerSecret.length() == 0) {
302  form.dispatchErrorOccurredEvent(this, "Authorize",
304  return;
305  }
306  if (twitter == null) {
307  twitter = new TwitterFactory().getInstance();
308  }
309  final String myConsumerKey = consumerKey;
310  final String myConsumerSecret = consumerSecret;
311  AsynchUtil.runAsynchronously(new Runnable() {
312  public void run() {
313  if (checkAccessToken(myConsumerKey, myConsumerSecret)) {
314  handler.post(new Runnable() {
315  @Override
316  public void run() {
317  IsAuthorized();
318  }
319  });
320  return;
321  }
322  try {
323  // potentially time-consuming calls
324  RequestToken newRequestToken;
325  twitter.setOAuthConsumer(myConsumerKey, myConsumerSecret);
326  newRequestToken = twitter.getOAuthRequestToken(CALLBACK_URL);
327  String authURL = newRequestToken.getAuthorizationURL();
328  requestToken = newRequestToken; // request token will be
329  // needed to get access token
330  Intent browserIntent = new Intent(Intent.ACTION_MAIN, Uri
331  .parse(authURL));
332  browserIntent.setClassName(container.$context(),
333  WEBVIEW_ACTIVITY_CLASS);
334  container.$context().startActivityForResult(browserIntent,
335  requestCode);
336  } catch (TwitterException e) {
337  Log.i("Twitter", "Got exception: " + e.getMessage());
338  e.printStackTrace();
339  form.dispatchErrorOccurredEvent(Twitter.this, "Authorize",
340  ErrorMessages.ERROR_TWITTER_EXCEPTION, e.getMessage());
341  DeAuthorize(); // clean up
342  } catch (IllegalStateException ise){ //This should never happen cause it should return
343  // at the if (checkAccessToken...). We mark as an error but let continue
344  Log.e("Twitter", "OAuthConsumer was already set: launch IsAuthorized()");
345  handler.post(new Runnable() {
346  @Override
347  public void run() {
348  IsAuthorized();
349  }
350  });
351  }
352  }
353  });
354  }
355 
360  @SimpleFunction(description = "Checks whether we already have access, and if so, causes "
361  + "IsAuthorized event handler to be called.")
362  public void CheckAuthorized() {
363  final String myConsumerKey = consumerKey;
364  final String myConsumerSecret = consumerSecret;
365  AsynchUtil.runAsynchronously(new Runnable() {
366  public void run() {
367  if (checkAccessToken(myConsumerKey, myConsumerSecret)) {
368  handler.post(new Runnable() {
369  @Override
370  public void run() {
371  IsAuthorized();
372  }
373  });
374  }
375  }
376  });
377  }
378 
379  /*
380  * Get result from starting WebView activity to authorize access
381  */
382  @Override
383  public void resultReturned(int requestCode, int resultCode, Intent data) {
384  Log.i("Twitter", "Got result " + resultCode);
385  if (data != null) {
386  Uri uri = data.getData();
387  if (uri != null) {
388  Log.i("Twitter", "Intent URI: " + uri.toString());
389  final String oauthVerifier = uri.getQueryParameter("oauth_verifier");
390  if (twitter == null) {
391  Log.e("Twitter", "twitter field is unexpectedly null");
392  form.dispatchErrorOccurredEvent(this, "Authorize",
394  "internal error: can't access Twitter library");
395  new RuntimeException().printStackTrace();
396  }
397  if (requestToken != null && oauthVerifier != null
398  && oauthVerifier.length() != 0) {
399  AsynchUtil.runAsynchronously(new Runnable() {
400  public void run() {
401  try {
402  AccessToken resultAccessToken;
403  resultAccessToken = twitter.getOAuthAccessToken(requestToken,
404  oauthVerifier);
405  accessToken = resultAccessToken;
406  userName = accessToken.getScreenName();
407  saveAccessToken(resultAccessToken);
408  handler.post(new Runnable() {
409  @Override
410  public void run() {
411  IsAuthorized();
412  }
413  });
414  } catch (TwitterException e) {
415  Log.e("Twitter", "Got exception: " + e.getMessage());
416  e.printStackTrace();
417  form.dispatchErrorOccurredEvent(Twitter.this, "Authorize",
419  e.getMessage());
420  deAuthorize(); // clean up
421  }
422  }
423  });
424  } else {
425  form.dispatchErrorOccurredEvent(this, "Authorize",
427  deAuthorize(); // clean up
428  }
429  } else {
430  Log.e("Twitter", "uri returned from WebView activity was unexpectedly null");
431  deAuthorize(); // clean up so we can call Authorize again
432  }
433  } else {
434  Log.e("Twitter", "intent returned from WebView activity was unexpectedly null");
435  deAuthorize(); // clean up so we can call Authorize again
436  }
437  }
438 
439  private void saveAccessToken(AccessToken accessToken) {
440  final SharedPreferences.Editor sharedPrefsEditor = sharedPreferences.edit();
441  if (accessToken == null) {
442  sharedPrefsEditor.remove(ACCESS_TOKEN_TAG);
443  sharedPrefsEditor.remove(ACCESS_SECRET_TAG);
444  } else {
445  sharedPrefsEditor.putString(ACCESS_TOKEN_TAG, accessToken.getToken());
446  sharedPrefsEditor.putString(ACCESS_SECRET_TAG,
447  accessToken.getTokenSecret());
448  }
449  sharedPrefsEditor.commit();
450  }
451 
452  private AccessToken retrieveAccessToken() {
453  String token = sharedPreferences.getString(ACCESS_TOKEN_TAG, "");
454  String secret = sharedPreferences.getString(ACCESS_SECRET_TAG, "");
455  if (token.length() == 0 || secret.length() == 0) {
456  return null;
457  }
458  return new AccessToken(token, secret);
459  }
460 
464  @SimpleFunction(description = "Removes Twitter authorization from this running app instance")
465  public void DeAuthorize() {
466  deAuthorize();
467  }
468 
469  private void deAuthorize() {
470  final twitter4j.Twitter oldTwitter;
471  requestToken = null;
472  accessToken = null;
473  userName = "";
474  oldTwitter = twitter;
475  twitter = null; // setting twitter to null gives us a quick check
476  // that we don't have an authorized version around.
477  saveAccessToken(accessToken);
478 
479  // clear the access token from the old twitter instance, just in case
480  // someone stashed it away.
481  if (oldTwitter != null) {
482  oldTwitter.setOAuthAccessToken(null);
483  }
484  }
485 
493  @SimpleFunction(description = "This sends a tweet as the logged-in user with the "
494  + "specified Text, which will be trimmed if it exceeds "
495  + MAX_CHARACTERS
496  + " characters. "
497  + "<p><u>Requirements</u>: This should only be called after the "
498  + "<code>IsAuthorized</code> event has been raised, indicating that the "
499  + "user has successfully logged in to Twitter.</p>")
500  public void Tweet(final String status) {
501 
502  if (twitter == null || userName.length() == 0) {
503  form.dispatchErrorOccurredEvent(this, "Tweet",
505  return;
506  }
507  // TODO(sharon): note that if the user calls DeAuthorize immediately
508  // after
509  // Tweet it is possible that the DeAuthorize call can slip in
510  // and invalidate the authorization credentials for myTwitter, causing
511  // the call below to fail. If we want to prevent this we could consider
512  // using an ExecutorService object to serialize calls to Twitter.
513  AsynchUtil.runAsynchronously(new Runnable() {
514  public void run() {
515  try {
516  twitter.updateStatus(status);
517  } catch (TwitterException e) {
518  form.dispatchErrorOccurredEvent(Twitter.this, "Tweet",
520  }
521  }
522  });
523  }
524 
533  @SimpleFunction(description = "This sends a tweet as the logged-in user with the "
534  + "specified Text and a path to the image to be uploaded, which will be trimmed if it "
535  + "exceeds " + MAX_CHARACTERS + " characters. "
536  + "If an image is not found or invalid, only the text will be tweeted."
537  + "<p><u>Requirements</u>: This should only be called after the "
538  + "<code>IsAuthorized</code> event has been raised, indicating that the "
539  + "user has successfully logged in to Twitter.</p>" )
540  public void TweetWithImage(final String status, final String imagePath) {
541  if (twitter == null || userName.length() == 0) {
542  form.dispatchErrorOccurredEvent(this, "TweetWithImage",
544  return;
545  }
546 
547  AsynchUtil.runAsynchronously(new Runnable() {
548  public void run() {
549  try {
550  String cleanImagePath = imagePath;
551  // Clean up the file path if necessary
552  if (cleanImagePath.startsWith("file://")) {
553  cleanImagePath = imagePath.replace("file://", "");
554  }
555  File imageFilePath = new File(cleanImagePath);
556  if (imageFilePath.exists()) {
557  StatusUpdate theTweet = new StatusUpdate(status);
558  theTweet.setMedia(imageFilePath);
559  twitter.updateStatus(theTweet);
560  }
561  else {
562  form.dispatchErrorOccurredEvent(Twitter.this, "TweetWithImage",
564  }
565  } catch (TwitterException e) {
566  form.dispatchErrorOccurredEvent(Twitter.this, "TweetWithImage",
568  }
569  }
570  });
571 
572  }
573 
582  @SimpleFunction(description = "Requests the " + MAX_MENTIONS_RETURNED
583  + " most "
584  + "recent mentions of the logged-in user. When the mentions have been "
585  + "retrieved, the system will raise the <code>MentionsReceived</code> "
586  + "event and set the <code>Mentions</code> property to the list of "
587  + "mentions."
588  + "<p><u>Requirements</u>: This should only be called after the "
589  + "<code>IsAuthorized</code> event has been raised, indicating that the "
590  + "user has successfully logged in to Twitter.</p>")
591  public void RequestMentions() {
592  if (twitter == null || userName.length() == 0) {
593  form.dispatchErrorOccurredEvent(this, "RequestMentions",
595  return;
596  }
597  AsynchUtil.runAsynchronously(new Runnable() {
598  List<Status> replies = Collections.emptyList();
599 
600  public void run() {
601  try {
602  replies = twitter.getMentionsTimeline();
603  } catch (TwitterException e) {
604  form.dispatchErrorOccurredEvent(Twitter.this, "RequestMentions",
606  e.getMessage());
607  } finally {
608  handler.post(new Runnable() {
609  public void run() {
610  mentions.clear();
611  for (Status status : replies) {
612  mentions.add(status.getUser().getScreenName() + " "
613  + status.getText());
614  }
615  MentionsReceived(mentions);
616  }
617  });
618  }
619  }
620  });
621  }
622 
628  @SimpleEvent(description = "This event is raised when the mentions of the logged-in user "
629  + "requested through <code>RequestMentions</code> have been retrieved. "
630  + "A list of the mentions can then be found in the <code>mentions</code> "
631  + "parameter or the <code>Mentions</code> property.")
632  public void MentionsReceived(final List<String> mentions) {
633  EventDispatcher.dispatchEvent(this, "MentionsReceived", mentions);
634  }
635 
648  @SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "This property contains a list of mentions of the "
649  + "logged-in user. Initially, the list is empty. To set it, the "
650  + "program must: <ol> "
651  + "<li> Call the <code>Authorize</code> method.</li> "
652  + "<li> Wait for the <code>IsAuthorized</code> event.</li> "
653  + "<li> Call the <code>RequestMentions</code> method.</li> "
654  + "<li> Wait for the <code>MentionsReceived</code> event.</li></ol>\n"
655  + "The value of this property will then be set to the list of mentions "
656  + "(and will maintain its value until any subsequent calls to "
657  + "<code>RequestMentions</code>).")
658  public List<String> Mentions() {
659  return mentions;
660  }
661 
666  public void RequestFollowers() {
667  if (twitter == null || userName.length() == 0) {
668  form.dispatchErrorOccurredEvent(this, "RequestFollowers",
670  "Need to login?");
671  return;
672  }
673  AsynchUtil.runAsynchronously(new Runnable() {
674  List<User> friends = new ArrayList<User>();
675 
676  public void run() {
677  try {
678  IDs followerIDs = twitter.getFollowersIDs(-1);
679  for (long id : followerIDs.getIDs()) {
680  // convert from the IDs returned to the User
681  friends.add(twitter.showUser(id));
682  }
683  } catch (TwitterException e) {
684  form.dispatchErrorOccurredEvent(Twitter.this, "RequestFollowers",
686  e.getMessage());
687  } finally {
688  handler.post(new Runnable() {
689  public void run() {
690  followers.clear();
691  for (User user : friends) {
692  followers.add(user.getName());
693  }
694  FollowersReceived(followers);
695  }
696  });
697  }
698  }
699  });
700  }
701 
707  @SimpleEvent(description = "This event is raised when all of the followers of the "
708  + "logged-in user requested through <code>RequestFollowers</code> have "
709  + "been retrieved. A list of the followers can then be found in the "
710  + "<code>followers</code> parameter or the <code>Followers</code> "
711  + "property.")
712  public void FollowersReceived(final List<String> followers2) {
713  EventDispatcher.dispatchEvent(this, "FollowersReceived", followers2);
714  }
715 
728  @SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "This property contains a list of the followers of the "
729  + "logged-in user. Initially, the list is empty. To set it, the "
730  + "program must: <ol> "
731  + "<li> Call the <code>Authorize</code> method.</li> "
732  + "<li> Wait for the <code>IsAuthorized</code> event.</li> "
733  + "<li> Call the <code>RequestFollowers</code> method.</li> "
734  + "<li> Wait for the <code>FollowersReceived</code> event.</li></ol>\n"
735  + "The value of this property will then be set to the list of "
736  + "followers (and maintain its value until any subsequent call to "
737  + "<code>RequestFollowers</code>).")
738  public List<String> Followers() {
739  return followers;
740  }
741 
750  @SimpleFunction(description = "Requests the " + MAX_MENTIONS_RETURNED
751  + " most "
752  + "recent direct messages sent to the logged-in user. When the "
753  + "messages have been retrieved, the system will raise the "
754  + "<code>DirectMessagesReceived</code> event and set the "
755  + "<code>DirectMessages</code> property to the list of messages."
756  + "<p><u>Requirements</u>: This should only be called after the "
757  + "<code>IsAuthorized</code> event has been raised, indicating that the "
758  + "user has successfully logged in to Twitter.</p>")
759  public void RequestDirectMessages() {
760  if (twitter == null || userName.length() == 0) {
761  form.dispatchErrorOccurredEvent(this, "RequestDirectMessages",
763  "Need to login?");
764  return;
765  }
766  AsynchUtil.runAsynchronously(new Runnable() {
767  List<DirectMessage> messages = Collections.emptyList();
768 
769  @Override
770  public void run() {
771  try {
772  messages = twitter.getDirectMessages();
773  } catch (TwitterException e) {
774  form.dispatchErrorOccurredEvent(Twitter.this,
775  "RequestDirectMessages",
777  e.getMessage());
778  } finally {
779  handler.post(new Runnable() {
780  @Override
781  public void run() {
782  directMessages.clear();
783  for (DirectMessage message : messages) {
784  directMessages.add(message.getSenderScreenName() + " "
785  + message.getText());
786  }
787  DirectMessagesReceived(directMessages);
788  }
789  });
790  }
791  }
792 
793  });
794  }
795 
801  @SimpleEvent(description = "This event is raised when the recent messages "
802  + "requested through <code>RequestDirectMessages</code> have "
803  + "been retrieved. A list of the messages can then be found in the "
804  + "<code>messages</code> parameter or the <code>Messages</code> "
805  + "property.")
806  public void DirectMessagesReceived(final List<String> messages) {
807  EventDispatcher.dispatchEvent(this, "DirectMessagesReceived", messages);
808  }
809 
822  @SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "This property contains a list of the most recent "
823  + "messages mentioning the logged-in user. Initially, the list is "
824  + "empty. To set it, the program must: <ol> "
825  + "<li> Call the <code>Authorize</code> method.</li> "
826  + "<li> Wait for the <code>Authorized</code> event.</li> "
827  + "<li> Call the <code>RequestDirectMessages</code> method.</li> "
828  + "<li> Wait for the <code>DirectMessagesReceived</code> event.</li>"
829  + "</ol>\n"
830  + "The value of this property will then be set to the list of direct "
831  + "messages retrieved (and maintain that value until any subsequent "
832  + "call to <code>RequestDirectMessages</code>).")
833  public List<String> DirectMessages() {
834  return directMessages;
835  }
836 
844  @SimpleFunction(description = "This sends a direct (private) message to the specified "
845  + "user. The message will be trimmed if it exceeds "
846  + MAX_CHARACTERS
847  + "characters. "
848  + "<p><u>Requirements</u>: This should only be called after the "
849  + "<code>IsAuthorized</code> event has been raised, indicating that the "
850  + "user has successfully logged in to Twitter.</p>")
851  public void DirectMessage(final String user, final String message) {
852  if (twitter == null || userName.length() == 0) {
853  form.dispatchErrorOccurredEvent(this, "DirectMessage",
855  return;
856  }
857  AsynchUtil.runAsynchronously(new Runnable() {
858  public void run() {
859  try {
860  twitter.sendDirectMessage(user, message);
861  } catch (TwitterException e) {
862  form.dispatchErrorOccurredEvent(Twitter.this, "DirectMessage",
864  }
865  }
866  });
867  }
868 
873  public void Follow(final String user) {
874  if (twitter == null || userName.length() == 0) {
875  form.dispatchErrorOccurredEvent(this, "Follow",
876  ErrorMessages.ERROR_TWITTER_FOLLOW_FAILED, "Need to login?");
877  return;
878  }
879  AsynchUtil.runAsynchronously(new Runnable() {
880  public void run() {
881  try {
882  twitter.createFriendship(user);
883  } catch (TwitterException e) {
884  form.dispatchErrorOccurredEvent(Twitter.this, "Follow",
886  }
887  }
888  });
889  }
890 
895  public void StopFollowing(final String user) {
896  if (twitter == null || userName.length() == 0) {
897  form.dispatchErrorOccurredEvent(this, "StopFollowing",
899  return;
900  }
901  AsynchUtil.runAsynchronously(new Runnable() {
902  public void run() {
903  try {
904  twitter.destroyFriendship(user);
905  } catch (TwitterException e) {
906  form.dispatchErrorOccurredEvent(Twitter.this, "StopFollowing",
908  }
909  }
910  });
911  }
912 
917  public void RequestFriendTimeline() {
918  if (twitter == null || userName.length() == 0) {
919  form.dispatchErrorOccurredEvent(this, "RequestFriendTimeline",
921  "Need to login?");
922  return;
923  }
924  AsynchUtil.runAsynchronously(new Runnable() {
925  List<Status> messages = Collections.emptyList();
926 
927  public void run() {
928  try {
929  messages = twitter.getHomeTimeline();
930  } catch (TwitterException e) {
931  form.dispatchErrorOccurredEvent(Twitter.this,
932  "RequestFriendTimeline",
934  e.getMessage());
935  } finally {
936  handler.post(new Runnable() {
937  public void run() {
938  timeline.clear();
939  for (Status message : messages) {
940  List<String> status = new ArrayList<String>();
941  status.add(message.getUser().getScreenName());
942  status.add(message.getText());
943  timeline.add(status);
944  }
945  FriendTimelineReceived(timeline);
946  }
947  });
948  }
949  }
950  });
951  }
952 
959  @SimpleEvent(description = "This event is raised when the messages "
960  + "requested through <code>RequestFriendTimeline</code> have "
961  + "been retrieved. The <code>timeline</code> parameter and the "
962  + "<code>Timeline</code> property will contain a list of lists, where "
963  + "each sub-list contains a status update of the form (username message)")
964  public void FriendTimelineReceived(final List<List<String>> timeline) {
965  EventDispatcher.dispatchEvent(this, "FriendTimelineReceived", timeline);
966  }
967 
981  @SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "This property contains the 20 most recent messages of "
982  + "users being followed. Initially, the list is empty. To set it, "
983  + "the program must: <ol> "
984  + "<li> Call the <code>Authorize</code> method.</li> "
985  + "<li> Wait for the <code>IsAuthorized</code> event.</li> "
986  + "<li> Specify users to follow with one or more calls to the "
987  + "<code>Follow</code> method.</li> "
988  + "<li> Call the <code>RequestFriendTimeline</code> method.</li> "
989  + "<li> Wait for the <code>FriendTimelineReceived</code> event.</li> "
990  + "</ol>\n"
991  + "The value of this property will then be set to the list of messages "
992  + "(and maintain its value until any subsequent call to "
993  + "<code>RequestFriendTimeline</code>.")
994  public List<List<String>> FriendTimeline() {
995  return timeline;
996  }
997 
1004  @SimpleFunction(description = "This searches Twitter for the given String query."
1005  + "<p><u>Requirements</u>: This should only be called after the "
1006  + "<code>IsAuthorized</code> event has been raised, indicating that the "
1007  + "user has successfully logged in to Twitter.</p>")
1008  public void SearchTwitter(final String query) {
1009  if (twitter == null || userName.length() == 0) {
1010  form.dispatchErrorOccurredEvent(this, "SearchTwitter",
1011  ErrorMessages.ERROR_TWITTER_SEARCH_FAILED, "Need to login?");
1012  return;
1013  }
1014  AsynchUtil.runAsynchronously(new Runnable() {
1015  List<Status> tweets = Collections.emptyList();
1016 
1017  public void run() {
1018  try {
1019  tweets = twitter.search(new Query(query)).getTweets();
1020  } catch (TwitterException e) {
1021  form.dispatchErrorOccurredEvent(Twitter.this, "SearchTwitter",
1022  ErrorMessages.ERROR_TWITTER_SEARCH_FAILED, e.getMessage());
1023  } finally {
1024  handler.post(new Runnable() {
1025  public void run() {
1026  searchResults.clear();
1027  for (Status tweet : tweets) {
1028  searchResults.add(tweet.getUser().getName() + " " + tweet.getText());
1029  }
1030  SearchSuccessful(searchResults);
1031  }
1032  });
1033  }
1034  }
1035  });
1036  }
1037 
1043  @SimpleEvent(description = "This event is raised when the results of the search "
1044  + "requested through <code>SearchSuccessful</code> have "
1045  + "been retrieved. A list of the results can then be found in the "
1046  + "<code>results</code> parameter or the <code>Results</code> "
1047  + "property.")
1048  public void SearchSuccessful(final List<String> searchResults) {
1049  EventDispatcher.dispatchEvent(this, "SearchSuccessful", searchResults);
1050  }
1051 
1062  @SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "This property, which is initially empty, is set to a "
1063  + "list of search results after the program: <ol>"
1064  + "<li>Calls the <code>SearchTwitter</code> method.</li> "
1065  + "<li>Waits for the <code>SearchSuccessful</code> event.</li></ol>\n"
1066  + "The value of the property will then be the same as the parameter to "
1067  + "<code>SearchSuccessful</code>. Note that it is not necessary to "
1068  + "call the <code>Authorize</code> method before calling "
1069  + "<code>SearchTwitter</code>.")
1070  public List<String> SearchResults() {
1071  return searchResults;
1072  }
1073 
1080  private boolean checkAccessToken(String myConsumerKey, String myConsumerSecret) {
1081  accessToken = retrieveAccessToken();
1082  if (accessToken == null) {
1083  return false;
1084  }
1085  else {
1086  if (twitter == null) {
1087  twitter = new TwitterFactory().getInstance();
1088  }
1089  try {
1090  twitter.setOAuthConsumer(consumerKey, consumerSecret);
1091  twitter.setOAuthAccessToken(accessToken);
1092  }
1093  catch (IllegalStateException ies) {
1094  //ignore: it means that the consumer data was already set
1095  }
1096  if (userName.trim().length() == 0) {
1097  User user;
1098  try {
1099  user = twitter.verifyCredentials();
1100  userName = user.getScreenName();
1101  } catch (TwitterException e) {// something went wrong (networks or bad credentials <-- DeAuthorize
1102  deAuthorize();
1103  return false;
1104  }
1105  }
1106  return true;
1107  }
1108  }
1109 }
com.google.appinventor.components.runtime.EventDispatcher
Definition: EventDispatcher.java:22
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_SET_STATUS_FAILED
static final int ERROR_TWITTER_SET_STATUS_FAILED
Definition: ErrorMessages.java:39
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_UNSUPPORTED_LOGIN_FUNCTION
static final int ERROR_TWITTER_UNSUPPORTED_LOGIN_FUNCTION
Definition: ErrorMessages.java:34
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_FOLLOW_FAILED
static final int ERROR_TWITTER_FOLLOW_FAILED
Definition: ErrorMessages.java:44
com.google.appinventor.components.runtime.WebViewActivity
Definition: WebViewActivity.java:31
com.google.appinventor.components.annotations.SimpleFunction
Definition: SimpleFunction.java:23
com.google.appinventor.components.annotations.UsesLibraries
Definition: UsesLibraries.java:21
com.google.appinventor.components.runtime.util.ErrorMessages
Definition: ErrorMessages.java:17
com.google.appinventor.components.runtime.util
-*- mode: java; c-basic-offset: 2; -*-
Definition: AccountChooser.java:7
com.google.appinventor.components.runtime.Twitter.RequestFollowers
void RequestFollowers()
Definition: Twitter.java:666
com.google.appinventor.components.common.YaVersion
Definition: YaVersion.java:14
com.google.appinventor.components.annotations.DesignerProperty
Definition: DesignerProperty.java:25
com.google.appinventor.components.common.PropertyTypeConstants.PROPERTY_TYPE_STRING
static final String PROPERTY_TYPE_STRING
Definition: PropertyTypeConstants.java:237
com.google.appinventor.components
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_BLANK_CONSUMER_KEY_OR_SECRET
static final int ERROR_TWITTER_BLANK_CONSUMER_KEY_OR_SECRET
Definition: ErrorMessages.java:35
com.google.appinventor.components.runtime.Form.APPINVENTOR_URL_SCHEME
static final String APPINVENTOR_URL_SCHEME
Definition: Form.java:136
com.google.appinventor.components.annotations.androidmanifest.ActionElement
Definition: ActionElement.java:31
com.google.appinventor.components.annotations.DesignerComponent
Definition: DesignerComponent.java:22
com.google.appinventor.components.annotations.SimpleEvent
Definition: SimpleEvent.java:20
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_AUTHORIZATION_FAILED
static final int ERROR_TWITTER_AUTHORIZATION_FAILED
Definition: ErrorMessages.java:38
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_DIRECT_MESSAGE_FAILED
static final int ERROR_TWITTER_DIRECT_MESSAGE_FAILED
Definition: ErrorMessages.java:43
com.google.appinventor.components.annotations.PropertyCategory.BEHAVIOR
BEHAVIOR
Definition: PropertyCategory.java:15
com.google.appinventor.components.runtime.Twitter
Definition: Twitter.java:111
com.google.appinventor.components.runtime.Twitter.Follow
void Follow(final String user)
Definition: Twitter.java:873
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_UNABLE_TO_GET_ACCESS_TOKEN
static final int ERROR_TWITTER_UNABLE_TO_GET_ACCESS_TOKEN
Definition: ErrorMessages.java:37
com.google.appinventor.components.runtime.File
Definition: File.java:53
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_EXCEPTION
static final int ERROR_TWITTER_EXCEPTION
Definition: ErrorMessages.java:36
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_SEARCH_FAILED
static final int ERROR_TWITTER_SEARCH_FAILED
Definition: ErrorMessages.java:47
com.google.appinventor.components.annotations.androidmanifest.IntentFilterElement
Definition: IntentFilterElement.java:30
com.google.appinventor.components.annotations.UsesPermissions
Definition: UsesPermissions.java:21
com.google.appinventor.components.runtime.Twitter.RequestFriendTimeline
void RequestFriendTimeline()
Definition: Twitter.java:917
com.google.appinventor.components.annotations.androidmanifest
Definition: ActionElement.java:6
com.google.appinventor.components.runtime.Twitter.Twitter
Twitter(ComponentContainer container)
Definition: Twitter.java:170
com.google.appinventor.components.runtime.EventDispatcher.dispatchEvent
static boolean dispatchEvent(Component component, String eventName, Object...args)
Definition: EventDispatcher.java:188
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_STOP_FOLLOWING_FAILED
static final int ERROR_TWITTER_STOP_FOLLOWING_FAILED
Definition: ErrorMessages.java:45
com.google.appinventor.components.runtime.AndroidNonvisibleComponent
Definition: AndroidNonvisibleComponent.java:17
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_REQUEST_FOLLOWERS_FAILED
static final int ERROR_TWITTER_REQUEST_FOLLOWERS_FAILED
Definition: ErrorMessages.java:41
com.google.appinventor.components.annotations.SimpleProperty
Definition: SimpleProperty.java:23
com.google.appinventor.components.runtime.util.AsynchUtil.runAsynchronously
static void runAsynchronously(final Runnable call)
Definition: AsynchUtil.java:23
com.google.appinventor.components.annotations.PropertyCategory
Definition: PropertyCategory.java:13
com.google.appinventor.components.runtime.ComponentContainer
Definition: ComponentContainer.java:16
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.runtime.Twitter.StopFollowing
void StopFollowing(final String user)
Definition: Twitter.java:895
com.google.appinventor.components.runtime.Component
Definition: Component.java:17
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_REQUEST_DIRECT_MESSAGES_FAILED
static final int ERROR_TWITTER_REQUEST_DIRECT_MESSAGES_FAILED
Definition: ErrorMessages.java:42
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_INVALID_IMAGE_PATH
static final int ERROR_TWITTER_INVALID_IMAGE_PATH
Definition: ErrorMessages.java:48
com.google.appinventor.components.common
Definition: ComponentCategory.java:7
com.google.appinventor.components.common.ComponentCategory
Definition: ComponentCategory.java:48
com.google.appinventor.components.runtime.ActivityResultListener
Definition: ActivityResultListener.java:16
com.google.appinventor.components.annotations.SimpleObject
Definition: SimpleObject.java:23
com.google.appinventor.components.runtime.util.AsynchUtil
Definition: AsynchUtil.java:17
com.google
com
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_REQUEST_MENTIONS_FAILED
static final int ERROR_TWITTER_REQUEST_MENTIONS_FAILED
Definition: ErrorMessages.java:40
com.google.appinventor.components.runtime.ComponentContainer.$form
Form $form()
com.google.appinventor.components.runtime.ComponentContainer.$context
Activity $context()
com.google.appinventor.components.annotations.androidmanifest.ActivityElement
Definition: ActivityElement.java:33
com.google.appinventor.components.runtime.Form
Definition: Form.java:126
com.google.appinventor.components.common.PropertyTypeConstants
Definition: PropertyTypeConstants.java:14
com.google.appinventor.components.annotations
com.google.appinventor.components.annotations.UsesActivities
Definition: UsesActivities.java:24
com.google.appinventor.components.runtime.Twitter.resultReturned
void resultReturned(int requestCode, int resultCode, Intent data)
Definition: Twitter.java:383
com.google.appinventor
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_TWITTER_REQUEST_FRIEND_TIMELINE_FAILED
static final int ERROR_TWITTER_REQUEST_FRIEND_TIMELINE_FAILED
Definition: ErrorMessages.java:46