1 /* ownCloud Android client application 
   2  *   Copyright (C) 2012  Bartek Przybylski 
   3  *   Copyright (C) 2012-2013 ownCloud Inc. 
   5  *   This program is free software: you can redistribute it and/or modify 
   6  *   it under the terms of the GNU General Public License version 2, 
   7  *   as published by the Free Software Foundation. 
   9  *   This program is distributed in the hope that it will be useful, 
  10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  12  *   GNU General Public License for more details. 
  14  *   You should have received a copy of the GNU General Public License 
  15  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  19 package com
.owncloud
.android
.authentication
; 
  21 import com
.owncloud
.android
.AccountUtils
; 
  22 import com
.owncloud
.android
.Log_OC
; 
  23 import com
.owncloud
.android
.ui
.dialog
.SslValidatorDialog
; 
  24 import com
.owncloud
.android
.ui
.dialog
.SslValidatorDialog
.OnSslValidatorListener
; 
  25 import com
.owncloud
.android
.utils
.OwnCloudVersion
; 
  26 import com
.owncloud
.android
.network
.OwnCloudClientUtils
; 
  27 import com
.owncloud
.android
.operations
.OwnCloudServerCheckOperation
; 
  28 import com
.owncloud
.android
.operations
.ExistenceCheckOperation
; 
  29 import com
.owncloud
.android
.operations
.OAuth2GetAccessToken
; 
  30 import com
.owncloud
.android
.operations
.OnRemoteOperationListener
; 
  31 import com
.owncloud
.android
.operations
.RemoteOperation
; 
  32 import com
.owncloud
.android
.operations
.RemoteOperationResult
; 
  33 import com
.owncloud
.android
.operations
.RemoteOperationResult
.ResultCode
; 
  35 import android
.accounts
.Account
; 
  36 import android
.accounts
.AccountAuthenticatorActivity
; 
  37 import android
.accounts
.AccountManager
; 
  38 import android
.app
.AlertDialog
; 
  39 import android
.app
.Dialog
; 
  40 import android
.app
.ProgressDialog
; 
  41 import android
.content
.ContentResolver
; 
  42 import android
.content
.DialogInterface
; 
  43 import android
.content
.Intent
; 
  44 import android
.content
.SharedPreferences
; 
  45 import android
.graphics
.Rect
; 
  46 import android
.graphics
.drawable
.Drawable
; 
  47 import android
.net
.Uri
; 
  48 import android
.os
.Bundle
; 
  49 import android
.os
.Handler
; 
  50 import android
.preference
.PreferenceManager
; 
  51 import android
.text
.Editable
; 
  52 import android
.text
.InputType
; 
  53 import android
.text
.TextWatcher
; 
  54 import android
.view
.KeyEvent
; 
  55 import android
.view
.MotionEvent
; 
  56 import android
.view
.View
; 
  57 import android
.view
.View
.OnFocusChangeListener
; 
  58 import android
.view
.View
.OnTouchListener
; 
  59 import android
.view
.Window
; 
  60 import android
.view
.inputmethod
.EditorInfo
; 
  61 import android
.widget
.CheckBox
; 
  62 import android
.widget
.EditText
; 
  63 import android
.widget
.Button
; 
  64 import android
.widget
.TextView
; 
  65 import android
.widget
.Toast
; 
  66 import android
.widget
.TextView
.OnEditorActionListener
; 
  68 import com
.owncloud
.android
.R
; 
  70 import eu
.alefzero
.webdav
.WebdavClient
; 
  73  * This Activity is used to add an ownCloud account to the App 
  75  * @author Bartek Przybylski 
  76  * @author David A. Velasco 
  78 public class AuthenticatorActivity 
extends AccountAuthenticatorActivity
 
  79 implements  OnRemoteOperationListener
, OnSslValidatorListener
, OnFocusChangeListener
, OnEditorActionListener 
{ 
  81     private static final String TAG 
= AuthenticatorActivity
.class.getSimpleName(); 
  83     public static final String EXTRA_ACCOUNT 
= "ACCOUNT"; 
  84     public static final String EXTRA_USER_NAME 
= "USER_NAME"; 
  85     public static final String EXTRA_HOST_NAME 
= "HOST_NAME"; 
  86     public static final String EXTRA_ACTION 
= "ACTION"; 
  88     private static final String KEY_HOST_URL_TEXT 
= "HOST_URL_TEXT"; 
  89     private static final String KEY_OC_VERSION 
= "OC_VERSION"; 
  90     private static final String KEY_ACCOUNT 
= "ACCOUNT"; 
  91     private static final String KEY_SERVER_VALID 
= "SERVER_VALID"; 
  92     private static final String KEY_SERVER_CHECKED 
= "SERVER_CHECKED"; 
  93     private static final String KEY_SERVER_CHECK_IN_PROGRESS 
= "SERVER_CHECK_IN_PROGRESS";  
  94     private static final String KEY_SERVER_STATUS_TEXT 
= "SERVER_STATUS_TEXT"; 
  95     private static final String KEY_SERVER_STATUS_ICON 
= "SERVER_STATUS_ICON"; 
  96     private static final String KEY_IS_SSL_CONN 
= "IS_SSL_CONN"; 
  97     private static final String KEY_PASSWORD_VISIBLE 
= "PASSWORD_VISIBLE"; 
  98     private static final String KEY_AUTH_STATUS_TEXT 
= "AUTH_STATUS_TEXT"; 
  99     private static final String KEY_AUTH_STATUS_ICON 
= "AUTH_STATUS_ICON"; 
 100     private static final String KEY_REFRESH_BUTTON_ENABLED 
= "KEY_REFRESH_BUTTON_ENABLED"; 
 102     private static final String OAUTH_MODE_ON 
= "on"; 
 103     private static final String OAUTH_MODE_OFF 
= "off"; 
 104     private static final String OAUTH_MODE_OPTIONAL 
= "optional"; 
 106     private static final int DIALOG_LOGIN_PROGRESS 
= 0; 
 107     private static final int DIALOG_SSL_VALIDATOR 
= 1; 
 108     private static final int DIALOG_CERT_NOT_SAVED 
= 2; 
 109     private static final int DIALOG_OAUTH2_LOGIN_PROGRESS 
= 3; 
 111     public static final byte ACTION_CREATE 
= 0; 
 112     public static final byte ACTION_UPDATE_TOKEN 
= 1; 
 115     private String mHostBaseUrl
; 
 116     private OwnCloudVersion mDiscoveredVersion
; 
 118     private int mServerStatusText
, mServerStatusIcon
; 
 119     private boolean mServerIsChecked
, mServerIsValid
, mIsSslConn
; 
 120     private int mAuthStatusText
, mAuthStatusIcon
;     
 122     private final Handler mHandler 
= new Handler(); 
 123     private Thread mOperationThread
; 
 124     private OwnCloudServerCheckOperation mOcServerChkOperation
; 
 125     private ExistenceCheckOperation mAuthCheckOperation
; 
 126     private RemoteOperationResult mLastSslUntrustedServerResult
; 
 128     private Uri mNewCapturedUriFromOAuth2Redirection
; 
 130     private AccountManager mAccountMgr
; 
 131     private boolean mJustCreated
; 
 132     private byte mAction
; 
 133     private Account mAccount
; 
 135     private EditText mHostUrlInput
; 
 136     private EditText mUsernameInput
; 
 137     private EditText mPasswordInput
; 
 138     private CheckBox mOAuth2Check
; 
 139     private String mOAuthAccessToken
; 
 140     private View mOkButton
; 
 141     private TextView mAuthStatusLayout
; 
 143     private TextView mOAuthAuthEndpointText
; 
 144     private TextView mOAuthTokenEndpointText
; 
 146     private boolean mRefreshButtonEnabled
; 
 152      * IMPORTANT ENTRY POINT 1: activity is shown to the user 
 155     protected void onCreate(Bundle savedInstanceState
) { 
 156         super.onCreate(savedInstanceState
); 
 157         getWindow().requestFeature(Window
.FEATURE_NO_TITLE
); 
 159         /// set view and get references to view elements 
 160         setContentView(R
.layout
.account_setup
); 
 161         mHostUrlInput 
= (EditText
) findViewById(R
.id
.hostUrlInput
); 
 162         mUsernameInput 
= (EditText
) findViewById(R
.id
.account_username
); 
 163         mPasswordInput 
= (EditText
) findViewById(R
.id
.account_password
); 
 164         mOAuthAuthEndpointText 
= (TextView
)findViewById(R
.id
.oAuthEntryPoint_1
); 
 165         mOAuthTokenEndpointText 
= (TextView
)findViewById(R
.id
.oAuthEntryPoint_2
); 
 166         mOAuth2Check 
= (CheckBox
) findViewById(R
.id
.oauth_onOff_check
); 
 167         mOkButton 
= findViewById(R
.id
.buttonOK
); 
 168         mAuthStatusLayout 
= (TextView
) findViewById(R
.id
.auth_status_text
);  
 170         /// complete label for 'register account' button 
 171         Button b 
= (Button
) findViewById(R
.id
.account_register
); 
 173             b
.setText(String
.format(getString(R
.string
.auth_register
), getString(R
.string
.app_name
))); 
 177         mAccountMgr 
= AccountManager
.get(this); 
 178         mNewCapturedUriFromOAuth2Redirection 
= null
; 
 179         mAction 
= getIntent().getByteExtra(EXTRA_ACTION
, ACTION_CREATE
);  
 183         if (savedInstanceState 
== null
) { 
 184             /// connection state and info 
 185             mServerStatusText 
= mServerStatusIcon 
= 0; 
 186             mServerIsValid 
= false
; 
 187             mServerIsChecked 
= false
; 
 189             mAuthStatusText 
= mAuthStatusIcon 
= 0; 
 191             /// retrieve extras from intent 
 192             String tokenType 
= getIntent().getExtras().getString(AccountAuthenticator
.KEY_AUTH_TOKEN_TYPE
); 
 193             boolean oAuthRequired 
= AccountAuthenticator
.AUTH_TOKEN_TYPE_ACCESS_TOKEN
.equals(tokenType
) || OAUTH_MODE_ON
.equals(getString(R
.string
.oauth2_mode
)); 
 195             mAccount 
= getIntent().getExtras().getParcelable(EXTRA_ACCOUNT
); 
 196             if (mAccount 
!= null
) { 
 197                 String ocVersion 
= mAccountMgr
.getUserData(mAccount
, AccountAuthenticator
.KEY_OC_VERSION
); 
 198                 if (ocVersion 
!= null
) { 
 199                     mDiscoveredVersion 
= new OwnCloudVersion(ocVersion
); 
 201                 mHostBaseUrl 
= normalizeUrl(mAccountMgr
.getUserData(mAccount
, AccountAuthenticator
.KEY_OC_BASE_URL
)); 
 202                 mHostUrlInput
.setText(mHostBaseUrl
); 
 203                 String userName 
= mAccount
.name
.substring(0, mAccount
.name
.lastIndexOf('@')); 
 204                 mUsernameInput
.setText(userName
); 
 205                 oAuthRequired 
= (mAccountMgr
.getUserData(mAccount
, AccountAuthenticator
.KEY_SUPPORTS_OAUTH2
) != null
); 
 207             mOAuth2Check
.setChecked(oAuthRequired
); 
 208             changeViewByOAuth2Check(oAuthRequired
); 
 212             /// connection state and info 
 213             mServerIsValid 
= savedInstanceState
.getBoolean(KEY_SERVER_VALID
); 
 214             mServerIsChecked 
= savedInstanceState
.getBoolean(KEY_SERVER_CHECKED
); 
 215             mServerStatusText 
= savedInstanceState
.getInt(KEY_SERVER_STATUS_TEXT
); 
 216             mServerStatusIcon 
= savedInstanceState
.getInt(KEY_SERVER_STATUS_ICON
); 
 217             mIsSslConn 
= savedInstanceState
.getBoolean(KEY_IS_SSL_CONN
); 
 218             mAuthStatusText 
= savedInstanceState
.getInt(KEY_AUTH_STATUS_TEXT
); 
 219             mAuthStatusIcon 
= savedInstanceState
.getInt(KEY_AUTH_STATUS_ICON
); 
 220             if (savedInstanceState
.getBoolean(KEY_PASSWORD_VISIBLE
, false
)) { 
 225             String ocVersion 
= savedInstanceState
.getString(KEY_OC_VERSION
); 
 226             if (ocVersion 
!= null
) { 
 227                 mDiscoveredVersion 
= new OwnCloudVersion(ocVersion
); 
 229             mHostBaseUrl 
= savedInstanceState
.getString(KEY_HOST_URL_TEXT
); 
 231             // account data, if updating 
 232             mAccount 
= savedInstanceState
.getParcelable(KEY_ACCOUNT
); 
 234             // check if server check was interrupted by a configuration change 
 235             if (savedInstanceState
.getBoolean(KEY_SERVER_CHECK_IN_PROGRESS
, false
)) { 
 239             // refresh button enabled 
 240             mRefreshButtonEnabled 
= savedInstanceState
.getBoolean(KEY_REFRESH_BUTTON_ENABLED
); 
 246         if (mServerIsChecked 
&& !mServerIsValid 
&& mRefreshButtonEnabled
) showRefreshButton(); 
 247         mOkButton
.setEnabled(mServerIsValid
); // state not automatically recovered in configuration changes 
 249         if (!OAUTH_MODE_OPTIONAL
.equals(getString(R
.string
.oauth2_mode
))) { 
 250             mOAuth2Check
.setVisibility(View
.GONE
); 
 253         if (mAction 
== ACTION_UPDATE_TOKEN
) { 
 254             /// lock things that should not change 
 255             mHostUrlInput
.setEnabled(false
); 
 256             mUsernameInput
.setEnabled(false
); 
 257             mOAuth2Check
.setVisibility(View
.GONE
); 
 258             if (!mServerIsValid 
&& mOcServerChkOperation 
== null
) { 
 263         mPasswordInput
.setText("");     // clean password to avoid social hacking (disadvantage: password in removed if the device is turned aside) 
 266         /// bind view elements to listeners 
 267         mHostUrlInput
.setOnFocusChangeListener(this); 
 268         mHostUrlInput
.setOnTouchListener(new RightDrawableOnTouchListener() { 
 270             public boolean onDrawableTouch(final MotionEvent event
) { 
 271                 if (event
.getAction() == MotionEvent
.ACTION_UP
) { 
 272                     AuthenticatorActivity
.this.onRefreshClick(); 
 277         mHostUrlInput
.addTextChangedListener(new TextWatcher() { 
 280             public void afterTextChanged(Editable s
) { 
 281                 if (!mHostBaseUrl
.equals(normalizeUrl(mHostUrlInput
.getText().toString()))) { 
 282                     mOkButton
.setEnabled(false
); 
 287             public void beforeTextChanged(CharSequence s
, int start
, int count
, int after
) {} 
 290             public void onTextChanged(CharSequence s
, int start
, int before
, int count
) {} 
 293         mPasswordInput
.setOnFocusChangeListener(this); 
 294         mPasswordInput
.setImeOptions(EditorInfo
.IME_ACTION_DONE
); 
 295         mPasswordInput
.setOnEditorActionListener(this); 
 296         mPasswordInput
.setOnTouchListener(new RightDrawableOnTouchListener() { 
 298             public boolean onDrawableTouch(final MotionEvent event
) { 
 299                 if (event
.getAction() == MotionEvent
.ACTION_UP
) { 
 300                     AuthenticatorActivity
.this.onViewPasswordClick(); 
 308      * Saves relevant state before {@link #onPause()} 
 310      * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag, intended to defer the  
 311      * processing of the redirection caught in {@link #onNewIntent(Intent)} until {@link #onResume()}  
 313      * See {@link #loadSavedInstanceState(Bundle)} 
 316     protected void onSaveInstanceState(Bundle outState
) { 
 317         super.onSaveInstanceState(outState
); 
 319         /// connection state and info 
 320         outState
.putInt(KEY_SERVER_STATUS_TEXT
, mServerStatusText
); 
 321         outState
.putInt(KEY_SERVER_STATUS_ICON
, mServerStatusIcon
); 
 322         outState
.putBoolean(KEY_SERVER_VALID
, mServerIsValid
); 
 323         outState
.putBoolean(KEY_SERVER_CHECKED
, mServerIsChecked
); 
 324         outState
.putBoolean(KEY_SERVER_CHECK_IN_PROGRESS
, (!mServerIsValid 
&& mOcServerChkOperation 
!= null
)); 
 325         outState
.putBoolean(KEY_IS_SSL_CONN
, mIsSslConn
); 
 326         outState
.putBoolean(KEY_PASSWORD_VISIBLE
, isPasswordVisible()); 
 327         outState
.putInt(KEY_AUTH_STATUS_ICON
, mAuthStatusIcon
); 
 328         outState
.putInt(KEY_AUTH_STATUS_TEXT
, mAuthStatusText
); 
 331         if (mDiscoveredVersion 
!= null
) { 
 332             outState
.putString(KEY_OC_VERSION
, mDiscoveredVersion
.toString()); 
 334         outState
.putString(KEY_HOST_URL_TEXT
, mHostBaseUrl
); 
 336         /// account data, if updating 
 337         if (mAccount 
!= null
) { 
 338             outState
.putParcelable(KEY_ACCOUNT
, mAccount
); 
 341         // refresh button enabled 
 342         outState
.putBoolean(KEY_REFRESH_BUTTON_ENABLED
, mRefreshButtonEnabled
); 
 348      * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION request 
 351      * To make this possible, this activity needs to be qualified with android:launchMode = "singleTask" in the 
 352      * AndroidManifest.xml file. 
 355     protected void onNewIntent (Intent intent
) { 
 356         Log_OC
.d(TAG
, "onNewIntent()"); 
 357         Uri data 
= intent
.getData(); 
 358         if (data 
!= null 
&& data
.toString().startsWith(getString(R
.string
.oauth2_redirect_uri
))) { 
 359             mNewCapturedUriFromOAuth2Redirection 
= data
; 
 365      * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and  
 366      * deferred in {@link #onNewIntent(Intent)}, is processed here. 
 369     protected void onResume() { 
 371         // the state of mOAuth2Check is automatically recovered between configuration changes, but not before onCreate() finishes; so keep the next lines here 
 372         changeViewByOAuth2Check(mOAuth2Check
.isChecked());   
 373         if (mAction 
== ACTION_UPDATE_TOKEN 
&& mJustCreated
) { 
 374             if (mOAuth2Check
.isChecked()) 
 375                 Toast
.makeText(this, R
.string
.auth_expired_oauth_token_toast
, Toast
.LENGTH_LONG
).show(); 
 377                 Toast
.makeText(this, R
.string
.auth_expired_basic_auth_toast
, Toast
.LENGTH_LONG
).show(); 
 380         if (mNewCapturedUriFromOAuth2Redirection 
!= null
) { 
 381             getOAuth2AccessTokenFromCapturedRedirection();             
 384         mJustCreated 
= false
; 
 389      * Parses the redirection with the response to the GET AUTHORIZATION request to the  
 390      * oAuth server and requests for the access token (GET ACCESS TOKEN) 
 392     private void getOAuth2AccessTokenFromCapturedRedirection() { 
 393         /// Parse data from OAuth redirection 
 394         String queryParameters 
= mNewCapturedUriFromOAuth2Redirection
.getQuery(); 
 395         mNewCapturedUriFromOAuth2Redirection 
= null
; 
 397         /// Showing the dialog with instructions for the user. 
 398         showDialog(DIALOG_OAUTH2_LOGIN_PROGRESS
); 
 400         /// GET ACCESS TOKEN to the oAuth server  
 401         RemoteOperation operation 
= new OAuth2GetAccessToken(   getString(R
.string
.oauth2_client_id
),  
 402                 getString(R
.string
.oauth2_redirect_uri
),        
 403                 getString(R
.string
.oauth2_grant_type
), 
 405         //WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(getString(R.string.oauth2_url_endpoint_access)), getApplicationContext()); 
 406         WebdavClient client 
= OwnCloudClientUtils
.createOwnCloudClient(Uri
.parse(mOAuthTokenEndpointText
.getText().toString().trim()), getApplicationContext()); 
 407         operation
.execute(client
, this, mHandler
); 
 413      * Handles the change of focus on the text inputs for the server URL and the password 
 415     public void onFocusChange(View view
, boolean hasFocus
) { 
 416         if (view
.getId() == R
.id
.hostUrlInput
) {    
 418                 onUrlInputFocusLost((TextView
) view
); 
 419                 if (!mServerIsValid
) { 
 427         } else if (view
.getId() == R
.id
.account_password
) { 
 428             onPasswordFocusChanged((TextView
) view
, hasFocus
); 
 434      * Handles changes in focus on the text input for the server URL. 
 436      * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to  
 437      * other field. The operation to check the existence of the server in the entered URL is 
 440      * When hasFocus:    user 'comes back' to write again the server URL. 
 442      * @param hostInput     TextView with the URL input field receiving the change of focus. 
 444     private void onUrlInputFocusLost(TextView hostInput
) { 
 445         if (!mHostBaseUrl
.equals(normalizeUrl(mHostUrlInput
.getText().toString()))) { 
 448             mOkButton
.setEnabled(mServerIsValid
); 
 453     private void checkOcServer() { 
 454         String uri 
= mHostUrlInput
.getText().toString().trim(); 
 455         mServerIsValid 
= false
; 
 456         mServerIsChecked 
= false
; 
 457         mOkButton
.setEnabled(false
); 
 458         mDiscoveredVersion 
= null
; 
 460         if (uri
.length() != 0) { 
 461             mServerStatusText 
= R
.string
.auth_testing_connection
; 
 462             mServerStatusIcon 
= R
.drawable
.progress_small
; 
 464             mOcServerChkOperation 
= new  OwnCloudServerCheckOperation(uri
, this); 
 465             WebdavClient client 
= OwnCloudClientUtils
.createOwnCloudClient(Uri
.parse(uri
), this); 
 466             mOperationThread 
= mOcServerChkOperation
.execute(client
, this, mHandler
); 
 468             mServerStatusText 
= 0; 
 469             mServerStatusIcon 
= 0; 
 476      * Handles changes in focus on the text input for the password (basic authorization). 
 478      * When (hasFocus), the button to toggle password visibility is shown. 
 480      * When (!hasFocus), the button is made invisible and the password is hidden. 
 482      * @param passwordInput    TextView with the password input field receiving the change of focus. 
 483      * @param hasFocus          'True' if focus is received, 'false' if is lost 
 485     private void onPasswordFocusChanged(TextView passwordInput
, boolean hasFocus
) { 
 487             showViewPasswordButton(); 
 490             hidePasswordButton(); 
 495     private void showViewPasswordButton() { 
 496         //int drawable = android.R.drawable.ic_menu_view; 
 497         int drawable 
= R
.drawable
.ic_view
; 
 498         if (isPasswordVisible()) { 
 499             //drawable = android.R.drawable.ic_secure; 
 500             drawable 
= R
.drawable
.ic_hide
; 
 502         mPasswordInput
.setCompoundDrawablesWithIntrinsicBounds(0, 0, drawable
, 0); 
 505     private boolean isPasswordVisible() { 
 506         return ((mPasswordInput
.getInputType() & InputType
.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
) == InputType
.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
); 
 509     private void hidePasswordButton() { 
 510         mPasswordInput
.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); 
 513     private void showPassword() { 
 514         mPasswordInput
.setInputType(InputType
.TYPE_CLASS_TEXT 
| InputType
.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
); 
 515         showViewPasswordButton(); 
 518     private void hidePassword() { 
 519         mPasswordInput
.setInputType(InputType
.TYPE_CLASS_TEXT 
| InputType
.TYPE_TEXT_VARIATION_PASSWORD
); 
 520         showViewPasswordButton(); 
 525      * Cancels the authenticator activity 
 527      * IMPORTANT ENTRY POINT 3: Never underestimate the importance of cancellation 
 529      * This method is bound in the layout/acceoun_setup.xml resource file. 
 531      * @param view      Cancel button 
 533     public void onCancelClick(View view
) { 
 534         setResult(RESULT_CANCELED
);     // TODO review how is this related to AccountAuthenticator (debugging) 
 541      * Checks the credentials of the user in the root of the ownCloud server 
 542      * before creating a new local account. 
 544      * For basic authorization, a check of existence of the root folder is 
 547      * For OAuth, starts the flow to get an access token; the credentials test  
 548      * is postponed until it is available. 
 550      * IMPORTANT ENTRY POINT 4 
 552      * @param view      OK button 
 554     public void onOkClick(View view
) { 
 555         // this check should be unnecessary 
 556         if (mDiscoveredVersion 
== null 
|| !mDiscoveredVersion
.isVersionValid()  || mHostBaseUrl 
== null 
|| mHostBaseUrl
.length() == 0) { 
 557             mServerStatusIcon 
= R
.drawable
.common_error
; 
 558             mServerStatusText 
= R
.string
.auth_wtf_reenter_URL
; 
 560             mOkButton
.setEnabled(false
); 
 561             Log_OC
.wtf(TAG
,  "The user was allowed to click 'connect' to an unchecked server!!"); 
 565         if (mOAuth2Check
.isChecked()) { 
 566             startOauthorization(); 
 569             checkBasicAuthorization(); 
 575      * Tests the credentials entered by the user performing a check of existence on  
 576      * the root folder of the ownCloud server. 
 578     private void checkBasicAuthorization() { 
 579         /// get the path to the root folder through WebDAV from the version server 
 580         String webdav_path 
= AccountUtils
.getWebdavPath(mDiscoveredVersion
, false
); 
 582         /// get basic credentials entered by user 
 583         String username 
= mUsernameInput
.getText().toString(); 
 584         String password 
= mPasswordInput
.getText().toString(); 
 586         /// be gentle with the user 
 587         showDialog(DIALOG_LOGIN_PROGRESS
); 
 589         /// test credentials accessing the root folder 
 590         mAuthCheckOperation 
= new  ExistenceCheckOperation("", this, false
); 
 591         WebdavClient client 
= OwnCloudClientUtils
.createOwnCloudClient(Uri
.parse(mHostBaseUrl 
+ webdav_path
), this); 
 592         client
.setBasicCredentials(username
, password
); 
 593         mOperationThread 
= mAuthCheckOperation
.execute(client
, this, mHandler
); 
 598      * Starts the OAuth 'grant type' flow to get an access token, with  
 599      * a GET AUTHORIZATION request to the BUILT-IN authorization server.  
 601     private void startOauthorization() { 
 602         // be gentle with the user 
 603         mAuthStatusIcon 
= R
.drawable
.progress_small
; 
 604         mAuthStatusText 
= R
.string
.oauth_login_connection
; 
 607         // GET AUTHORIZATION request 
 608         //Uri uri = Uri.parse(getString(R.string.oauth2_url_endpoint_auth)); 
 609         Uri uri 
= Uri
.parse(mOAuthAuthEndpointText
.getText().toString().trim()); 
 610         Uri
.Builder uriBuilder 
= uri
.buildUpon(); 
 611         uriBuilder
.appendQueryParameter(OAuth2Constants
.KEY_RESPONSE_TYPE
, getString(R
.string
.oauth2_response_type
)); 
 612         uriBuilder
.appendQueryParameter(OAuth2Constants
.KEY_REDIRECT_URI
, getString(R
.string
.oauth2_redirect_uri
));    
 613         uriBuilder
.appendQueryParameter(OAuth2Constants
.KEY_CLIENT_ID
, getString(R
.string
.oauth2_client_id
)); 
 614         uriBuilder
.appendQueryParameter(OAuth2Constants
.KEY_SCOPE
, getString(R
.string
.oauth2_scope
)); 
 615         //uriBuilder.appendQueryParameter(OAuth2Constants.KEY_STATE, whateverwewant); 
 616         uri 
= uriBuilder
.build(); 
 617         Log_OC
.d(TAG
, "Starting browser to view " + uri
.toString()); 
 618         Intent i 
= new Intent(Intent
.ACTION_VIEW
, uri
); 
 624      * Callback method invoked when a RemoteOperation executed by this Activity finishes. 
 626      * Dispatches the operation flow to the right method. 
 629     public void onRemoteOperationFinish(RemoteOperation operation
, RemoteOperationResult result
) { 
 631         if (operation 
instanceof OwnCloudServerCheckOperation
) { 
 632             onOcServerCheckFinish((OwnCloudServerCheckOperation
) operation
, result
); 
 634         } else if (operation 
instanceof OAuth2GetAccessToken
) { 
 635             onGetOAuthAccessTokenFinish((OAuth2GetAccessToken
)operation
, result
); 
 637         } else if (operation 
instanceof ExistenceCheckOperation
)  { 
 638             onAuthorizationCheckFinish((ExistenceCheckOperation
)operation
, result
); 
 645      * Processes the result of the server check performed when the user finishes the enter of the 
 648      * @param operation     Server check performed. 
 649      * @param result        Result of the check. 
 651     private void onOcServerCheckFinish(OwnCloudServerCheckOperation operation
, RemoteOperationResult result
) { 
 652         if (operation
.equals(mOcServerChkOperation
)) { 
 653             /// save result state 
 654             mServerIsChecked 
= true
; 
 655             mServerIsValid 
= result
.isSuccess(); 
 656             mIsSslConn 
= (result
.getCode() == ResultCode
.OK_SSL
); 
 657             mOcServerChkOperation 
= null
; 
 659             /// update status icon and text 
 660             if (mServerIsValid
) { 
 665             updateServerStatusIconAndText(result
); 
 668             /// very special case (TODO: move to a common place for all the remote operations) 
 669             if (result
.getCode() == ResultCode
.SSL_RECOVERABLE_PEER_UNVERIFIED
) { 
 670                 mLastSslUntrustedServerResult 
= result
; 
 671                 showDialog(DIALOG_SSL_VALIDATOR
);  
 674             /// retrieve discovered version and normalize server URL 
 675             mDiscoveredVersion 
= operation
.getDiscoveredVersion(); 
 676             mHostBaseUrl 
= normalizeUrl(mHostUrlInput
.getText().toString()); 
 678             /// allow or not the user try to access the server 
 679             mOkButton
.setEnabled(mServerIsValid
); 
 681         }   // else nothing ; only the last check operation is considered;  
 682         // multiple can be triggered if the user amends a URL before a previous check can be triggered 
 686     private String 
normalizeUrl(String url
) { 
 687         if (url 
!= null 
&& url
.length() > 0) { 
 689             if (!url
.toLowerCase().startsWith("http://") && 
 690                     !url
.toLowerCase().startsWith("https://")) { 
 692                     url 
= "https://" + url
; 
 694                     url 
= "http://" + url
; 
 698             if (url
.endsWith("/")) { 
 699                 url 
= url
.substring(0, url
.length() - 1); 
 702         return (url 
!= null ? url 
: ""); 
 706      * Chooses the right icon and text to show to the user for the received operation result. 
 708      * @param result    Result of a remote operation performed in this activity 
 710     private void updateServerStatusIconAndText(RemoteOperationResult result
) { 
 711         mServerStatusIcon 
= R
.drawable
.common_error
;    // the most common case in the switch below 
 713         switch (result
.getCode()) { 
 715             mServerStatusIcon 
= android
.R
.drawable
.ic_secure
; 
 716             mServerStatusText 
= R
.string
.auth_secure_connection
; 
 721             if (mHostUrlInput
.getText().toString().trim().toLowerCase().startsWith("http://") ) { 
 722                 mServerStatusText 
= R
.string
.auth_connection_established
; 
 723                 mServerStatusIcon 
= R
.drawable
.ic_ok
; 
 725                 mServerStatusText 
= R
.string
.auth_nossl_plain_ok_title
; 
 726                 mServerStatusIcon 
= android
.R
.drawable
.ic_partial_secure
; 
 730         case NO_NETWORK_CONNECTION
: 
 731             mServerStatusIcon 
= R
.drawable
.no_network
; 
 732             mServerStatusText 
= R
.string
.auth_no_net_conn_title
; 
 735         case SSL_RECOVERABLE_PEER_UNVERIFIED
: 
 736             mServerStatusText 
= R
.string
.auth_ssl_unverified_server_title
; 
 739             mServerStatusText 
= R
.string
.auth_bad_oc_version_title
; 
 741         case WRONG_CONNECTION
: 
 742             mServerStatusText 
= R
.string
.auth_wrong_connection_title
; 
 745             mServerStatusText 
= R
.string
.auth_timeout_title
; 
 747         case INCORRECT_ADDRESS
: 
 748             mServerStatusText 
= R
.string
.auth_incorrect_address_title
; 
 751             mServerStatusText 
= R
.string
.auth_ssl_general_error_title
; 
 754             mServerStatusText 
= R
.string
.auth_unauthorized
; 
 756         case HOST_NOT_AVAILABLE
: 
 757             mServerStatusText 
= R
.string
.auth_unknown_host_title
; 
 759         case INSTANCE_NOT_CONFIGURED
: 
 760             mServerStatusText 
= R
.string
.auth_not_configured_title
; 
 763             mServerStatusText 
= R
.string
.auth_incorrect_path_title
; 
 766             mServerStatusText 
= R
.string
.auth_oauth_error
; 
 768         case OAUTH2_ERROR_ACCESS_DENIED
: 
 769             mServerStatusText 
= R
.string
.auth_oauth_error_access_denied
; 
 771         case UNHANDLED_HTTP_CODE
: 
 773             mServerStatusText 
= R
.string
.auth_unknown_error_title
; 
 776             mServerStatusText 
= 0; 
 777             mServerStatusIcon 
= 0; 
 783      * Chooses the right icon and text to show to the user for the received operation result. 
 785      * @param result    Result of a remote operation performed in this activity 
 787     private void updateAuthStatusIconAndText(RemoteOperationResult result
) { 
 788         mAuthStatusIcon 
= R
.drawable
.common_error
;    // the most common case in the switch below 
 790         switch (result
.getCode()) { 
 792             mAuthStatusIcon 
= android
.R
.drawable
.ic_secure
; 
 793             mAuthStatusText 
= R
.string
.auth_secure_connection
; 
 798             if (mHostUrlInput
.getText().toString().trim().toLowerCase().startsWith("http://") ) { 
 799                 mAuthStatusText 
= R
.string
.auth_connection_established
; 
 800                 mAuthStatusIcon 
= R
.drawable
.ic_ok
; 
 802                 mAuthStatusText 
= R
.string
.auth_nossl_plain_ok_title
; 
 803                 mAuthStatusIcon 
= android
.R
.drawable
.ic_partial_secure
; 
 807         case NO_NETWORK_CONNECTION
: 
 808             mAuthStatusIcon 
= R
.drawable
.no_network
; 
 809             mAuthStatusText 
= R
.string
.auth_no_net_conn_title
; 
 812         case SSL_RECOVERABLE_PEER_UNVERIFIED
: 
 813             mAuthStatusText 
= R
.string
.auth_ssl_unverified_server_title
; 
 816             mAuthStatusText 
= R
.string
.auth_bad_oc_version_title
; 
 818         case WRONG_CONNECTION
: 
 819             mAuthStatusText 
= R
.string
.auth_wrong_connection_title
; 
 822             mAuthStatusText 
= R
.string
.auth_timeout_title
; 
 824         case INCORRECT_ADDRESS
: 
 825             mAuthStatusText 
= R
.string
.auth_incorrect_address_title
; 
 828             mAuthStatusText 
= R
.string
.auth_ssl_general_error_title
; 
 831             mAuthStatusText 
= R
.string
.auth_unauthorized
; 
 833         case HOST_NOT_AVAILABLE
: 
 834             mAuthStatusText 
= R
.string
.auth_unknown_host_title
; 
 836         case INSTANCE_NOT_CONFIGURED
: 
 837             mAuthStatusText 
= R
.string
.auth_not_configured_title
; 
 840             mAuthStatusText 
= R
.string
.auth_incorrect_path_title
; 
 843             mAuthStatusText 
= R
.string
.auth_oauth_error
; 
 845         case OAUTH2_ERROR_ACCESS_DENIED
: 
 846             mAuthStatusText 
= R
.string
.auth_oauth_error_access_denied
; 
 848         case UNHANDLED_HTTP_CODE
: 
 850             mAuthStatusText 
= R
.string
.auth_unknown_error_title
; 
 860      * Processes the result of the request for and access token send  
 861      * to an OAuth authorization server. 
 863      * @param operation     Operation performed requesting the access token. 
 864      * @param result        Result of the operation. 
 866     private void onGetOAuthAccessTokenFinish(OAuth2GetAccessToken operation
, RemoteOperationResult result
) { 
 868             dismissDialog(DIALOG_OAUTH2_LOGIN_PROGRESS
); 
 869         } catch (IllegalArgumentException e
) { 
 870             // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens 
 873         String webdav_path 
= AccountUtils
.getWebdavPath(mDiscoveredVersion
, true
); 
 874         if (result
.isSuccess() && webdav_path 
!= null
) { 
 875             /// be gentle with the user 
 876             showDialog(DIALOG_LOGIN_PROGRESS
); 
 878             /// time to test the retrieved access token on the ownCloud server 
 879             mOAuthAccessToken 
= ((OAuth2GetAccessToken
)operation
).getResultTokenMap().get(OAuth2Constants
.KEY_ACCESS_TOKEN
); 
 880             Log_OC
.d(TAG
, "Got ACCESS TOKEN: " + mOAuthAccessToken
); 
 881             mAuthCheckOperation 
= new ExistenceCheckOperation("", this, false
); 
 882             WebdavClient client 
= OwnCloudClientUtils
.createOwnCloudClient(Uri
.parse(mHostBaseUrl 
+ webdav_path
), this); 
 883             client
.setBearerCredentials(mOAuthAccessToken
); 
 884             mAuthCheckOperation
.execute(client
, this, mHandler
); 
 887             updateAuthStatusIconAndText(result
); 
 889             Log_OC
.d(TAG
, "Access failed: " + result
.getLogMessage()); 
 895      * Processes the result of the access check performed to try the user credentials. 
 897      * Creates a new account through the AccountManager. 
 899      * @param operation     Access check performed. 
 900      * @param result        Result of the operation. 
 902     private void onAuthorizationCheckFinish(ExistenceCheckOperation operation
, RemoteOperationResult result
) { 
 904             dismissDialog(DIALOG_LOGIN_PROGRESS
); 
 905         } catch (IllegalArgumentException e
) { 
 906             // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens 
 909         if (result
.isSuccess()) { 
 910             Log_OC
.d(TAG
, "Successful access - time to save the account"); 
 912             if (mAction 
== ACTION_CREATE
) { 
 922             updateAuthStatusIconAndText(result
); 
 924             Log_OC
.d(TAG
, "Access failed: " + result
.getLogMessage()); 
 930      * Sets the proper response to get that the Account Authenticator that started this activity saves  
 931      * a new authorization token for mAccount. 
 933     private void updateToken() { 
 934         Bundle response 
= new Bundle(); 
 935         response
.putString(AccountManager
.KEY_ACCOUNT_NAME
, mAccount
.name
); 
 936         response
.putString(AccountManager
.KEY_ACCOUNT_TYPE
, mAccount
.type
); 
 937         boolean isOAuth 
= mOAuth2Check
.isChecked(); 
 939             response
.putString(AccountManager
.KEY_AUTHTOKEN
, mOAuthAccessToken
); 
 940             // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention 
 941             mAccountMgr
.setAuthToken(mAccount
, AccountAuthenticator
.AUTH_TOKEN_TYPE_ACCESS_TOKEN
, mOAuthAccessToken
); 
 943             response
.putString(AccountManager
.KEY_AUTHTOKEN
, mPasswordInput
.getText().toString()); 
 944             mAccountMgr
.setPassword(mAccount
, mPasswordInput
.getText().toString()); 
 946         setAccountAuthenticatorResult(response
); 
 951      * Creates a new account through the Account Authenticator that started this activity.  
 953      * This makes the account permanent. 
 955      * TODO Decide how to name the OAuth accounts 
 957     private void createAccount() { 
 958         /// create and save new ownCloud account 
 959         boolean isOAuth 
= mOAuth2Check
.isChecked(); 
 961         Uri uri 
= Uri
.parse(mHostBaseUrl
); 
 962         String username 
= mUsernameInput
.getText().toString().trim(); 
 964             username 
= "OAuth_user" + (new java
.util
.Random(System
.currentTimeMillis())).nextLong(); 
 966         String accountName 
= username 
+ "@" + uri
.getHost(); 
 967         if (uri
.getPort() >= 0) { 
 968             accountName 
+= ":" + uri
.getPort(); 
 970         mAccount 
= new Account(accountName
, AccountAuthenticator
.ACCOUNT_TYPE
); 
 972             mAccountMgr
.addAccountExplicitly(mAccount
, "", null
);  // with our implementation, the password is never input in the app 
 974             mAccountMgr
.addAccountExplicitly(mAccount
, mPasswordInput
.getText().toString(), null
); 
 977         /// add the new account as default in preferences, if there is none already 
 978         Account defaultAccount 
= AccountUtils
.getCurrentOwnCloudAccount(this); 
 979         if (defaultAccount 
== null
) { 
 980             SharedPreferences
.Editor editor 
= PreferenceManager
 
 981                     .getDefaultSharedPreferences(this).edit(); 
 982             editor
.putString("select_oc_account", accountName
); 
 986         /// prepare result to return to the Authenticator 
 987         //  TODO check again what the Authenticator makes with it; probably has the same effect as addAccountExplicitly, but it's not well done 
 988         final Intent intent 
= new Intent();        
 989         intent
.putExtra(AccountManager
.KEY_ACCOUNT_TYPE
,    AccountAuthenticator
.ACCOUNT_TYPE
); 
 990         intent
.putExtra(AccountManager
.KEY_ACCOUNT_NAME
,    mAccount
.name
); 
 992             intent
.putExtra(AccountManager
.KEY_AUTHTOKEN
,   AccountAuthenticator
.ACCOUNT_TYPE
); // TODO check this; not sure it's right; maybe 
 993         intent
.putExtra(AccountManager
.KEY_USERDATA
,        username
); 
 995             mAccountMgr
.setAuthToken(mAccount
, AccountAuthenticator
.AUTH_TOKEN_TYPE_ACCESS_TOKEN
, mOAuthAccessToken
); 
 997         /// add user data to the new account; TODO probably can be done in the last parameter addAccountExplicitly, or in KEY_USERDATA 
 998         mAccountMgr
.setUserData(mAccount
, AccountAuthenticator
.KEY_OC_VERSION
,    mDiscoveredVersion
.toString()); 
 999         mAccountMgr
.setUserData(mAccount
, AccountAuthenticator
.KEY_OC_BASE_URL
,   mHostBaseUrl
); 
1001             mAccountMgr
.setUserData(mAccount
, AccountAuthenticator
.KEY_SUPPORTS_OAUTH2
, "TRUE");  // TODO this flag should be unnecessary 
1003         setAccountAuthenticatorResult(intent
.getExtras()); 
1004         setResult(RESULT_OK
, intent
); 
1006         /// immediately request for the synchronization of the new account 
1007         Bundle bundle 
= new Bundle(); 
1008         bundle
.putBoolean(ContentResolver
.SYNC_EXTRAS_MANUAL
, true
); 
1009         ContentResolver
.requestSync(mAccount
, AccountAuthenticator
.AUTHORITY
, bundle
); 
1016      * Necessary to update the contents of the SSL Dialog 
1018      * TODO move to some common place for all possible untrusted SSL failures 
1021     protected void onPrepareDialog(int id
, Dialog dialog
, Bundle args
) { 
1023         case DIALOG_LOGIN_PROGRESS
: 
1024         case DIALOG_CERT_NOT_SAVED
: 
1025         case DIALOG_OAUTH2_LOGIN_PROGRESS
: 
1027         case DIALOG_SSL_VALIDATOR
: { 
1028             ((SslValidatorDialog
)dialog
).updateResult(mLastSslUntrustedServerResult
); 
1032             Log_OC
.e(TAG
, "Incorrect dialog called with id = " + id
); 
1041     protected Dialog 
onCreateDialog(int id
) { 
1042         Dialog dialog 
= null
; 
1044         case DIALOG_LOGIN_PROGRESS
: { 
1045             /// simple progress dialog 
1046             ProgressDialog working_dialog 
= new ProgressDialog(this); 
1047             working_dialog
.setMessage(getResources().getString(R
.string
.auth_trying_to_login
)); 
1048             working_dialog
.setIndeterminate(true
); 
1049             working_dialog
.setCancelable(true
); 
1051             .setOnCancelListener(new DialogInterface
.OnCancelListener() { 
1053                 public void onCancel(DialogInterface dialog
) { 
1054                     /// TODO study if this is enough 
1055                     Log_OC
.i(TAG
, "Login canceled"); 
1056                     if (mOperationThread 
!= null
) { 
1057                         mOperationThread
.interrupt(); 
1062             dialog 
= working_dialog
; 
1065         case DIALOG_OAUTH2_LOGIN_PROGRESS
: { 
1066             ProgressDialog working_dialog 
= new ProgressDialog(this); 
1067             working_dialog
.setMessage(String
.format("Getting authorization"));  
1068             working_dialog
.setIndeterminate(true
); 
1069             working_dialog
.setCancelable(true
); 
1071             .setOnCancelListener(new DialogInterface
.OnCancelListener() { 
1073                 public void onCancel(DialogInterface dialog
) { 
1074                     Log_OC
.i(TAG
, "Login canceled"); 
1078             dialog 
= working_dialog
; 
1081         case DIALOG_SSL_VALIDATOR
: { 
1082             /// TODO start to use new dialog interface, at least for this (it is a FragmentDialog already) 
1083             dialog 
= SslValidatorDialog
.newInstance(this, mLastSslUntrustedServerResult
, this); 
1086         case DIALOG_CERT_NOT_SAVED
: { 
1087             AlertDialog
.Builder builder 
= new AlertDialog
.Builder(this); 
1088             builder
.setMessage(getResources().getString(R
.string
.ssl_validator_not_saved
)); 
1089             builder
.setCancelable(false
); 
1090             builder
.setPositiveButton(R
.string
.common_ok
, new DialogInterface
.OnClickListener() { 
1092                 public void onClick(DialogInterface dialog
, int which
) { 
1096             dialog 
= builder
.create(); 
1100             Log_OC
.e(TAG
, "Incorrect dialog called with id = " + id
); 
1107      * Starts and activity to open the 'new account' page in the ownCloud web site 
1109      * @param view      'Account register' button 
1111     public void onRegisterClick(View view
) { 
1112         Intent register 
= new Intent(Intent
.ACTION_VIEW
, Uri
.parse(getString(R
.string
.url_account_register
))); 
1113         setResult(RESULT_CANCELED
); 
1114         startActivity(register
); 
1119      * Updates the content and visibility state of the icon and text associated 
1120      * to the last check on the ownCloud server. 
1122     private void showServerStatus() { 
1123         TextView tv 
= (TextView
) findViewById(R
.id
.server_status_text
); 
1125         if (mServerStatusIcon 
== 0 && mServerStatusText 
== 0) { 
1126             tv
.setVisibility(View
.INVISIBLE
); 
1129             tv
.setText(mServerStatusText
); 
1130             tv
.setCompoundDrawablesWithIntrinsicBounds(mServerStatusIcon
, 0, 0, 0); 
1131             tv
.setVisibility(View
.VISIBLE
); 
1138      * Updates the content and visibility state of the icon and text associated 
1139      * to the interactions with the OAuth authorization server. 
1141     private void showAuthStatus() { 
1142         if (mAuthStatusIcon 
== 0 && mAuthStatusText 
== 0) { 
1143             mAuthStatusLayout
.setVisibility(View
.INVISIBLE
); 
1146             mAuthStatusLayout
.setText(mAuthStatusText
); 
1147             mAuthStatusLayout
.setCompoundDrawablesWithIntrinsicBounds(mAuthStatusIcon
, 0, 0, 0); 
1148             mAuthStatusLayout
.setVisibility(View
.VISIBLE
); 
1153     private void showRefreshButton() { 
1154         mHostUrlInput
.setCompoundDrawablesWithIntrinsicBounds(0, 0, R
.drawable
.ic_action_refresh_black
, 0); 
1155         mRefreshButtonEnabled 
= true
; 
1158     private void hideRefreshButton() { 
1159         mHostUrlInput
.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); 
1160         mRefreshButtonEnabled 
= false
; 
1164      * Called when the refresh button in the input field for ownCloud host is clicked. 
1166      * Performs a new check on the URL in the input field. 
1168      * @param view      Refresh 'button' 
1170     public void onRefreshClick() { 
1176      * Called when the eye icon in the password field is clicked. 
1178      * Toggles the visibility of the password in the field.  
1180     public void onViewPasswordClick() { 
1181         int selectionStart 
= mPasswordInput
.getSelectionStart(); 
1182         int selectionEnd 
= mPasswordInput
.getSelectionEnd(); 
1183         if (isPasswordVisible()) { 
1188         mPasswordInput
.setSelection(selectionStart
, selectionEnd
); 
1193      * Called when the checkbox for OAuth authorization is clicked. 
1195      * Hides or shows the input fields for user & password.  
1197      * @param view      'View password' 'button' 
1199     public void onCheckClick(View view
) { 
1200         CheckBox oAuth2Check 
= (CheckBox
)view
;       
1201         changeViewByOAuth2Check(oAuth2Check
.isChecked()); 
1206      * Changes the visibility of input elements depending upon the kind of authorization 
1207      * chosen by the user: basic or OAuth 
1209      * @param checked       'True' when OAuth is selected. 
1211     public void changeViewByOAuth2Check(Boolean checked
) { 
1214             mOAuthAuthEndpointText
.setVisibility(View
.VISIBLE
); 
1215             mOAuthTokenEndpointText
.setVisibility(View
.VISIBLE
); 
1216             mUsernameInput
.setVisibility(View
.GONE
); 
1217             mPasswordInput
.setVisibility(View
.GONE
); 
1219             mOAuthAuthEndpointText
.setVisibility(View
.GONE
); 
1220             mOAuthTokenEndpointText
.setVisibility(View
.GONE
); 
1221             mUsernameInput
.setVisibility(View
.VISIBLE
); 
1222             mPasswordInput
.setVisibility(View
.VISIBLE
); 
1228      * Called from SslValidatorDialog when a new server certificate was correctly saved. 
1230     public void onSavedCertificate() { 
1231         mOperationThread 
= mOcServerChkOperation
.retry(this, mHandler
);                 
1235      * Called from SslValidatorDialog when a new server certificate could not be saved  
1236      * when the user requested it. 
1239     public void onFailedSavingCertificate() { 
1240         showDialog(DIALOG_CERT_NOT_SAVED
); 
1245      *  Called when the 'action' button in an IME is pressed ('enter' in software keyboard). 
1247      *  Used to trigger the authorization check when the user presses 'enter' after writing the password. 
1250     public boolean onEditorAction(TextView inputField
, int actionId
, KeyEvent event
) { 
1251         if (inputField 
!= null 
&& inputField
.equals(mPasswordInput
) &&  
1252                 actionId 
== EditorInfo
.IME_ACTION_DONE
) { 
1253             if (mOkButton
.isEnabled()) { 
1254                 mOkButton
.performClick(); 
1257         return false
;   // always return false to grant that the software keyboard is hidden anyway 
1261     private abstract static class RightDrawableOnTouchListener 
implements OnTouchListener  
{ 
1263         private int fuzz 
= 75; 
1269         public boolean onTouch(View view
, MotionEvent event
) { 
1270             Drawable rightDrawable 
= null
; 
1271             if (view 
instanceof TextView
) { 
1272                 Drawable
[] drawables 
= ((TextView
)view
).getCompoundDrawables(); 
1273                 if (drawables
.length 
> 2) { 
1274                     rightDrawable 
= drawables
[2]; 
1277             if (rightDrawable 
!= null
) { 
1278                 final int x 
= (int) event
.getX(); 
1279                 final int y 
= (int) event
.getY(); 
1280                 final Rect bounds 
= rightDrawable
.getBounds(); 
1281                 if (x 
>= (view
.getRight() - bounds
.width() - fuzz
) && x 
<= (view
.getRight() - view
.getPaddingRight() + fuzz
) 
1282                     && y 
>= (view
.getPaddingTop() - fuzz
) && y 
<= (view
.getHeight() - view
.getPaddingBottom()) + fuzz
) { 
1284                     return onDrawableTouch(event
); 
1290         public abstract boolean onDrawableTouch(final MotionEvent event
);