Java tutorial
/* * Copyright (C) 2014 yvolk (Yuri Volkov), http://yurivolkov.com * Copyright (C) 2010 Brion N. Emde, "BLOA" example, http://github.com/brione/Brion-Learns-OAuth * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.andstatus.app.account; import android.accounts.AccountManager; import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import oauth.signpost.OAuth; import oauth.signpost.OAuthConsumer; import oauth.signpost.exception.OAuthCommunicationException; import oauth.signpost.exception.OAuthExpectationFailedException; import oauth.signpost.exception.OAuthMessageSignerException; import oauth.signpost.exception.OAuthNotAuthorizedException; import org.andstatus.app.ActivityRequestCode; import org.andstatus.app.HelpActivity; import org.andstatus.app.IntentExtra; import org.andstatus.app.R; import org.andstatus.app.TimelineActivity; import org.andstatus.app.account.MyAccount.CredentialsVerificationStatus; import org.andstatus.app.context.MyContextHolder; import org.andstatus.app.context.MySettingsActivity; import org.andstatus.app.context.MyPreferences; import org.andstatus.app.net.http.ConnectionException; import org.andstatus.app.net.http.HttpConnection; import org.andstatus.app.origin.Origin; import org.andstatus.app.origin.PersistentOriginList; import org.andstatus.app.service.MyServiceManager; import org.andstatus.app.service.MyServiceState; import org.andstatus.app.util.DialogFactory; import org.andstatus.app.util.MyLog; import org.andstatus.app.util.TriState; import org.json.JSONException; import org.json.JSONObject; import android.view.*; /** * Add new or edit existing account * * @author yvolk@yurivolkov.com */ public class AccountSettingsActivity extends Activity { private static final String TAG = AccountSettingsActivity.class.getSimpleName(); private static final String FRAGMENT_TAG = AccountSettingsFragment.class.getSimpleName(); /** * This is single list of (in fact, enums...) of Message/Dialog IDs */ private static final int MSG_NONE = 1; private static final int MSG_ACCOUNT_VALID = 2; private static final int MSG_ACCOUNT_INVALID = 3; private static final int MSG_CONNECTION_EXCEPTION = 5; private static final int MSG_CREDENTIALS_OF_OTHER_USER = 7; /** * We are going to finish/restart this Activity */ private boolean mIsFinishing = false; private boolean overrideBackActivity = false; private StateOfAccountChangeProcess state = null; private StringBuilder mLatestErrorMessage = new StringBuilder(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyContextHolder.initialize(this, this); MyContextHolder.upgradeIfNeeded(this); if (HelpActivity.startFromActivity(this)) { return; } MyPreferences.loadTheme(this); getFragmentManager().beginTransaction() .replace(android.R.id.content, new AccountSettingsFragment(), FRAGMENT_TAG).commit(); } private View findFramentViewById(int id) { return getFragmentManager().findFragmentByTag(FRAGMENT_TAG).getView().findViewById(id); } protected boolean selectOrigin() { Intent i = new Intent(AccountSettingsActivity.this, PersistentOriginList.class); i.setAction(Intent.ACTION_INSERT); startActivityForResult(i, ActivityRequestCode.SELECT_ORIGIN.id); return true; } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); restoreState(intent, "onNewIntent"); } /** * Restore previous state and set the Activity mode depending on input (Intent). * We should decide if we should use the stored state or a newly created one * @param intent * @param calledFrom - for logging only * @param savedInstanceState */ protected void restoreState(Intent intent, String calledFrom) { String message = ""; if (state == null) { state = StateOfAccountChangeProcess.fromStoredState(); message += (state.restored ? "Old state restored; " : "No previous state; "); } else { message += "State existed and " + (state.restored ? "was restored earlier; " : "was not restored earlier; "); } StateOfAccountChangeProcess newState = StateOfAccountChangeProcess.fromIntent(intent); if (state.actionCompleted || newState.useThisState) { message += "New state; "; state = newState; if (state.originShouldBeSelected) { selectOrigin(); } else if (state.accountShouldBeSelected) { AccountSelector.selectAccount(this, 0, ActivityRequestCode.SELECT_ACCOUNT); message += "Select account; "; } message += "action=" + state.getAccountAction() + "; "; updateScreen(); } if (state.authenticatiorResponse != null) { message += "authenticatiorResponse; "; } MyLog.v(this, "setState from " + calledFrom + "; " + message + "intent=" + intent.toUri(0)); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (ActivityRequestCode.fromId(requestCode)) { case SELECT_ACCOUNT: onAccountSelected(resultCode, data); break; case SELECT_ORIGIN: onOriginSelected(resultCode, data); break; default: super.onActivityResult(requestCode, resultCode, data); break; } } private void onAccountSelected(int resultCode, Intent data) { if (resultCode == RESULT_OK) { state.builder = MyAccount.Builder.newOrExistingFromAccountName(MyContextHolder.get(), data.getStringExtra(IntentExtra.EXTRA_ACCOUNT_NAME.key), TriState.UNKNOWN); if (!state.builder.isPersistent()) { mIsFinishing = true; } } else { mIsFinishing = true; } if (!mIsFinishing) { MyLog.v(this, "Switching to the selected account"); MyContextHolder.get().persistentAccounts().setCurrentAccount(state.builder.getAccount()); state.setAccountAction(Intent.ACTION_EDIT); updateScreen(); } else { MyLog.v(this, "No account supplied, finishing"); finish(); } } private void onOriginSelected(int resultCode, Intent data) { Origin origin = Origin.Builder.buildUnknown(); if (resultCode == RESULT_OK) { origin = MyContextHolder.get().persistentOrigins() .fromName(data.getStringExtra(IntentExtra.EXTRA_ORIGIN_NAME.key)); if (origin.isPersistent() && state.getAccount().getOriginId() != origin.getId()) { // If we have changed the System, we should recreate the Account state.builder = MyAccount.Builder.newOrExistingFromAccountName(MyContextHolder.get(), AccountName.fromOriginAndUserNames(MyContextHolder.get(), origin.getName(), state.getAccount().getUsername()).toString(), TriState.fromBoolean(state.getAccount().isOAuth())); updateScreen(); } } if (!origin.isPersistent()) { closeAndGoBack(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.account_settings, menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem item = menu.findItem(R.id.remove_account_menu_id); item.setEnabled(state.builder.isPersistent()); item.setVisible(state.builder.isPersistent()); return super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: closeAndGoBack(); return true; case R.id.preferences_menu_id: startMyPreferenceActivity(); break; case R.id.remove_account_menu_id: DialogFactory.showYesCancelDialog(getFragmentManager().findFragmentByTag(FRAGMENT_TAG), R.string.remove_account_dialog_title, R.string.remove_account_dialog_text, ActivityRequestCode.REMOVE_ACCOUNT); default: break; } return super.onOptionsItemSelected(item); } private void startMyPreferenceActivity() { finish(); startActivity(new Intent(this, MySettingsActivity.class)); } private void updateScreen() { showTitle(); showErrors(); showOrigin(); showUsername(); showPassword(); showAccountState(); showAddAccountButton(); showVerifyCredentialsButton(); showDefaultAccountCheckbox(); } private void showTitle() { MyAccount ma = state.getAccount(); String title = getText(R.string.account_settings_activity_title).toString(); if (ma.isValid()) { title += " - " + ma.getAccountName(); } getActionBar().setTitle(title); } private void showErrors() { showTextView(R.id.latest_error_label, R.string.latest_error_label, mLatestErrorMessage.length() > 0); showTextView(R.id.latest_error, mLatestErrorMessage, mLatestErrorMessage.length() > 0); } private void showOrigin() { MyAccount ma = state.getAccount(); TextView view = (TextView) findFramentViewById(R.id.origin_name); view.setText(this.getText(R.string.title_preference_origin_system).toString() .replace("{0}", ma.getOrigin().getName()) .replace("{1}", ma.getOrigin().getOriginType().getTitle())); } private void showUsername() { MyAccount ma = state.getAccount(); showTextView(R.id.username_label, ma.alternativeTermForResourceId(R.string.title_preference_username), state.builder.isPersistent() || ma.isUsernameNeededToStartAddingNewAccount()); EditText usernameEditable = (EditText) findFramentViewById(R.id.username); if (state.builder.isPersistent() || !ma.isUsernameNeededToStartAddingNewAccount()) { usernameEditable.setVisibility(View.GONE); } else { usernameEditable.setVisibility(View.VISIBLE); usernameEditable.setHint(ma.alternativeTermForResourceId(R.string.summary_preference_username)); usernameEditable.addTextChangedListener(textWatcher); } if (ma.getUsername().compareTo(usernameEditable.getText().toString()) != 0) { usernameEditable.setText(ma.getUsername()); } showTextView(R.id.username_readonly, ma.getUsername(), state.builder.isPersistent()); } private TextWatcher textWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // Empty } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // Empty } @Override public void afterTextChanged(Editable s) { clearError(); } }; private void showPassword() { MyAccount ma = state.getAccount(); boolean isNeeded = ma.getConnection().isPasswordNeeded(); StringBuilder labelBuilder = new StringBuilder(); if (isNeeded) { labelBuilder.append(this.getText(R.string.summary_preference_password)); if (TextUtils.isEmpty(ma.getPassword())) { labelBuilder.append(": (" + this.getText(R.string.not_set) + ")"); } } showTextView(R.id.password_label, labelBuilder.toString(), isNeeded); EditText passwordEditable = (EditText) findFramentViewById(R.id.password); if (ma.getPassword().compareTo(passwordEditable.getText().toString()) != 0) { passwordEditable.setText(ma.getPassword()); } passwordEditable.setVisibility(isNeeded ? View.VISIBLE : View.GONE); passwordEditable.setEnabled(!ma.isValidAndVerified()); passwordEditable.addTextChangedListener(textWatcher); } private void showAccountState() { MyAccount ma = state.getAccount(); StringBuilder summary = null; if (state.builder.isPersistent()) { switch (ma.getCredentialsVerified()) { case SUCCEEDED: summary = new StringBuilder(this.getText(R.string.summary_preference_verify_credentials)); break; default: if (state.builder.isPersistent()) { summary = new StringBuilder( this.getText(R.string.summary_preference_verify_credentials_failed)); } else { if (ma.isOAuth()) { summary = new StringBuilder(this.getText(R.string.summary_preference_add_account_oauth)); } else { summary = new StringBuilder(this.getText(R.string.summary_preference_add_account_basic)); } } break; } } ((TextView) findFramentViewById(R.id.account_state)).setText(summary); } private void showAddAccountButton() { TextView textView = showTextView(R.id.add_account, null, !state.builder.isPersistent()); textView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { clearError(); updateChangedFields(); updateScreen(); MyAccount ma = state.getAccount(); CharSequence error = ""; boolean addAccountEnabled = !ma.isUsernameNeededToStartAddingNewAccount() || ma.isUsernameValid(); if (addAccountEnabled) { if (!ma.isOAuth() && !ma.getCredentialsPresent()) { addAccountEnabled = false; error = getText(R.string.title_preference_password); } } else { error = getText(ma.alternativeTermForResourceId(R.string.title_preference_username)); } if (addAccountEnabled) { verifyCredentials(true); } else { appendError(getText(R.string.error_invalid_value) + ": " + error); } } }); } private void clearError() { if (mLatestErrorMessage.length() > 0) { mLatestErrorMessage.setLength(0); showErrors(); } } private void showVerifyCredentialsButton() { TextView textView = showTextView(R.id.verify_credentials, state.getAccount().isValidAndVerified() ? R.string.title_preference_verify_credentials : R.string.title_preference_verify_credentials_failed, state.builder.isPersistent()); textView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { clearError(); updateChangedFields(); updateScreen(); verifyCredentials(true); } }); } private void showDefaultAccountCheckbox() { MyAccount ma = state.getAccount(); boolean isDefaultAccount = ma.getAccountName() .equals(MyContextHolder.get().persistentAccounts().getDefaultAccountName()); CheckBox checkBox = (CheckBox) findFramentViewById(R.id.is_default_account); checkBox.setVisibility(state.builder.isPersistent() ? View.VISIBLE : View.GONE); checkBox.setEnabled(!isDefaultAccount); checkBox.setChecked(isDefaultAccount); checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { MyContextHolder.get().persistentAccounts().setDefaultAccount(state.getAccount()); updateScreen(); } } }); } private TextView showTextView(int textViewId, int textResourceId, boolean isVisible) { return showTextView(textViewId, textResourceId == 0 ? null : getText(textResourceId), isVisible); } private TextView showTextView(int textViewId, CharSequence text, boolean isVisible) { TextView textView = (TextView) findFramentViewById(textViewId); if (!TextUtils.isEmpty(text)) { textView.setText(text); } textView.setVisibility(isVisible ? View.VISIBLE : View.GONE); return textView; } @Override protected void onResume() { super.onResume(); MyContextHolder.get().setInForeground(true); MyContextHolder.initialize(this, this); MyServiceManager.setServiceUnavailable(); MyServiceManager.stopService(); updateScreen(); Uri uri = getIntent().getData(); if (uri != null) { if (MyLog.isLoggable(TAG, MyLog.DEBUG)) { MyLog.d(TAG, "uri=" + uri.toString()); } if (HttpConnection.CALLBACK_URI.getScheme().equals(uri.getScheme())) { // To prevent repeating of this task getIntent().setData(null); // This activity was started by Twitter ("Service Provider") // so start second step of OAuth Authentication process new OAuthAcquireAccessTokenTask().execute(uri); // and return back to default screen overrideBackActivity = true; } } } /** * Verify credentials * * @param reVerify true - Verify only if we didn't do this yet */ private void verifyCredentials(boolean reVerify) { MyAccount ma = state.getAccount(); if (reVerify || ma.getCredentialsVerified() == CredentialsVerificationStatus.NEVER) { MyServiceManager.setServiceUnavailable(); MyServiceState state2 = MyServiceManager.getServiceState(); if (state2 != MyServiceState.STOPPED) { MyServiceManager.stopService(); if (state2 != MyServiceState.UNKNOWN) { appendError(getText(R.string.system_is_busy_try_later) + " (" + state2 + ")"); return; } } if (ma.getCredentialsPresent()) { // Credentials are present, so we may verify them // This is needed even for OAuth - to know Twitter Username new VerifyCredentialsTask().execute(); } else { if (ma.isOAuth() && reVerify) { // Credentials are not present, // so start asynchronous OAuth Authentication process if (!ma.areClientKeysPresent()) { new OAuthRegisterClientTask().execute(); } else { new OAuthAcquireRequestTokenTask().execute(); // and return back to default screen overrideBackActivity = true; } } } } } private void updateChangedFields() { if (!state.builder.isPersistent()) { EditText usernameEditable = (EditText) findFramentViewById(R.id.username); String username = usernameEditable.getText().toString(); if (username.compareTo(state.getAccount().getUsername()) != 0) { boolean isOAuth = state.getAccount().isOAuth(); String originName = state.getAccount().getOrigin().getName(); state.builder = MyAccount.Builder.newOrExistingFromAccountName(MyContextHolder.get(), AccountName.fromOriginAndUserNames(MyContextHolder.get(), originName, username).toString(), TriState.fromBoolean(isOAuth)); } } EditText passwordEditable = (EditText) findFramentViewById(R.id.password); if (state.getAccount().getPassword().compareTo(passwordEditable.getText().toString()) != 0) { state.builder.setPassword(passwordEditable.getText().toString()); } } private void appendError(CharSequence errorMessage) { if (TextUtils.isEmpty(errorMessage)) { return; } if (mLatestErrorMessage.length() > 0) { mLatestErrorMessage.append("/n"); } mLatestErrorMessage.append(errorMessage); showErrors(); } @Override protected void onPause() { super.onPause(); state.save(); if (mIsFinishing) { MyContextHolder.release(); if (overrideBackActivity) { returnToOurActivity(); } } MyContextHolder.get().setInForeground(false); } private void returnToOurActivity() { Class<? extends Activity> ourActivity; MyContextHolder.initialize(this, this); if (MyContextHolder.get().persistentAccounts().size() > 1) { ourActivity = MySettingsActivity.class; } else { ourActivity = TimelineActivity.class; } MyLog.v(this, "Returning to " + ourActivity.getSimpleName()); Intent i = new Intent(this, ourActivity); // On modifying activity back stack see http://stackoverflow.com/questions/11366700/modification-of-the-back-stack-in-android i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(i); } /** * This semaphore helps to avoid ripple effect: changes in MyAccount cause * changes in this activity ... */ private boolean somethingIsBeingProcessed = false; @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { closeAndGoBack(); return true; } return super.onKeyDown(keyCode, event); } /** * Mark the action completed, close this activity and go back to the proper screen. * Return result to the caller if necessary. * See also {@link com.android.email.activity.setup.AccountSetupBasics.finish} * * @return */ private void closeAndGoBack() { // Explicitly save MyAccount only on "Back key" state.builder.save(); String message = ""; state.actionCompleted = true; overrideBackActivity = true; if (state.authenticatiorResponse != null) { // We should return result back to AccountManager overrideBackActivity = false; if (state.actionSucceeded) { if (state.builder.isPersistent()) { // Pass the new/edited account back to the account manager Bundle result = new Bundle(); result.putString(AccountManager.KEY_ACCOUNT_NAME, state.getAccount().getAccountName()); result.putString(AccountManager.KEY_ACCOUNT_TYPE, AuthenticatorService.ANDROID_ACCOUNT_TYPE); state.authenticatiorResponse.onResult(result); message += "authenticatiorResponse; account.name=" + state.getAccount().getAccountName() + "; "; } } else { state.authenticatiorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled"); } } // Forget old state state.forget(); if (!mIsFinishing) { MyLog.v(this, "finish: action=" + state.getAccountAction() + "; " + message); mIsFinishing = true; finish(); } } /** * Start system activity which allow to manage list of accounts * See <a href="https://groups.google.com/forum/?fromgroups#!topic/android-developers/RfrIb5V_Bpo">per account settings in Jelly Beans</a>. * For versions prior to Jelly Bean see <a href="http://stackoverflow.com/questions/3010103/android-how-to-create-intent-to-open-the-activity-that-displays-the-accounts"> * Android - How to Create Intent to open the activity that displays the Accounts & Sync settings? screen</a> */ public static void startManageExistingAccounts(android.content.Context context) { Intent intent; // TODO: Figure out more concrete Intent to the list of AndStatus accounts intent = new Intent(context, AccountSettingsActivity.class); context.startActivity(intent); } public static void startAddNewAccount(android.content.Context context) { Intent intent; intent = new Intent(context, AccountSettingsActivity.class); intent.setAction(Intent.ACTION_INSERT); context.startActivity(intent); } public StateOfAccountChangeProcess getState() { return state; } /** * Step 1 of 3 of the OAuth Authentication * Needed in case we don't have the AndStatus Client keys for the Microblogging system */ private class OAuthRegisterClientTask extends AsyncTask<Void, Void, JSONObject> { private ProgressDialog dlg; @Override protected void onPreExecute() { dlg = ProgressDialog.show(AccountSettingsActivity.this, getText(R.string.dialog_title_registering_client), getText(R.string.dialog_summary_registering_client), // duration indeterminate true, // not cancel-able false); } @Override protected JSONObject doInBackground(Void... arg0) { JSONObject jso = null; boolean requestSucceeded = false; String message = ""; String message2 = ""; try { state.builder.getOriginConfig(); if (!state.getAccount().areClientKeysPresent()) { state.builder.registerClient(); } requestSucceeded = state.getAccount().areClientKeysPresent(); } catch (ConnectionException e) { message = e.getMessage(); MyLog.e(this, e); } try { if (!requestSucceeded) { message2 = AccountSettingsActivity.this.getString(R.string.dialog_title_authentication_failed); if (message != null && message.length() > 0) { message2 = message2 + ": " + message; } MyLog.d(TAG, message2); } jso = new JSONObject(); jso.put("succeeded", requestSucceeded); jso.put("message", message2); } catch (JSONException e) { MyLog.e(this, e); } return jso; } // This is in the UI thread, so we can mess with the UI @Override protected void onPostExecute(JSONObject jso) { DialogFactory.dismissSafely(dlg); if (jso != null) { try { boolean succeeded = jso.getBoolean("succeeded"); String message = jso.getString("message"); if (succeeded) { String accountName = state.getAccount().getAccountName(); MyContextHolder.get().persistentAccounts().initialize(); state.builder = MyAccount.Builder.newOrExistingFromAccountName(MyContextHolder.get(), accountName, TriState.TRUE); updateScreen(); new OAuthAcquireRequestTokenTask().execute(); // and return back to default screen overrideBackActivity = true; } else { appendError(message); state.builder.setCredentialsVerificationStatus(CredentialsVerificationStatus.FAILED); updateScreen(); } } catch (JSONException e) { MyLog.e(this, e); } } } } /** * Task 2 of 3 required for OAuth Authentication. * See http://www.snipe.net/2009/07/writing-your-first-twitter-application-with-oauth/ * for good OAuth Authentication flow explanation. * * During this task: * 1. AndStatus ("Consumer") Requests "Request Token" from Twitter ("Service provider"), * 2. Waits for that Request Token * 3. Consumer directs User to the Service Provider: opens Twitter site in Internet Browser window * in order to Obtain User Authorization. * 4. This task ends. * * What will occur later: * 5. After User Authorized AndStatus in the Internet Browser, * Twitter site will redirect User back to * AndStatus and then the second OAuth task will start. * * @author yvolk@yurivolkov.com This code is based on "BLOA" example, * http://github.com/brione/Brion-Learns-OAuth yvolk: I had to move * this code from OAuthActivity here in order to be able to show * ProgressDialog and to get rid of any "Black blank screens" */ private class OAuthAcquireRequestTokenTask extends AsyncTask<Void, Void, JSONObject> { private ProgressDialog dlg; @Override protected void onPreExecute() { dlg = ProgressDialog.show(AccountSettingsActivity.this, getText(R.string.dialog_title_acquiring_a_request_token), getText(R.string.dialog_summary_acquiring_a_request_token), // indeterminate duration true, // not cancelable false); } @Override protected JSONObject doInBackground(Void... arg0) { JSONObject jso = null; boolean requestSucceeded = false; String message = ""; String message2 = ""; try { MyAccount ma = state.getAccount(); MyLog.v(this, "Retrieving request token for " + ma); OAuthConsumer consumer = state.getAccount().getOAuthConsumerAndProvider().getConsumer(); // This is really important. If you were able to register your // real callback Uri with Twitter, and not some fake Uri // like I registered when I wrote this example, you need to send // null as the callback Uri in this function call. Then // Twitter will correctly process your callback redirection String authUrl = state.getAccount().getOAuthConsumerAndProvider().getProvider() .retrieveRequestToken(consumer, HttpConnection.CALLBACK_URI.toString()); state.setRequestTokenWithSecret(consumer.getToken(), consumer.getTokenSecret()); // This is needed in order to complete the process after redirect // from the Browser to the same activity. state.actionCompleted = false; android.webkit.CookieManager cookieManager = android.webkit.CookieManager.getInstance(); cookieManager.removeAllCookie(); // Start Web view (looking just like Web Browser) Intent i = new Intent(AccountSettingsActivity.this, AccountSettingsWebActivity.class); i.putExtra(AccountSettingsWebActivity.EXTRA_URLTOOPEN, authUrl); AccountSettingsActivity.this.startActivity(i); requestSucceeded = true; } catch (OAuthMessageSignerException e) { message = e.getMessage(); MyLog.e(this, e); } catch (OAuthNotAuthorizedException e) { message = e.getMessage(); MyLog.e(this, e); } catch (OAuthExpectationFailedException e) { message = e.getMessage(); MyLog.e(this, e); } catch (OAuthCommunicationException e) { message = e.getMessage(); MyLog.e(this, e); } try { if (!requestSucceeded) { message2 = AccountSettingsActivity.this.getString(R.string.dialog_title_authentication_failed); if (message != null && message.length() > 0) { message2 = message2 + ": " + message; } MyLog.d(TAG, message2); state.builder.clearClientKeys(); } jso = new JSONObject(); jso.put("succeeded", requestSucceeded); jso.put("message", message2); } catch (JSONException e) { MyLog.i(this, e); } return jso; } // This is in the UI thread, so we can mess with the UI @Override protected void onPostExecute(JSONObject jso) { DialogFactory.dismissSafely(dlg); if (jso != null) { try { boolean succeeded = jso.getBoolean("succeeded"); String message = jso.getString("message"); if (succeeded) { // Finish this activity in order to start properly // after redirection from Browser // Because of initializations in onCreate... AccountSettingsActivity.this.finish(); } else { appendError(message); state.builder.setCredentialsVerificationStatus(CredentialsVerificationStatus.FAILED); updateScreen(); } } catch (JSONException e) { MyLog.e(this, e); } } } } /** * Task 3 of 3 required for OAuth Authentication. * * During this task: * 1. AndStatus ("Consumer") exchanges "Request Token", * obtained earlier from Twitter ("Service provider"), * for "Access Token". * 2. Stores the Access token for all future interactions with Twitter. * * @author yvolk@yurivolkov.com This code is based on "BLOA" example, * http://github.com/brione/Brion-Learns-OAuth yvolk: I had to move * this code from OAuthActivity here in order to be able to show * ProgressDialog and to get rid of any "Black blank screens" */ private class OAuthAcquireAccessTokenTask extends AsyncTask<Uri, Void, JSONObject> { private ProgressDialog dlg; @Override protected void onPreExecute() { dlg = ProgressDialog.show(AccountSettingsActivity.this, getText(R.string.dialog_title_acquiring_an_access_token), getText(R.string.dialog_summary_acquiring_an_access_token), // indeterminate duration true, // not cancelable false); } @Override protected JSONObject doInBackground(Uri... uris) { JSONObject jso = null; String message = ""; boolean authenticated = false; if (state.getAccount().getOAuthConsumerAndProvider() == null) { message = "Connection is not OAuth"; MyLog.e(this, message); } else { // We don't need to worry about any saved states: we can reconstruct // the state Uri uri = uris[0]; if (uri != null && HttpConnection.CALLBACK_URI.getHost().equals(uri.getHost())) { String token = state.getRequestToken(); String secret = state.getRequestSecret(); state.builder.setCredentialsVerificationStatus(CredentialsVerificationStatus.NEVER); try { // Clear the request stuff, we've used it already state.setRequestTokenWithSecret(null, null); OAuthConsumer consumer = state.getAccount().getOAuthConsumerAndProvider().getConsumer(); if (!(token == null || secret == null)) { consumer.setTokenWithSecret(token, secret); } String otoken = uri.getQueryParameter(OAuth.OAUTH_TOKEN); String verifier = uri.getQueryParameter(OAuth.OAUTH_VERIFIER); /* * yvolk 2010-07-08: It appeared that this may be not true: * Assert.assertEquals(otoken, mConsumer.getToken()); (e.g. * if User denied access during OAuth...) hence this is not * Assert :-) */ if (otoken != null || consumer.getToken() != null) { state.getAccount().getOAuthConsumerAndProvider().getProvider() .retrieveAccessToken(consumer, verifier); // Now we can retrieve the goodies token = consumer.getToken(); secret = consumer.getTokenSecret(); authenticated = true; } } catch (OAuthMessageSignerException | OAuthNotAuthorizedException | OAuthExpectationFailedException | OAuthCommunicationException e) { message = e.getMessage(); MyLog.e(this, e); } finally { if (authenticated) { state.builder.setUserTokenWithSecret(token, secret); } } } } try { jso = new JSONObject(); jso.put("succeeded", authenticated); jso.put("message", message); } catch (JSONException e) { MyLog.e(this, e); } return jso; } // This is in the UI thread, so we can mess with the UI @Override protected void onPostExecute(JSONObject jso) { DialogFactory.dismissSafely(dlg); if (jso != null) { try { boolean succeeded = jso.getBoolean("succeeded"); String message = jso.getString("message"); MyLog.d(TAG, this.getClass().getName() + " ended, " + (succeeded ? "authenticated" : "authentication failed")); if (succeeded) { // Credentials are present, so we may verify them // This is needed even for OAuth - to know Twitter Username new VerifyCredentialsTask().execute(); } else { String message2 = AccountSettingsActivity.this .getString(R.string.dialog_title_authentication_failed); if (message != null && message.length() > 0) { message2 = message2 + ": " + message; MyLog.d(TAG, message); } appendError(message2); state.builder.setCredentialsVerificationStatus(CredentialsVerificationStatus.FAILED); updateScreen(); } } catch (JSONException e) { MyLog.e(this, e); } } } } /** * Assuming we already have credentials to verify, verify them * @author yvolk@yurivolkov.com */ private class VerifyCredentialsTask extends AsyncTask<Void, Void, JSONObject> { private ProgressDialog dlg; private boolean skip = false; @Override protected void onPreExecute() { dlg = ProgressDialog.show(AccountSettingsActivity.this, getText(R.string.dialog_title_checking_credentials), getText(R.string.dialog_summary_checking_credentials), // indeterminate duration true, // not cancelable false); synchronized (AccountSettingsActivity.this) { if (somethingIsBeingProcessed) { skip = true; } else { somethingIsBeingProcessed = true; } } } @Override protected JSONObject doInBackground(Void... arg0) { JSONObject jso = null; int what = MSG_NONE; String message = ""; if (!skip) { what = MSG_ACCOUNT_INVALID; try { state.builder.getOriginConfig(); if (state.builder.verifyCredentials(true)) { what = MSG_ACCOUNT_VALID; } } catch (ConnectionException e) { switch (e.getStatusCode()) { case AUTHENTICATION_ERROR: what = MSG_ACCOUNT_INVALID; break; case CREDENTIALS_OF_OTHER_USER: what = MSG_CREDENTIALS_OF_OTHER_USER; break; default: what = MSG_CONNECTION_EXCEPTION; break; } message = e.toString(); MyLog.v(this, e); } } try { jso = new JSONObject(); jso.put("what", what); jso.put("message", message); } catch (JSONException e) { MyLog.e(this, e); } return jso; } /** * Credentials were verified just now! * This is in the UI thread, so we can mess with the UI */ @Override protected void onPostExecute(JSONObject jso) { DialogFactory.dismissSafely(dlg); boolean succeeded = false; CharSequence errorMessage = ""; if (jso != null) { try { int what = jso.getInt("what"); CharSequence message = jso.getString("message"); switch (what) { case MSG_ACCOUNT_VALID: Toast.makeText(AccountSettingsActivity.this, R.string.authentication_successful, Toast.LENGTH_SHORT).show(); succeeded = true; break; case MSG_ACCOUNT_INVALID: errorMessage = getText(R.string.dialog_summary_authentication_failed); break; case MSG_CREDENTIALS_OF_OTHER_USER: errorMessage = getText(R.string.error_credentials_of_other_user); break; case MSG_CONNECTION_EXCEPTION: errorMessage = getText(R.string.error_connection_error) + " \n" + message; MyLog.i(this, errorMessage.toString()); break; default: break; } } catch (JSONException e) { MyLog.e(this, e); } } if (!skip) { StateOfAccountChangeProcess state2 = AccountSettingsActivity.this.state; // Note: MyAccount was already saved inside MyAccount.verifyCredentials // Now we only have to deal with the state state2.actionSucceeded = succeeded; if (succeeded) { state2.actionCompleted = true; if (state2.getAccountAction().compareTo(Intent.ACTION_INSERT) == 0) { state2.setAccountAction(Intent.ACTION_EDIT); // TODO: Decide if we need closeAndGoBack() here } } somethingIsBeingProcessed = false; } updateScreen(); appendError(errorMessage); } } }