/*
* Sonet - Android Social Networking Widget
* Copyright (C) 2009 Bryan Emmanuel
*
* 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/>.
*
* Bryan Emmanuel piusvelte@gmail.com
*/
package com.piusvelte.sonet;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.Toast;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.piusvelte.sonet.fragment.ItemsDialogFragment;
import com.piusvelte.sonet.fragment.RssNameDialogFragment;
import com.piusvelte.sonet.fragment.RssUrlDialogFragment;
import com.piusvelte.sonet.loader.AddAccountLoader;
import com.piusvelte.sonet.loader.MemberAuthenticationLoader;
import com.piusvelte.sonet.loader.OAuthLoginLoader;
import com.piusvelte.sonet.provider.Accounts;
import com.piusvelte.sonet.social.Client;
import static com.piusvelte.sonet.Sonet.GOOGLEPLUS;
import static com.piusvelte.sonet.Sonet.PINTEREST;
import static com.piusvelte.sonet.Sonet.RSS;
import static com.piusvelte.sonet.Sonet.SMS;
public class OAuthLogin extends BaseActivity implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG = "OAuthLogin";
private static final String DIALOG_RSS_URL = "dialog:rss_url";
private static final String DIALOG_RSS_NAME = "dialog:rss_name";
private static final String DIALOG_ADD_SYSTEM_ACCOUNT = "dialog:add_system_account";
private static final int REQUEST_RSS_URL = 0;
private static final int REQUEST_RSS_NAME = 1;
private static final int REQUEST_ADD_SYSTEM_ACCOUNT = 2;
private static final int REQUEST_RESOLVE_CLIENT_CONNECTION_ERROR = 3;
private int mWidgetId;
private long mAccountId;
private String mServiceName = "unknown";
private SonetWebView mSonetWebView;
private View mLoadingView;
@Nullable
private OAuthLoginLoader.OAuthLoginLoaderResult mOAuthLoginLoaderResult;
private static final int LOADER_OAUTH_LOGIN = 0;
private static final int LOADER_SMS = 1;
private static final int LOADER_PINTEREST = 2;
private static final int LOADER_MEMBER_AUTHENTICATION = 3;
private static final int LOADER_ADD_ACCOUNT = 4;
private static final String LOADER_ARG_NETWORK = "network";
private static final String LOADER_ARG_AUTHENTICATED_URL = "authenticated_url";
private OAuthLoginLoaderCallbacks mOAuthLoginLoaderCallbacks = new OAuthLoginLoaderCallbacks(this);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login);
mLoadingView = findViewById(R.id.loading);
setResult(RESULT_CANCELED);
Intent intent = getIntent();
if (intent != null) {
Bundle extras = intent.getExtras();
if (extras != null) {
int service = extras.getInt(Accounts.SERVICE, Sonet.INVALID_SERVICE);
if (BuildConfig.DEBUG) {
Log.d(TAG, "start login for service=" + service);
}
mServiceName = Sonet.getServiceName(getResources(), service);
mWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
mAccountId = extras.getLong(Sonet.EXTRA_ACCOUNT_ID, Sonet.INVALID_ACCOUNT_ID);
mSonetWebView = new SonetWebView(this);
switch (service) {
case SMS:
mLoadingView.setVisibility(View.VISIBLE);
getSupportLoaderManager().initLoader(LOADER_SMS, null, this);
break;
case RSS:
RssUrlDialogFragment.newInstance(REQUEST_RSS_URL)
.show(getSupportFragmentManager(), DIALOG_RSS_URL);
break;
case PINTEREST:
mLoadingView.setVisibility(View.VISIBLE);
getSupportLoaderManager().initLoader(LOADER_PINTEREST, null, this);
break;
case GOOGLEPLUS:
AccountManager accountManager = AccountManager.get(getApplicationContext());
Account[] accounts = accountManager.getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE);
if (accounts != null && accounts.length > 0) {
String[] names = new String[accounts.length];
for (int i = 0; i < names.length; i++) {
names[i] = accounts[i].name;
}
mOAuthLoginLoaderResult = new OAuthLoginLoader.OAuthLoginLoaderResult(this, service);
ItemsDialogFragment.newInstance(names, REQUEST_ADD_SYSTEM_ACCOUNT, R.string.select_account)
.show(getSupportFragmentManager(), DIALOG_ADD_SYSTEM_ACCOUNT);
}
break;
default: {
mLoadingView.setVisibility(View.VISIBLE);
Bundle args = new Bundle();
args.putInt(LOADER_ARG_NETWORK, service);
getSupportLoaderManager().initLoader(LOADER_OAUTH_LOGIN, args, mOAuthLoginLoaderCallbacks);
break;
}
}
}
}
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_SMS:
return new CursorLoader(this, Accounts.getContentUri(this), new String[] { Accounts._ID }, Accounts.SERVICE + "=?",
new String[] { Integer.toString(SMS) }, null);
case LOADER_PINTEREST:
return new CursorLoader(this, Accounts.getContentUri(this), new String[] { Accounts._ID }, Accounts.SERVICE + "=?",
new String[] { Integer.toString(PINTEREST) }, null);
default:
return null;
}
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
switch (loader.getId()) {
case LOADER_SMS:
mLoadingView.setVisibility(View.GONE);
if (data != null) {
if (data.moveToFirst()) {
(Toast.makeText(OAuthLogin.this, "SMS has already been added.", Toast.LENGTH_LONG)).show();
} else {
addAccount(getResources().getStringArray(R.array.service_entries)[SMS], null, null, 0, SMS, null);
}
}
finish();
break;
case LOADER_PINTEREST:
mLoadingView.setVisibility(View.GONE);
if (data != null) {
if (data.moveToFirst()) {
(Toast.makeText(OAuthLogin.this, "Pinterest has already been added.", Toast.LENGTH_LONG)).show();
} else {
Toast.makeText(OAuthLogin.this, "Pinterest currently allows only public, non-authenticated viewing.", Toast.LENGTH_LONG)
.show();
String[] values = getResources().getStringArray(R.array.service_values);
String[] entries = getResources().getStringArray(R.array.service_entries);
for (int i = 0, l = values.length; i < l; i++) {
if (Integer.toString(PINTEREST).equals(values[i])) {
addAccount(entries[i], null, null, 0, PINTEREST, null);
break;
}
}
}
}
finish();
break;
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// NO-OP
}
private void addAccount(String username, String token, String secret, int expiry, int service, String sid) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "add account; username=" + username + ", service=" + service + ", sid=" + sid + ", mAccountId=" + mAccountId);
}
getSupportLoaderManager().restartLoader(LOADER_ADD_ACCOUNT,
null,
new AddAccountsCallbacks(LOADER_ADD_ACCOUNT,
this,
username,
token,
secret,
expiry,
service,
sid,
mAccountId,
mWidgetId));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_RESOLVE_CLIENT_CONNECTION_ERROR:
if (resultCode == RESULT_OK) {
mLoadingView.setVisibility(View.VISIBLE);
Bundle args = new Bundle();
args.putString(LOADER_ARG_AUTHENTICATED_URL, null);
getSupportLoaderManager().restartLoader(LOADER_MEMBER_AUTHENTICATION,
args,
new MemberAuthenticationLoaderCallbacks(this, mOAuthLoginLoaderResult));
} else {
// TODO toast
finish();
}
break;
default:
super.onActivityResult(requestCode, resultCode, data);
break;
}
}
@Override
public void onResult(int requestCode, int result, Intent data) {
switch (requestCode) {
case REQUEST_RSS_URL:
if (result == RESULT_OK) {
RssNameDialogFragment.newInstance(REQUEST_RSS_NAME, RssUrlDialogFragment.getUrl(data, null))
.show(getSupportFragmentManager(), DIALOG_RSS_NAME);
} else {
finish();
}
break;
case REQUEST_RSS_NAME:
if (result == RESULT_OK) {
String url = RssNameDialogFragment.getUrl(data, null);
String name = RssNameDialogFragment.getName(data, null);
addAccount(name, null, null, 0, RSS, url);
}
finish();
break;
case REQUEST_ADD_SYSTEM_ACCOUNT:
if (result == Activity.RESULT_OK) {
// add the account and refresh
String account = ItemsDialogFragment.getItems(data)[ItemsDialogFragment.getWhich(data, 0)];
// set this on the result
mOAuthLoginLoaderResult.client = Client.Builder.from(mOAuthLoginLoaderResult.client)
.setAccount(account)
.build();
mLoadingView.setVisibility(View.VISIBLE);
Bundle args = new Bundle();
// this isn't typically expected to be null, but for system accounts, there is no url
args.putString(LOADER_ARG_AUTHENTICATED_URL, null);
getSupportLoaderManager().restartLoader(LOADER_MEMBER_AUTHENTICATION,
args,
new MemberAuthenticationLoaderCallbacks(this, mOAuthLoginLoaderResult));
} else {
finish();
}
break;
}
}
private static class SonetWebView {
private OAuthLogin mOAuthLogin;
private WebView mWebView;
public SonetWebView(OAuthLogin oAuthLogin) {
mOAuthLogin = oAuthLogin;
mWebView = new WebView(mOAuthLogin);
((FrameLayout) mOAuthLogin.findViewById(R.id.webview_container)).addView(mWebView);
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (!TextUtils.isEmpty(url) && url.startsWith(mOAuthLogin.mOAuthLoginLoaderResult.client.getCallbackUrl())) {
mOAuthLogin.mLoadingView.setVisibility(View.VISIBLE);
Bundle args = new Bundle();
args.putString(LOADER_ARG_AUTHENTICATED_URL, url);
mOAuthLogin.getSupportLoaderManager().restartLoader(LOADER_MEMBER_AUTHENTICATION,
args,
new MemberAuthenticationLoaderCallbacks(mOAuthLogin, mOAuthLogin.mOAuthLoginLoaderResult));
return true;
}
return false;
}
});
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDefaultTextEncodingName("UTF-8");
}
public void open(String url) {
if (url != null) {
mWebView.loadUrl(url);
} else {
mOAuthLogin.finish();
}
}
}
private void setOAuthLoginLoaderResult(OAuthLoginLoader.OAuthLoginLoaderResult data) {
mLoadingView.setVisibility(View.GONE);
if (data != null) {
mOAuthLoginLoaderResult = data;
if (!TextUtils.isEmpty(mOAuthLoginLoaderResult.authUrl)) {
mSonetWebView.open(mOAuthLoginLoaderResult.authUrl);
} else {
Toast.makeText(OAuthLogin.this, String.format(getString(R.string.oauth_error), mServiceName), Toast.LENGTH_LONG).show();
finish();
}
} else {
Toast.makeText(OAuthLogin.this, String.format(getString(R.string.oauth_error), mServiceName), Toast.LENGTH_LONG).show();
finish();
}
}
private void setMemberAuthentication(Client.MemberAuthentication memberAuthentication) {
mLoadingView.setVisibility(View.GONE);
if (memberAuthentication != null) {
addAccount(memberAuthentication.username,
memberAuthentication.token,
memberAuthentication.secret,
memberAuthentication.expiry,
memberAuthentication.network,
memberAuthentication.id);
} else {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Client.MemberAuthentication not loaded");
}
if (mOAuthLoginLoaderResult == null
|| mOAuthLoginLoaderResult.client == null
|| !mOAuthLoginLoaderResult.client.hasConnectionError()
|| !mOAuthLoginLoaderResult.client.resolveConnectionError(this, REQUEST_RESOLVE_CLIENT_CONNECTION_ERROR)) {
// TODO toast
finish();
}
}
}
private static class OAuthLoginLoaderCallbacks implements LoaderManager.LoaderCallbacks<OAuthLoginLoader.OAuthLoginLoaderResult> {
@NonNull
private OAuthLogin mOAuthLogin;
OAuthLoginLoaderCallbacks(@NonNull OAuthLogin OAuthLogin) {
mOAuthLogin = OAuthLogin;
}
@Override
public Loader<OAuthLoginLoader.OAuthLoginLoaderResult> onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_OAUTH_LOGIN:
return new OAuthLoginLoader(mOAuthLogin, args.getInt(LOADER_ARG_NETWORK));
default:
return null;
}
}
@Override
public void onLoadFinished(Loader<OAuthLoginLoader.OAuthLoginLoaderResult> loader, OAuthLoginLoader.OAuthLoginLoaderResult data) {
switch (loader.getId()) {
case LOADER_OAUTH_LOGIN:
mOAuthLogin.setOAuthLoginLoaderResult(data);
break;
}
}
@Override
public void onLoaderReset(Loader<OAuthLoginLoader.OAuthLoginLoaderResult> loader) {
// NO-OP
}
}
private static class MemberAuthenticationLoaderCallbacks implements LoaderManager.LoaderCallbacks<Client.MemberAuthentication> {
@NonNull
private OAuthLogin mOAuthLogin;
@NonNull
private OAuthLoginLoader.OAuthLoginLoaderResult mOAuthLoginLoaderResult;
MemberAuthenticationLoaderCallbacks(@NonNull OAuthLogin oAuthLogin, @NonNull OAuthLoginLoader.OAuthLoginLoaderResult oAuthLoginLoaderResult) {
mOAuthLogin = oAuthLogin;
mOAuthLoginLoaderResult = oAuthLoginLoaderResult;
}
@Override
public Loader<Client.MemberAuthentication> onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_MEMBER_AUTHENTICATION:
return new MemberAuthenticationLoader(mOAuthLogin,
mOAuthLoginLoaderResult,
args.getString(LOADER_ARG_AUTHENTICATED_URL));
default:
return null;
}
}
@Override
public void onLoadFinished(Loader<Client.MemberAuthentication> loader, Client.MemberAuthentication data) {
switch (loader.getId()) {
case LOADER_MEMBER_AUTHENTICATION:
mOAuthLogin.setMemberAuthentication(data);
break;
}
}
@Override
public void onLoaderReset(Loader<Client.MemberAuthentication> loader) {
// NO-OP
}
}
private static class AddAccountsCallbacks implements LoaderManager.LoaderCallbacks<Boolean> {
private int mLoaderId;
private OAuthLogin mOAuthLogin;
private String mUsername;
private String mToken;
private String mSecret;
private int mExpiry;
private int mService;
private String mSid;
private long mAccountId;
private int mAppWidgetId;
public AddAccountsCallbacks(int loaderId,
OAuthLogin OAuthLogin,
String username,
String token,
String secret,
int expiry,
int service,
String sid,
long accountId,
int appWidgetId) {
mLoaderId = loaderId;
mOAuthLogin = OAuthLogin;
mUsername = username;
mToken = token;
mSecret = secret;
mExpiry = expiry;
mService = service;
mSid = sid;
mAccountId = accountId;
mAppWidgetId = appWidgetId;
}
@Override
public Loader<Boolean> onCreateLoader(int id, Bundle args) {
if (id == mLoaderId) {
return new AddAccountLoader(mOAuthLogin, mUsername, mToken, mSecret, mExpiry, mService, mSid, mAccountId, mAppWidgetId);
}
return null;
}
@Override
public void onLoadFinished(Loader<Boolean> loader, Boolean data) {
if (loader.getId() == mLoaderId) {
if (Boolean.TRUE.equals(data)) {
mOAuthLogin.setResult(RESULT_OK);
} else {
Toast.makeText(mOAuthLogin, "oops, something went wrong", Toast.LENGTH_SHORT).show();
}
mOAuthLogin.finish();
}
}
@Override
public void onLoaderReset(Loader<Boolean> loader) {
// NO-OP
}
}
}