/*
* Firetweet - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.getlantern.firetweet.activity.support;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.StrictMode;
import android.support.annotation.NonNull;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.ImageButton;
import android.view.View.OnClickListener;
import android.webkit.JavascriptInterface;
import android.webkit.SslErrorHandler;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import android.annotation.TargetApi;
import android.os.Build;
import android.webkit.WebView.FindListener;
import org.getlantern.firetweet.R;
import org.getlantern.firetweet.app.FiretweetApplication;
import org.getlantern.firetweet.provider.FiretweetDataStore.Accounts;
import org.getlantern.firetweet.proxy.ProxySettings;
import org.getlantern.firetweet.util.AsyncTaskUtils;
import org.getlantern.firetweet.util.OAuthPasswordAuthenticator;
import org.getlantern.firetweet.util.ParseUtils;
import org.getlantern.firetweet.util.TwitterContentUtils;
import org.getlantern.firetweet.util.Utils;
import org.getlantern.firetweet.util.net.OkHttpClientFactory;
import org.getlantern.firetweet.util.net.FiretweetHostResolverFactory;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.StringReader;
import twitter4j.Twitter;
import twitter4j.TwitterConstants;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.auth.RequestToken;
import twitter4j.conf.ConfigurationBuilder;
import static android.text.TextUtils.isEmpty;
import static org.getlantern.firetweet.util.Utils.getNonEmptyString;
import android.util.Log;
@SuppressLint("SetJavaScriptEnabled")
public class BrowserSignInActivity extends BaseSupportDialogActivity implements TwitterConstants {
private static final String INJECT_CONTENT = "javascript:window.injector.processHTML('<head>'+document.getElementsByTagName('html')[0].innerHTML+'</head>');";
private static final String PROXY_HOST = "127.0.0.1";
private static final int PROXY_PORT = 8787;
private static final String CANCEL_PAGE = "https://api.twitter.com/oauth/authorize";
private static final String CANCEL_TEXT = "You have not signed in";
private static final String SIGNUP_URL = "https://mobile.twitter.com/signup?oauth_token=";
private SharedPreferences mPreferences;
private WebView mWebView;
private View mProgressContainer;
private WebSettings mWebSettings;
private RequestToken mRequestToken;
public static String action = "sign_in";
private GetRequestTokenTask mTask;
@Override
public void onContentChanged() {
super.onContentChanged();
mWebView = (WebView) findViewById(R.id.webview);
mProgressContainer = findViewById(R.id.progress_container);
}
@Override
public void onDestroy() {
getLoaderManager().destroyLoader(0);
super.onDestroy();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case MENU_HOME: {
finish();
return true;
}
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
mPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
setContentView(R.layout.activity_browser_sign_in);
ProxySettings.setProxy(this, mWebView,
FiretweetApplication.PROXY_HOST,
FiretweetApplication.PROXY_PORT);
Log.d("TwitterBrowserSignIn", "Enabled proxy settings");
mWebView.setWebViewClient(new AuthorizationWebViewClient(this));
mWebView.setVerticalScrollBarEnabled(false);
mWebView.addJavascriptInterface(new InjectorJavaScriptInterface(this), "injector");
mWebSettings = mWebView.getSettings();
mWebSettings.setLoadsImagesAutomatically(true);
mWebSettings.setJavaScriptEnabled(true);
mWebSettings.setBlockNetworkImage(false);
mWebSettings.setSaveFormData(true);
mWebSettings.setDomStorageEnabled(true);
mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
getRequestToken();
}
private void getRequestToken() {
if (mRequestToken != null || mTask != null && mTask.getStatus() == AsyncTask.Status.RUNNING)
return;
mTask = new GetRequestTokenTask(this);
AsyncTaskUtils.executeTask(mTask);
}
private void loadUrl(final String url) {
if (mWebView == null) return;
mWebView.loadUrl(url);
}
private String readOAuthPin(final String html) {
try {
return OAuthPasswordAuthenticator.readOAuthPINFromHtml(new StringReader(html));
} catch (final XmlPullParserException | IOException e) {
e.printStackTrace();
}
return null;
}
private void setLoadProgressShown(final boolean shown) {
mProgressContainer.setVisibility(shown ? View.VISIBLE : View.GONE);
}
private void setRequestToken(final RequestToken token) {
mRequestToken = token;
}
static class AuthorizationWebViewClient extends WebViewClient {
private final BrowserSignInActivity mActivity;
private ImageButton closeButton;
private WebView webView;
AuthorizationWebViewClient(final BrowserSignInActivity activity) {
mActivity = activity;
closeButton = (ImageButton) activity.findViewById(R.id.close_button);
android.view.ViewGroup.LayoutParams params = closeButton.getLayoutParams();
params.height = 60;
params.width = 60;
closeButton.setLayoutParams(params);
webView = (WebView)activity.findViewById(R.id.webview);
closeButton.setVisibility(View.VISIBLE);
addListenerOnButton();
}
@Override
public void onPageFinished(final WebView view, final String url) {
super.onPageFinished(view, url);
Log.d("TwitterBrowserSignIn", "WebView finished loading page " + url + " " + view.getTitle());
final Uri uri = Uri.parse(url);
if (url.equals(CANCEL_PAGE) && uri.getQuery() == null) {
searchCancelText(view);
}
view.loadUrl(INJECT_CONTENT);
mActivity.setLoadProgressShown(false);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public void searchCancelText(WebView view) {
view.setFindListener(new FindListener() {
@Override
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
if (numberOfMatches > 0) {
mActivity.finish();
}
}
});
view.findAllAsync(CANCEL_TEXT);
}
@Override
public void onPageStarted(final WebView view, final String url, final Bitmap favicon) {
super.onPageStarted(view, url, favicon);
mActivity.setLoadProgressShown(true);
}
@Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
}
@Override
public void onReceivedError(final WebView view, final int errorCode, final String description,
final String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
Log.d("TwitterBrowserSignIn", "Received error for " + description + " " + failingUrl);
Toast.makeText(mActivity, R.string.error_occurred, Toast.LENGTH_SHORT).show();
mActivity.finish();
}
@Override
public void onReceivedSslError(final WebView view, @NonNull final SslErrorHandler handler, final SslError error) {
if (mActivity.mPreferences.getBoolean(KEY_IGNORE_SSL_ERROR, false)) {
handler.proceed();
} else {
handler.cancel();
}
}
public void closeWebView() {
if (webView != null) {
mActivity.finish();
}
}
public void addListenerOnButton() {
closeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
closeWebView();
}
});
}
@Override
public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
final Uri uri = Uri.parse(url);
if (uri.toString().toLowerCase().contains("mobile.twitter.com/welcome/interests") ||
url.toLowerCase().equals("https://mobile.twitter.com") ||
url.startsWith(OAUTH_CALLBACK_URL)) {
final String oauth_verifier = uri.getQueryParameter(EXTRA_OAUTH_VERIFIER);
final RequestToken request_token = mActivity.mRequestToken;
if (oauth_verifier != null && request_token != null) {
final Intent intent = new Intent();
intent.putExtra(EXTRA_OAUTH_VERIFIER, oauth_verifier);
intent.putExtra(EXTRA_REQUEST_TOKEN, request_token.getToken());
intent.putExtra(EXTRA_REQUEST_TOKEN_SECRET, request_token.getTokenSecret());
mActivity.setResult(RESULT_OK, intent);
mActivity.finish();
}
return true;
}
return false;
}
}
static class GetRequestTokenTask extends AsyncTask<Object, Object, RequestToken> {
private final String mConsumerKey, mConsumerSecret;
private final FiretweetApplication mApplication;
private final SharedPreferences mPreferences;
private final BrowserSignInActivity mActivity;
public GetRequestTokenTask(final BrowserSignInActivity activity) {
mActivity = activity;
mApplication = FiretweetApplication.getInstance(activity);
mPreferences = activity.getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE);
final Intent intent = activity.getIntent();
mConsumerKey = intent.getStringExtra(Accounts.CONSUMER_KEY);
mConsumerSecret = intent.getStringExtra(Accounts.CONSUMER_SECRET);
}
@Override
protected RequestToken doInBackground(final Object... params) {
final ConfigurationBuilder cb = new ConfigurationBuilder();
final boolean enable_gzip_compressing = mPreferences.getBoolean(KEY_GZIP_COMPRESSING, false);
final boolean ignore_ssl_error = mPreferences.getBoolean(KEY_IGNORE_SSL_ERROR, false);
final boolean enable_proxy = mPreferences.getBoolean(KEY_ENABLE_PROXY, false);
final String consumerKey = getNonEmptyString(mPreferences, KEY_CONSUMER_KEY, TWITTER_CONSUMER_KEY_3);
final String consumerSecret = getNonEmptyString(mPreferences, KEY_CONSUMER_SECRET,
TWITTER_CONSUMER_SECRET_3);
cb.setHostAddressResolverFactory(new FiretweetHostResolverFactory(mApplication));
cb.setHttpClientFactory(new OkHttpClientFactory(mApplication));
if (TwitterContentUtils.isOfficialKey(mActivity, consumerKey, consumerSecret)) {
Utils.setMockOfficialUserAgent(mActivity, cb);
} else {
Utils.setUserAgent(mActivity, cb);
}
cb.setRestBaseURL(DEFAULT_REST_BASE_URL);
cb.setOAuthBaseURL(DEFAULT_OAUTH_BASE_URL);
cb.setSigningRestBaseURL(DEFAULT_SIGNING_REST_BASE_URL);
cb.setSigningOAuthBaseURL(DEFAULT_SIGNING_OAUTH_BASE_URL);
if (!isEmpty(mConsumerKey) && !isEmpty(mConsumerSecret)) {
cb.setOAuthConsumerKey(mConsumerKey);
cb.setOAuthConsumerSecret(mConsumerSecret);
} else {
cb.setOAuthConsumerKey(consumerKey);
cb.setOAuthConsumerSecret(consumerSecret);
}
cb.setGZIPEnabled(enable_gzip_compressing);
cb.setIgnoreSSLError(ignore_ssl_error);
Log.d("TwitterBrowserSignIn", "Enabled proxy configuration");
try {
final Twitter twitter = new TwitterFactory(cb.build()).getInstance();
return twitter.getOAuthRequestToken(OAUTH_CALLBACK_OOB);
} catch (final TwitterException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(final RequestToken data) {
mActivity.setLoadProgressShown(false);
mActivity.setRequestToken(data);
if (data == null) {
if (!mActivity.isFinishing()) {
Toast.makeText(mActivity, R.string.error_occurred, Toast.LENGTH_SHORT).show();
mActivity.finish();
}
return;
}
if (action == "sign_up") {
mActivity.loadUrl(SIGNUP_URL + data.getToken() + "&context=oauth");
} else {
mActivity.loadUrl(data.getAuthorizationURL() + "&force_login=true");
}
}
@Override
protected void onPreExecute() {
mActivity.setLoadProgressShown(true);
}
}
static class InjectorJavaScriptInterface {
private final BrowserSignInActivity mActivity;
InjectorJavaScriptInterface(final BrowserSignInActivity activity) {
mActivity = activity;
}
@JavascriptInterface
public void processHTML(final String html) {
final String oauthVerifier = mActivity.readOAuthPin(html);
final RequestToken requestToken = mActivity.mRequestToken;
if (oauthVerifier != null && requestToken != null) {
final Intent intent = new Intent();
intent.putExtra(EXTRA_OAUTH_VERIFIER, oauthVerifier);
intent.putExtra(EXTRA_REQUEST_TOKEN, requestToken.getToken());
intent.putExtra(EXTRA_REQUEST_TOKEN_SECRET, requestToken.getTokenSecret());
mActivity.setResult(RESULT_OK, intent);
mActivity.finish();
}
}
}
}