Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

initial

  • Loading branch information...
commit 80653305754b9ebbd9dc520edcf08559c3ea2cf6 0 parents
sugree authored
12 .gitignore
... ... @@ -0,0 +1,12 @@
  1 +*~
  2 +*.DS_Store
  3 +*.class
  4 +*java#
  5 +*.classpath
  6 +*.project
  7 +*.settings
  8 +.gradle
  9 +twitter/local.properties
  10 +twitter/build/
  11 +twitter/bin/
  12 +twitter/gen/
28 build.gradle
... ... @@ -0,0 +1,28 @@
  1 +buildscript {
  2 + repositories {
  3 + mavenRepo urls: 'http://jvoegele.com/maven2/'
  4 + }
  5 + dependencies {
  6 + classpath 'com.jvoegele.gradle.plugins:android-plugin:0.9.3'
  7 + }
  8 +}
  9 +
  10 +dependsOnChildren()
  11 +
  12 +subprojects {
  13 + apply plugin: 'java'
  14 + apply plugin: 'eclipse'
  15 + apply plugin: com.jvoegele.gradle.plugins.android.AndroidPlugin
  16 +
  17 + proguard.enabled = false
  18 +
  19 + repositories {
  20 + flatDir name: 'localRepository', dirs: "${projectDir}/libs"
  21 + mavenCentral()
  22 + }
  23 +
  24 + eclipseClasspath {
  25 + downloadSources = false
  26 + downloadJavadoc = false
  27 + }
  28 +}
1  settings.gradle
... ... @@ -0,0 +1 @@
  1 +include "twitter"
7 twitter/AndroidManifest.xml
... ... @@ -0,0 +1,7 @@
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3 + package="com.sugree.twitter"
  4 + android:versionCode="1"
  5 + android:versionName="1.0">
  6 + <application />
  7 +</manifest>
4 twitter/build.gradle
... ... @@ -0,0 +1,4 @@
  1 +dependencies {
  2 + compile 'oauth.signpost:signpost-core:1.2.1.1'
  3 + compile 'oauth.signpost:signpost-commonshttp4:1.2.1.1'
  4 +}
17 twitter/build.properties
... ... @@ -0,0 +1,17 @@
  1 +# This file is used to override default values used by the Ant build system.
  2 +#
  3 +# This file must be checked in Version Control Systems, as it is
  4 +# integral to the build system of your project.
  5 +
  6 +# This file is only used by the Ant script.
  7 +
  8 +# You can use this to override default values such as
  9 +# 'source.dir' for the location of your java source folder and
  10 +# 'out.dir' for the location of your output folder.
  11 +
  12 +# You can also use it define how the release builds are signed by declaring
  13 +# the following properties:
  14 +# 'key.store' for the location of your keystore and
  15 +# 'key.alias' for the name of the key to use.
  16 +# The password will be asked during the build when you use the 'release' target.
  17 +
76 twitter/build.xml
... ... @@ -0,0 +1,76 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project name="twitter-android-sdk" default="help">
  3 +
  4 + <!-- The local.properties file is created and updated by the 'android' tool.
  5 + It contains the path to the SDK. It should *NOT* be checked in in Version
  6 + Control Systems. -->
  7 + <property file="local.properties" />
  8 +
  9 + <!-- The build.properties file can be created by you and is never touched
  10 + by the 'android' tool. This is the place to change some of the default property values
  11 + used by the Ant rules.
  12 + Here are some properties you may want to change/update:
  13 +
  14 + application.package
  15 + the name of your application package as defined in the manifest. Used by the
  16 + 'uninstall' rule.
  17 + source.dir
  18 + the name of the source directory. Default is 'src'.
  19 + out.dir
  20 + the name of the output directory. Default is 'bin'.
  21 +
  22 + Properties related to the SDK location or the project target should be updated
  23 + using the 'android' tool with the 'update' action.
  24 +
  25 + This file is an integral part of the build system for your application and
  26 + should be checked in in Version Control Systems.
  27 +
  28 + -->
  29 + <property file="build.properties" />
  30 +
  31 + <!-- The default.properties file is created and updated by the 'android' tool, as well
  32 + as ADT.
  33 + This file is an integral part of the build system for your application and
  34 + should be checked in in Version Control Systems. -->
  35 + <property file="default.properties" />
  36 +
  37 + <!-- Custom Android task to deal with the project target, and import the proper rules.
  38 + This requires ant 1.6.0 or above. -->
  39 + <path id="android.antlibs">
  40 + <pathelement path="${sdk.dir}/tools/lib/anttasks.jar" />
  41 + <pathelement path="${sdk.dir}/tools/lib/sdklib.jar" />
  42 + <pathelement path="${sdk.dir}/tools/lib/androidprefs.jar" />
  43 + <pathelement path="${sdk.dir}/tools/lib/apkbuilder.jar" />
  44 + <pathelement path="${sdk.dir}/tools/lib/jarutils.jar" />
  45 + </path>
  46 +
  47 + <taskdef name="setup"
  48 + classname="com.android.ant.SetupTask"
  49 + classpathref="android.antlibs" />
  50 +
  51 + <!-- Execute the Android Setup task that will setup some properties specific to the target,
  52 + and import the build rules files.
  53 +
  54 + The rules file is imported from
  55 + <SDK>/platforms/<target_platform>/templates/android_rules.xml
  56 +
  57 + To customize some build steps for your project:
  58 + - copy the content of the main node <project> from android_rules.xml
  59 + - paste it in this build.xml below the <setup /> task.
  60 + - disable the import by changing the setup task below to <setup import="false" />
  61 +
  62 + This will ensure that the properties are setup correctly but that your customized
  63 + build steps are used.
  64 + -->
  65 + <setup />
  66 +
  67 + <target name="jar" depends="compile">
  68 + <xpath input="AndroidManifest.xml" expression="/manifest/@android:versionName"
  69 + output="project.version" />
  70 + <delete file="${out.absolute.dir}/${ant.project.name}-${project.version}.jar" />
  71 + <jar destfile="${out.absolute.dir}/${ant.project.name}-${project.version}.jar">
  72 + <fileset dir="${out.classes.absolute.dir}" />
  73 + </jar>
  74 + </target>
  75 +
  76 +</project>
12 twitter/default.properties
... ... @@ -0,0 +1,12 @@
  1 +# This file is automatically generated by Android Tools.
  2 +# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
  3 +#
  4 +# This file must be checked in Version Control Systems.
  5 +#
  6 +# To customize properties used by the Ant build system use,
  7 +# "build.properties", and override values to adapt the script to your
  8 +# project structure.
  9 +
  10 +android.library=true
  11 +# Project target.
  12 +target=android-3
103 twitter/src/com/sugree/twitter/AsyncTwitterRunner.java
... ... @@ -0,0 +1,103 @@
  1 +/*
  2 + * Copyright 2010 Facebook, Inc.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package com.sugree.twitter;
  18 +
  19 +import java.io.FileNotFoundException;
  20 +import java.io.IOException;
  21 +import java.net.MalformedURLException;
  22 +
  23 +import android.content.Context;
  24 +import android.os.Bundle;
  25 +
  26 +/**
  27 + * A sample implementation of asynchronous API requests. This class provides
  28 + * the ability to execute API methods and have the call return immediately,
  29 + * without blocking the calling thread. This is necessary when accessing the
  30 + * API in the UI thread, for instance. The request response is returned to
  31 + * the caller via a callback interface, which the developer must implement.
  32 + *
  33 + * This sample implementation simply spawns a new thread for each request,
  34 + * and makes the API call immediately. This may work in many applications,
  35 + * but more sophisticated users may re-implement this behavior using a thread
  36 + * pool, a network thread, a request queue, or other mechanism. Advanced
  37 + * functionality could be built, such as rate-limiting of requests, as per
  38 + * a specific application's needs.
  39 + *
  40 + * @see RequestListener
  41 + * The callback interface.
  42 + *
  43 + * @author ssoneff@facebook.com
  44 + *
  45 + */
  46 +public class AsyncTwitterRunner {
  47 +
  48 + Twitter tw;
  49 +
  50 + public AsyncTwitterRunner(Twitter tw) {
  51 + this.tw = tw;
  52 + }
  53 +
  54 + /**
  55 + * Invalidate the current user session by removing the access token in
  56 + * memory, clearing the browser cookies, and calling auth.expireSession
  57 + * through the API. The application will be notified when logout is
  58 + * complete via the callback interface.
  59 + *
  60 + * Note that this method is asynchronous and the callback will be invoked
  61 + * in a background thread; operations that affect the UI will need to be
  62 + * posted to the UI thread or an appropriate handler.
  63 + *
  64 + * @param context
  65 + * The Android context in which the logout should be called: it
  66 + * should be the same context in which the login occurred in
  67 + * order to clear any stored cookies
  68 + * @param listener
  69 + * Callback interface to notify the application when the request
  70 + * has completed.
  71 + */
  72 + public void logout(final Context context, final RequestListener listener) {
  73 + new Thread() {
  74 + @Override public void run() {
  75 + try {
  76 + String response = tw.logout(context);
  77 + if (response.length() == 0 || response.equals("false")){
  78 + listener.onTwitterError(new TwitterError(
  79 + "auth.expireSession failed"));
  80 + return;
  81 + }
  82 + listener.onComplete(response);
  83 + } catch (FileNotFoundException e) {
  84 + listener.onFileNotFoundException(e);
  85 + } catch (MalformedURLException e) {
  86 + listener.onMalformedURLException(e);
  87 + } catch (IOException e) {
  88 + listener.onIOException(e);
  89 + }
  90 + }
  91 + }.start();
  92 + }
  93 +
  94 + public static interface RequestListener {
  95 + public void onComplete(String response);
  96 + public void onIOException(IOException e);
  97 + public void onFileNotFoundException(FileNotFoundException e);
  98 + public void onMalformedURLException(MalformedURLException e);
  99 + public void onTwitterError(TwitterError e);
  100 +
  101 + }
  102 +
  103 +}
24 twitter/src/com/sugree/twitter/DialogError.java
... ... @@ -0,0 +1,24 @@
  1 +package com.sugree.twitter;
  2 +
  3 +public class DialogError extends Throwable {
  4 +
  5 + private static final long serialVersionUID = -992704825747001028L;
  6 +
  7 + private int mErrorCode;
  8 + private String mFailingUrl;
  9 +
  10 + public DialogError(String message, int errorCode, String failingUrl) {
  11 + super(message);
  12 + mErrorCode = errorCode;
  13 + mFailingUrl = failingUrl;
  14 + }
  15 +
  16 + public int getErrorCode() {
  17 + return mErrorCode;
  18 + }
  19 +
  20 + public String getFailingUrl() {
  21 + return mFailingUrl;
  22 + }
  23 +
  24 +}
242 twitter/src/com/sugree/twitter/TwDialog.java
... ... @@ -0,0 +1,242 @@
  1 +/*
  2 + * Copyright 2010 Facebook, Inc.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package com.sugree.twitter;
  18 +
  19 +import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
  20 +import oauth.signpost.commonshttp.CommonsHttpOAuthProvider;
  21 +import oauth.signpost.exception.OAuthCommunicationException;
  22 +import oauth.signpost.exception.OAuthExpectationFailedException;
  23 +import oauth.signpost.exception.OAuthMessageSignerException;
  24 +import oauth.signpost.exception.OAuthNotAuthorizedException;
  25 +import android.app.Dialog;
  26 +import android.app.ProgressDialog;
  27 +import android.content.Context;
  28 +import android.content.Intent;
  29 +import android.graphics.Bitmap;
  30 +import android.graphics.Color;
  31 +import android.graphics.Typeface;
  32 +import android.graphics.drawable.Drawable;
  33 +import android.net.Uri;
  34 +import android.os.Bundle;
  35 +import android.os.Handler;
  36 +import android.util.Log;
  37 +import android.view.Display;
  38 +import android.view.ViewGroup;
  39 +import android.view.Window;
  40 +import android.webkit.WebView;
  41 +import android.webkit.WebViewClient;
  42 +import android.widget.FrameLayout;
  43 +import android.widget.LinearLayout;
  44 +import android.widget.TextView;
  45 +
  46 +import com.sugree.twitter.Twitter.DialogListener;
  47 +
  48 +public class TwDialog extends Dialog {
  49 + public static final String TAG = "twitter";
  50 +
  51 + static final int TW_BLUE = 0xFFC0DEED;
  52 + static final float[] DIMENSIONS_LANDSCAPE = {460, 260};
  53 + static final float[] DIMENSIONS_PORTRAIT = {280, 420};
  54 + static final FrameLayout.LayoutParams FILL =
  55 + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
  56 + ViewGroup.LayoutParams.FILL_PARENT);
  57 + static final int MARGIN = 4;
  58 + static final int PADDING = 2;
  59 +
  60 + private int mIcon;
  61 + private String mUrl;
  62 + private DialogListener mListener;
  63 + private ProgressDialog mSpinner;
  64 + private WebView mWebView;
  65 + private LinearLayout mContent;
  66 + private TextView mTitle;
  67 + private Handler mHandler;
  68 +
  69 + private CommonsHttpOAuthConsumer mConsumer;
  70 + private CommonsHttpOAuthProvider mProvider;
  71 +
  72 + public TwDialog(Context context,
  73 + CommonsHttpOAuthProvider provider,
  74 + CommonsHttpOAuthConsumer consumer,
  75 + DialogListener listener, int icon) {
  76 + super(context);
  77 + mProvider = provider;
  78 + mConsumer = consumer;
  79 + mListener = listener;
  80 + mIcon = icon;
  81 + mHandler = new Handler();
  82 + }
  83 +
  84 + @Override
  85 + protected void onCreate(Bundle savedInstanceState) {
  86 + super.onCreate(savedInstanceState);
  87 + mSpinner = new ProgressDialog(getContext());
  88 + mSpinner.requestWindowFeature(Window.FEATURE_NO_TITLE);
  89 + mSpinner.setMessage("Loading...");
  90 +
  91 + mContent = new LinearLayout(getContext());
  92 + mContent.setOrientation(LinearLayout.VERTICAL);
  93 + setUpTitle();
  94 + setUpWebView();
  95 +
  96 + Display display = getWindow().getWindowManager().getDefaultDisplay();
  97 + final float scale = getContext().getResources().getDisplayMetrics().density;
  98 + float[] dimensions = display.getWidth() < display.getHeight() ?
  99 + DIMENSIONS_PORTRAIT : DIMENSIONS_LANDSCAPE;
  100 + addContentView(mContent, new FrameLayout.LayoutParams(
  101 + (int) (dimensions[0] * scale + 0.5f),
  102 + (int) (dimensions[1] * scale + 0.5f)));
  103 +
  104 + retrieveRequestToken();
  105 + }
  106 +
  107 + @Override
  108 + public void show() {
  109 + super.show();
  110 + mSpinner.show();
  111 + }
  112 +
  113 + private void setUpTitle() {
  114 + requestWindowFeature(Window.FEATURE_NO_TITLE);
  115 + Drawable icon = getContext().getResources().getDrawable(mIcon);
  116 + mTitle = new TextView(getContext());
  117 + mTitle.setText("Twitter");
  118 + mTitle.setTextColor(Color.WHITE);
  119 + mTitle.setTypeface(Typeface.DEFAULT_BOLD);
  120 + mTitle.setBackgroundColor(TW_BLUE);
  121 + mTitle.setPadding(MARGIN + PADDING, MARGIN, MARGIN, MARGIN);
  122 + mTitle.setCompoundDrawablePadding(MARGIN + PADDING);
  123 + mTitle.setCompoundDrawablesWithIntrinsicBounds(
  124 + icon, null, null, null);
  125 + mContent.addView(mTitle);
  126 + }
  127 +
  128 + private void retrieveRequestToken() {
  129 + mSpinner.show();
  130 + new Thread() {
  131 + @Override
  132 + public void run() {
  133 + try {
  134 + mUrl = mProvider.retrieveRequestToken(mConsumer, Twitter.CALLBACK_URI);
  135 + mWebView.loadUrl(mUrl);
  136 + } catch (OAuthMessageSignerException e) {
  137 + mListener.onError(new DialogError(e.getMessage(), -1, Twitter.OAUTH_REQUEST_TOKEN));
  138 + } catch (OAuthNotAuthorizedException e) {
  139 + mListener.onError(new DialogError(e.getMessage(), -1, Twitter.OAUTH_REQUEST_TOKEN));
  140 + } catch (OAuthExpectationFailedException e) {
  141 + mListener.onError(new DialogError(e.getMessage(), -1, Twitter.OAUTH_REQUEST_TOKEN));
  142 + } catch (OAuthCommunicationException e) {
  143 + mListener.onError(new DialogError(e.getMessage(), -1, Twitter.OAUTH_REQUEST_TOKEN));
  144 + }
  145 + }
  146 + }.start();
  147 + }
  148 +
  149 + private void retrieveAccessToken(final String url) {
  150 + mSpinner.show();
  151 + new Thread() {
  152 + @Override
  153 + public void run() {
  154 + Uri uri = Uri.parse(url);
  155 + String verifier = uri.getQueryParameter(oauth.signpost.OAuth.OAUTH_VERIFIER);
  156 + final Bundle values = new Bundle();
  157 + try {
  158 + mProvider.retrieveAccessToken(mConsumer, verifier);
  159 + values.putString(Twitter.ACCESS_TOKEN, mConsumer.getToken());
  160 + values.putString(Twitter.SECRET_TOKEN, mConsumer.getTokenSecret());
  161 + mListener.onComplete(values);
  162 + } catch (OAuthMessageSignerException e) {
  163 + mListener.onError(new DialogError(e.getMessage(), -1, verifier));
  164 + } catch (OAuthNotAuthorizedException e) {
  165 + mListener.onTwitterError(new TwitterError(e.getMessage()));
  166 + } catch (OAuthExpectationFailedException e) {
  167 + mListener.onTwitterError(new TwitterError(e.getMessage()));
  168 + } catch (OAuthCommunicationException e) {
  169 + mListener.onError(new DialogError(e.getMessage(), -1, verifier));
  170 + }
  171 + mHandler.post(new Runnable() {
  172 + @Override
  173 + public void run() {
  174 + mSpinner.dismiss();
  175 + TwDialog.this.dismiss();
  176 + }
  177 + });
  178 + }
  179 + }.start();
  180 + }
  181 +
  182 + private void setUpWebView() {
  183 + mWebView = new WebView(getContext());
  184 + mWebView.setVerticalScrollBarEnabled(false);
  185 + mWebView.setHorizontalScrollBarEnabled(false);
  186 + mWebView.setWebViewClient(new TwDialog.TwWebViewClient());
  187 + mWebView.getSettings().setJavaScriptEnabled(true);
  188 + //mWebView.loadUrl(mUrl);
  189 + mWebView.setLayoutParams(FILL);
  190 + mContent.addView(mWebView);
  191 + }
  192 +
  193 + private class TwWebViewClient extends WebViewClient {
  194 +
  195 + @Override
  196 + public boolean shouldOverrideUrlLoading(WebView view, String url) {
  197 + Log.d(TAG, "Redirect URL: " + url);
  198 + if (url.startsWith(Twitter.CALLBACK_URI)) {
  199 + retrieveAccessToken(url);
  200 + return true;
  201 + } else if (url.startsWith(Twitter.CANCEL_URI)) {
  202 + mListener.onCancel();
  203 + TwDialog.this.dismiss();
  204 + return true;
  205 + }
  206 + // launch non-dialog URLs in a full browser
  207 + getContext().startActivity(
  208 + new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
  209 + return true;
  210 + }
  211 +
  212 + @Override
  213 + public void onReceivedError(WebView view, int errorCode,
  214 + String description, String failingUrl) {
  215 + super.onReceivedError(view, errorCode, description, failingUrl);
  216 + mListener.onError(
  217 + new DialogError(description, errorCode, failingUrl));
  218 + TwDialog.this.dismiss();
  219 + }
  220 +
  221 + @Override
  222 + public void onPageStarted(WebView view, String url, Bitmap favicon) {
  223 + Log.d(TAG, "WebView loading URL: " + url);
  224 + super.onPageStarted(view, url, favicon);
  225 + if (mSpinner.isShowing()) {
  226 + mSpinner.dismiss();
  227 + }
  228 + mSpinner.show();
  229 + }
  230 +
  231 + @Override
  232 + public void onPageFinished(WebView view, String url) {
  233 + super.onPageFinished(view, url);
  234 + String title = mWebView.getTitle();
  235 + if (title != null && title.length() > 0) {
  236 + mTitle.setText(title);
  237 + }
  238 + mSpinner.dismiss();
  239 + }
  240 +
  241 + }
  242 +}
136 twitter/src/com/sugree/twitter/Twitter.java
... ... @@ -0,0 +1,136 @@
  1 +package com.sugree.twitter;
  2 +
  3 +import java.io.IOException;
  4 +import java.net.MalformedURLException;
  5 +
  6 +import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
  7 +import oauth.signpost.commonshttp.CommonsHttpOAuthProvider;
  8 +import oauth.signpost.exception.OAuthCommunicationException;
  9 +import oauth.signpost.exception.OAuthExpectationFailedException;
  10 +import oauth.signpost.exception.OAuthMessageSignerException;
  11 +import oauth.signpost.exception.OAuthNotAuthorizedException;
  12 +import android.Manifest;
  13 +import android.content.Context;
  14 +import android.content.pm.PackageManager;
  15 +import android.os.Bundle;
  16 +import android.os.Handler;
  17 +import android.util.Log;
  18 +import android.webkit.CookieSyncManager;
  19 +
  20 +public class Twitter {
  21 + public static final String TAG = "twitter";
  22 +
  23 + public static final String CALLBACK_URI = "twitter://callback";
  24 + public static final String CANCEL_URI = "twitter://cancel";
  25 + public static final String ACCESS_TOKEN = "access_token";
  26 + public static final String SECRET_TOKEN = "secret_token";
  27 +
  28 + public static final String REQUEST = "request";
  29 + public static final String AUTHORIZE = "authorize";
  30 +
  31 + protected static String REQUEST_ENDPOINT = "https://api.twitter.com/1";
  32 +
  33 + protected static String OAUTH_REQUEST_TOKEN = "https://api.twitter.com/oauth/request_token";
  34 + protected static String OAUTH_ACCESS_TOKEN = "https://api.twitter.com/oauth/access_token";
  35 + protected static String OAUTH_AUTHORIZE = "https://api.twitter.com/oauth/authorize";
  36 +
  37 + private String mAccessToken = null;
  38 + private String mSecretToken = null;
  39 +
  40 + private int mIcon;
  41 + private CommonsHttpOAuthConsumer mHttpOauthConsumer;
  42 + private CommonsHttpOAuthProvider mHttpOauthProvider;
  43 +
  44 + public Twitter(int icon) {
  45 + mIcon = icon;
  46 + }
  47 +
  48 + public void authorize(Context ctx,
  49 + Handler handler,
  50 + String consumerKey,
  51 + String consumerSecret,
  52 + final DialogListener listener) {
  53 + mHttpOauthConsumer = new CommonsHttpOAuthConsumer(
  54 + consumerKey, consumerSecret);
  55 + mHttpOauthProvider = new CommonsHttpOAuthProvider(
  56 + OAUTH_REQUEST_TOKEN, OAUTH_ACCESS_TOKEN, OAUTH_AUTHORIZE);
  57 + CookieSyncManager.createInstance(ctx);
  58 + dialog(ctx, handler, new DialogListener() {
  59 +
  60 + @Override
  61 + public void onComplete(Bundle values) {
  62 + CookieSyncManager.getInstance().sync();
  63 + setAccessToken(values.getString(ACCESS_TOKEN));
  64 + setSecretToken(values.getString(SECRET_TOKEN));
  65 + if (isSessionValid()) {
  66 + Log.d(TAG, "token "+getAccessToken()+" "+getSecretToken());
  67 + listener.onComplete(values);
  68 + } else {
  69 + onTwitterError(new TwitterError("failed to receive oauth token"));
  70 + }
  71 + }
  72 +
  73 + @Override
  74 + public void onTwitterError(TwitterError e) {
  75 + Log.d(TAG, "Login failed: "+e);
  76 + listener.onTwitterError(e);
  77 + }
  78 +
  79 + @Override
  80 + public void onError(DialogError e) {
  81 + Log.d(TAG, "Login failed: "+e);
  82 + listener.onError(e);
  83 + }
  84 +
  85 + @Override
  86 + public void onCancel() {
  87 + Log.d(TAG, "Login cancelled");
  88 + listener.onCancel();
  89 + }
  90 +
  91 + });
  92 + }
  93 +
  94 + public String logout(Context context) throws MalformedURLException, IOException {
  95 + return "true";
  96 + }
  97 +
  98 + public void dialog(final Context ctx,
  99 + Handler handler,
  100 + final DialogListener listener) {
  101 + if (ctx.checkCallingOrSelfPermission(Manifest.permission.INTERNET) !=
  102 + PackageManager.PERMISSION_GRANTED) {
  103 + Util.showAlert(ctx, "Error", "Application requires permission to access the Internet");
  104 + return;
  105 + }
  106 + new TwDialog(ctx, mHttpOauthProvider, mHttpOauthConsumer,
  107 + listener, mIcon).show();
  108 + }
  109 +
  110 + public boolean isSessionValid() {
  111 + return getAccessToken() != null && getSecretToken() != null;
  112 + }
  113 +
  114 + public String getAccessToken() {
  115 + return mAccessToken;
  116 + }
  117 +
  118 + public void setAccessToken(String accessToken) {
  119 + mAccessToken = accessToken;
  120 + }
  121 +
  122 + public String getSecretToken() {
  123 + return mSecretToken;
  124 + }
  125 +
  126 + public void setSecretToken(String secretToken) {
  127 + mSecretToken = secretToken;
  128 + }
  129 +
  130 + public static interface DialogListener {
  131 + public void onComplete(Bundle values);
  132 + public void onTwitterError(TwitterError e);
  133 + public void onError(DialogError e);
  134 + public void onCancel();
  135 + }
  136 +}
28 twitter/src/com/sugree/twitter/TwitterError.java
... ... @@ -0,0 +1,28 @@
  1 +package com.sugree.twitter;
  2 +
  3 +public class TwitterError extends Throwable {
  4 +
  5 + private static final long serialVersionUID = 6626439442641443626L;
  6 +
  7 + private int mErrorCode = 0;
  8 + private String mErrorType;
  9 +
  10 + public TwitterError(String message) {
  11 + super(message);
  12 + }
  13 +
  14 + public TwitterError(String message, String errorType, int errorCode) {
  15 + super(message);
  16 + mErrorType = errorType;
  17 + mErrorCode = errorCode;
  18 + }
  19 +
  20 + public int getErrorCode() {
  21 + return mErrorCode;
  22 + }
  23 +
  24 + public String getErrorType() {
  25 + return mErrorType;
  26 + }
  27 +
  28 +}
296 twitter/src/com/sugree/twitter/Util.java
... ... @@ -0,0 +1,296 @@
  1 +/*
  2 + * Copyright 2010 Facebook, Inc.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package com.sugree.twitter;
  18 +
  19 +import java.io.BufferedOutputStream;
  20 +import java.io.BufferedReader;
  21 +import java.io.FileNotFoundException;
  22 +import java.io.IOException;
  23 +import java.io.InputStream;
  24 +import java.io.InputStreamReader;
  25 +import java.io.OutputStream;
  26 +import java.net.HttpURLConnection;
  27 +import java.net.MalformedURLException;
  28 +import java.net.URL;
  29 +import java.net.URLDecoder;
  30 +
  31 +import org.json.JSONException;
  32 +import org.json.JSONObject;
  33 +
  34 +import android.app.AlertDialog.Builder;
  35 +import android.content.Context;
  36 +import android.os.Bundle;
  37 +import android.util.Log;
  38 +import android.webkit.CookieManager;
  39 +import android.webkit.CookieSyncManager;
  40 +
  41 +
  42 +/**
  43 + * Utility class supporting the Facebook Object.
  44 + *
  45 + * @author ssoneff@facebook.com
  46 + *
  47 + */
  48 +public final class Util {
  49 +
  50 + /**
  51 + * Generate the multi-part post body providing the parameters and boundary
  52 + * string
  53 + *
  54 + * @param parameters the parameters need to be posted
  55 + * @param boundary the random string as boundary
  56 + * @return a string of the post body
  57 + */
  58 + public static String encodePostBody(Bundle parameters, String boundary) {
  59 + if (parameters == null) return "";
  60 + StringBuilder sb = new StringBuilder();
  61 +
  62 + for (String key : parameters.keySet()) {
  63 + if (parameters.getByteArray(key) != null) {
  64 + continue;
  65 + }
  66 +
  67 + sb.append("Content-Disposition: form-data; name=\"" + key +
  68 + "\"\r\n\r\n" + parameters.getString(key));
  69 + sb.append("\r\n" + "--" + boundary + "\r\n");
  70 + }
  71 +
  72 + return sb.toString();
  73 + }
  74 +
  75 + public static String encodeUrl(Bundle parameters) {
  76 + if (parameters == null) {
  77 + return "";
  78 + }
  79 +
  80 + StringBuilder sb = new StringBuilder();
  81 + boolean first = true;
  82 + for (String key : parameters.keySet()) {
  83 + if (first) first = false; else sb.append("&");
  84 + sb.append(key + "=" + parameters.getString(key));
  85 + }
  86 + return sb.toString();
  87 + }
  88 +
  89 + public static Bundle decodeUrl(String s) {
  90 + Bundle params = new Bundle();
  91 + if (s != null) {
  92 + String array[] = s.split("&");
  93 + for (String parameter : array) {
  94 + String v[] = parameter.split("=");
  95 + params.putString(v[0], v[1]);
  96 + }
  97 + }
  98 + return params;
  99 + }
  100 +
  101 + /**
  102 + * Parse a URL query and fragment parameters into a key-value bundle.
  103 + *
  104 + * @param url the URL to parse
  105 + * @return a dictionary bundle of keys and values
  106 + */
  107 + public static Bundle parseUrl(String url) {
  108 + // hack to prevent MalformedURLException
  109 + url = url.replace("fbconnect", "http");
  110 + try {
  111 + URL u = new URL(url);
  112 + Bundle b = decodeUrl(u.getQuery());
  113 + b.putAll(decodeUrl(u.getRef()));
  114 + return b;
  115 + } catch (MalformedURLException e) {
  116 + return new Bundle();
  117 + }
  118 + }
  119 +
  120 +
  121 + /**
  122 + * Connect to an HTTP URL and return the response as a string.
  123 + *
  124 + * Note that the HTTP method override is used on non-GET requests. (i.e.
  125 + * requests are made as "POST" with method specified in the body).
  126 + *
  127 + * @param url - the resource to open: must be a welformed URL
  128 + * @param method - the HTTP method to use ("GET", "POST", etc.)
  129 + * @param params - the query parameter for the URL (e.g. access_token=foo)
  130 + * @return the URL contents as a String
  131 + * @throws MalformedURLException - if the URL format is invalid
  132 + * @throws IOException - if a network problem occurs
  133 + */
  134 + public static String openUrl(String url, String method, Bundle params)
  135 + throws MalformedURLException, IOException {
  136 + // random string as boundary for multi-part http post
  137 + String strBoundary = "3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f";
  138 + String endLine = "\r\n";
  139 +
  140 + OutputStream os;
  141 +
  142 + if (method.equals("GET")) {
  143 + url = url + "?" + encodeUrl(params);
  144 + }
  145 + Log.d("Facebook-Util", method + " URL: " + url);
  146 + HttpURLConnection conn =
  147 + (HttpURLConnection) new URL(url).openConnection();
  148 + conn.setRequestProperty("User-Agent", System.getProperties().
  149 + getProperty("http.agent") + " FacebookAndroidSDK");
  150 + if (!method.equals("GET")) {
  151 + Bundle dataparams = new Bundle();
  152 + for (String key : params.keySet()) {
  153 + if (params.getByteArray(key) != null) {
  154 + dataparams.putByteArray(key, params.getByteArray(key));
  155 + }
  156 + }
  157 +
  158 + // use method override
  159 + if (!params.containsKey("method")) {
  160 + params.putString("method", method);
  161 + }
  162 +
  163 + if (params.containsKey("access_token")) {
  164 + String decoded_token = URLDecoder.decode(params.getString("access_token"));
  165 + params.putString("access_token", decoded_token);
  166 + }
  167 +
  168 + conn.setRequestMethod("POST");
  169 + conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+strBoundary);
  170 + conn.setDoOutput(true);
  171 + conn.setDoInput(true);
  172 + conn.setRequestProperty("Connection", "Keep-Alive");
  173 + conn.connect();
  174 + os = new BufferedOutputStream(conn.getOutputStream());
  175 +
  176 + os.write(("--" + strBoundary +endLine).getBytes());
  177 + os.write((encodePostBody(params, strBoundary)).getBytes());
  178 + os.write((endLine + "--" + strBoundary + endLine).getBytes());
  179 +
  180 + if (!dataparams.isEmpty()) {
  181 +
  182 + for (String key: dataparams.keySet()){
  183 + os.write(("Content-Disposition: form-data; filename=\"" + key + "\"" + endLine).getBytes());
  184 + os.write(("Content-Type: content/unknown" + endLine + endLine).getBytes());
  185 + os.write(dataparams.getByteArray(key));
  186 + os.write((endLine + "--" + strBoundary + endLine).getBytes());
  187 +
  188 + }
  189 + }
  190 + os.flush();
  191 + }
  192 +
  193 + String response = "";
  194 + try {
  195 + response = read(conn.getInputStream());
  196 + } catch (FileNotFoundException e) {
  197 + // Error Stream contains JSON that we can parse to a FB error
  198 + response = read(conn.getErrorStream());
  199 + }
  200 + return response;
  201 + }
  202 +
  203 + private static String read(InputStream in) throws IOException {
  204 + StringBuilder sb = new StringBuilder();
  205 + BufferedReader r = new BufferedReader(new InputStreamReader(in), 1000);
  206 + for (String line = r.readLine(); line != null; line = r.readLine()) {
  207 + sb.append(line);
  208 + }
  209 + in.close();
  210 + return sb.toString();
  211 + }
  212 +
  213 + public static void clearCookies(Context context) {
  214 + // Edge case: an illegal state exception is thrown if an instance of
  215 + // CookieSyncManager has not be created. CookieSyncManager is normally
  216 + // created by a WebKit view, but this might happen if you start the
  217 + // app, restore saved state, and click logout before running a UI
  218 + // dialog in a WebView -- in which case the app crashes
  219 + @SuppressWarnings("unused")
  220 + CookieSyncManager cookieSyncMngr =
  221 + CookieSyncManager.createInstance(context);
  222 + CookieManager cookieManager = CookieManager.getInstance();
  223 + cookieManager.removeAllCookie();
  224 + }
  225 +
  226 + /**
  227 + * Parse a server response into a JSON Object. This is a basic
  228 + * implementation using org.json.JSONObject representation. More
  229 + * sophisticated applications may wish to do their own parsing.
  230 + *
  231 + * The parsed JSON is checked for a variety of error fields and
  232 + * a FacebookException is thrown if an error condition is set,
  233 + * populated with the error message and error type or code if
  234 + * available.
  235 + *
  236 + * @param response - string representation of the response
  237 + * @return the response as a JSON Object
  238 + * @throws JSONException - if the response is not valid JSON
  239 + * @throws FacebookError - if an error condition is set
  240 + */
  241 + public static JSONObject parseJson(String response)
  242 + throws JSONException, TwitterError {
  243 + // Edge case: when sending a POST request to /[post_id]/likes
  244 + // the return value is 'true' or 'false'. Unfortunately
  245 + // these values cause the JSONObject constructor to throw
  246 + // an exception.
  247 + if (response.equals("false")) {
  248 + throw new TwitterError("request failed");
  249 + }
  250 + if (response.equals("true")) {
  251 + response = "{value : true}";
  252 + }
  253 + JSONObject json = new JSONObject(response);
  254 +
  255 + // errors set by the server are not consistent
  256 + // they depend on the method and endpoint
  257 + if (json.has("error")) {
  258 + JSONObject error = json.getJSONObject("error");
  259 + throw new TwitterError(
  260 + error.getString("message"), error.getString("type"), 0);
  261 + }
  262 + if (json.has("error_code") && json.has("error_msg")) {
  263 + throw new TwitterError(json.getString("error_msg"), "",
  264 + Integer.parseInt(json.getString("error_code")));
  265 + }
  266 + if (json.has("error_code")) {
  267 + throw new TwitterError("request failed", "",
  268 + Integer.parseInt(json.getString("error_code")));
  269 + }
  270 + if (json.has("error_msg")) {
  271 + throw new TwitterError(json.getString("error_msg"));
  272 + }
  273 + if (json.has("error_reason")) {
  274 + throw new TwitterError(json.getString("error_reason"));
  275 + }
  276 + return json;
  277 + }
  278 +
  279 + /**
  280 + * Display a simple alert dialog with the given text and title.
  281 + *
  282 + * @param context
  283 + * Android context in which the dialog should be displayed
  284 + * @param title
  285 + * Alert dialog title
  286 + * @param text
  287 + * Alert dialog message
  288 + */
  289 + public static void showAlert(Context context, String title, String text) {
  290 + Builder alertBuilder = new Builder(context);
  291 + alertBuilder.setTitle(title);
  292 + alertBuilder.setMessage(text);
  293 + alertBuilder.create().show();
  294 + }
  295 +
  296 +}

0 comments on commit 8065330

Please sign in to comment.
Something went wrong with that request. Please try again.