|
1 /** |
|
2 * Copyright (c) 2012-2013, Gerald Garcia |
|
3 * |
|
4 * This file is part of Andoid Caldav Sync Adapter Free. |
|
5 * |
|
6 * Andoid Caldav Sync Adapter Free is free software: you can redistribute |
|
7 * it and/or modify it under the terms of the GNU General Public License |
|
8 * as published by the Free Software Foundation, either version 3 of the |
|
9 * License, or at your option any later version. |
|
10 * |
|
11 * Andoid Caldav Sync Adapter Free is distributed in the hope that |
|
12 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied |
|
13 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 * GNU General Public License for more details. |
|
15 * |
|
16 * You should have received a copy of the GNU General Public License |
|
17 * along with Andoid Caldav Sync Adapter Free. |
|
18 * If not, see <http://www.gnu.org/licenses/>. |
|
19 * |
|
20 */ |
|
21 |
|
22 package org.gege.caldavsyncadapter.authenticator; |
|
23 |
|
24 import java.io.IOException; |
|
25 import java.io.UnsupportedEncodingException; |
|
26 import java.net.MalformedURLException; |
|
27 import java.net.URISyntaxException; |
|
28 |
|
29 import javax.xml.parsers.ParserConfigurationException; |
|
30 |
|
31 import org.apache.http.conn.HttpHostConnectException; |
|
32 import org.gege.caldavsyncadapter.R; |
|
33 import org.gege.caldavsyncadapter.caldav.CaldavFacade; |
|
34 import org.gege.caldavsyncadapter.caldav.CaldavFacade.TestConnectionResult; |
|
35 import org.xml.sax.SAXException; |
|
36 |
|
37 import android.accounts.Account; |
|
38 import android.accounts.AccountManager; |
|
39 import android.animation.Animator; |
|
40 import android.animation.AnimatorListenerAdapter; |
|
41 import android.annotation.TargetApi; |
|
42 import android.app.Activity; |
|
43 import android.content.Context; |
|
44 import android.content.pm.PackageManager.NameNotFoundException; |
|
45 import android.os.AsyncTask; |
|
46 import android.os.Build; |
|
47 import android.os.Bundle; |
|
48 import android.text.TextUtils; |
|
49 import android.util.Log; |
|
50 import android.view.KeyEvent; |
|
51 import android.view.Menu; |
|
52 import android.view.View; |
|
53 import android.view.inputmethod.EditorInfo; |
|
54 import android.widget.EditText; |
|
55 import android.widget.TextView; |
|
56 import android.widget.Toast; |
|
57 |
|
58 /** |
|
59 * Activity which displays a login screen to the user, offering registration as |
|
60 * well. |
|
61 */ |
|
62 public class AuthenticatorActivity extends Activity { |
|
63 |
|
64 private static final String TAG = "AuthenticatorActivity"; |
|
65 |
|
66 private static final String ACCOUNT_TYPE = "org.gege.caldavsyncadapter.account"; |
|
67 |
|
68 public static final String USER_DATA_URL_KEY = "USER_DATA_URL_KEY"; |
|
69 public static final String USER_DATA_USERNAME = "USER_DATA_USERNAME"; |
|
70 public static final String USER_DATA_VERSION = "USER_DATA_VERSION"; |
|
71 public static final String CURRENT_USER_DATA_VERSION = "1"; |
|
72 |
|
73 public static final String ACCOUNT_NAME_SPLITTER = "@"; |
|
74 |
|
75 /** |
|
76 * The default email to populate the email field with. |
|
77 */ |
|
78 public static final String EXTRA_EMAIL = "com.example.android.authenticatordemo.extra.EMAIL"; |
|
79 |
|
80 /** |
|
81 * Keep track of the login task to ensure we can cancel it if requested. |
|
82 */ |
|
83 private UserLoginTask mAuthTask = null; |
|
84 |
|
85 // Values for email and password at the time of the login attempt. |
|
86 private String mUser; |
|
87 private String mPassword; |
|
88 private Context mContext; |
|
89 |
|
90 // UI references. |
|
91 private EditText mUserView; |
|
92 private EditText mPasswordView; |
|
93 private View mLoginFormView; |
|
94 private View mLoginStatusView; |
|
95 private TextView mLoginStatusMessageView; |
|
96 |
|
97 private AccountManager mAccountManager; |
|
98 |
|
99 private String mURL; |
|
100 private EditText mURLView; |
|
101 |
|
102 private String mAccountname; |
|
103 private EditText mAccountnameView; |
|
104 |
|
105 public AuthenticatorActivity() { |
|
106 super(); |
|
107 |
|
108 } |
|
109 |
|
110 @Override |
|
111 protected void onCreate(Bundle savedInstanceState) { |
|
112 super.onCreate(savedInstanceState); |
|
113 |
|
114 mAccountManager = AccountManager.get(this); |
|
115 |
|
116 setContentView(R.layout.activity_authenticator); |
|
117 |
|
118 // Set up the login form. |
|
119 mUser = getIntent().getStringExtra(EXTRA_EMAIL); |
|
120 mUserView = (EditText) findViewById(R.id.user); |
|
121 mUserView.setText(mUser); |
|
122 |
|
123 mContext = getBaseContext(); |
|
124 |
|
125 mPasswordView = (EditText) findViewById(R.id.password); |
|
126 mPasswordView |
|
127 .setOnEditorActionListener(new TextView.OnEditorActionListener() { |
|
128 @Override |
|
129 public boolean onEditorAction(TextView textView, int id, |
|
130 KeyEvent keyEvent) { |
|
131 if (id == R.id.login || id == EditorInfo.IME_NULL) { |
|
132 attemptLogin(); |
|
133 return true; |
|
134 } |
|
135 return false; |
|
136 } |
|
137 }); |
|
138 |
|
139 |
|
140 mURLView = (EditText) findViewById(R.id.url); |
|
141 |
|
142 mAccountnameView = (EditText) findViewById(R.id.accountname); |
|
143 |
|
144 mLoginFormView = findViewById(R.id.login_form); |
|
145 mLoginStatusView = findViewById(R.id.login_status); |
|
146 mLoginStatusMessageView = (TextView) findViewById(R.id.login_status_message); |
|
147 |
|
148 findViewById(R.id.sign_in_button).setOnClickListener( |
|
149 new View.OnClickListener() { |
|
150 @Override |
|
151 public void onClick(View view) { |
|
152 attemptLogin(); |
|
153 } |
|
154 }); |
|
155 |
|
156 |
|
157 } |
|
158 |
|
159 @Override |
|
160 public boolean onCreateOptionsMenu(Menu menu) { |
|
161 super.onCreateOptionsMenu(menu); |
|
162 getMenuInflater().inflate(R.menu.activity_authenticator, menu); |
|
163 return true; |
|
164 } |
|
165 |
|
166 /** |
|
167 * Attempts to sign in or register the account specified by the login form. |
|
168 * If there are form errors (invalid email, missing fields, etc.), the |
|
169 * errors are presented and no actual login attempt is made. |
|
170 */ |
|
171 public void attemptLogin() { |
|
172 if (mAuthTask != null) { |
|
173 return; |
|
174 } |
|
175 |
|
176 // Reset errors. |
|
177 mUserView.setError(null); |
|
178 mPasswordView.setError(null); |
|
179 |
|
180 // Store values at the time of the login attempt. |
|
181 mUser = mUserView.getText().toString(); |
|
182 mPassword = mPasswordView.getText().toString(); |
|
183 mURL = mURLView.getText().toString(); |
|
184 mAccountname = mAccountnameView.getText().toString(); |
|
185 |
|
186 boolean cancel = false; |
|
187 View focusView = null; |
|
188 |
|
189 if (!mAccountname.equals("")) { |
|
190 Account TestAccount = new Account(mAccountname, ACCOUNT_TYPE); |
|
191 String TestUrl = mAccountManager.getUserData(TestAccount, AuthenticatorActivity.USER_DATA_URL_KEY); |
|
192 if (TestUrl != null) { |
|
193 mAccountnameView.setError(getString(R.string.error_account_already_in_use)); |
|
194 focusView = mAccountnameView; |
|
195 cancel = true; |
|
196 } |
|
197 } |
|
198 |
|
199 // Check for a valid password. |
|
200 if (TextUtils.isEmpty(mPassword)) { |
|
201 mPasswordView.setError(getString(R.string.error_field_required)); |
|
202 focusView = mPasswordView; |
|
203 cancel = true; |
|
204 } else if (mPassword.length() < 4) { |
|
205 mPasswordView.setError(getString(R.string.error_invalid_password)); |
|
206 focusView = mPasswordView; |
|
207 cancel = true; |
|
208 } |
|
209 |
|
210 // Check for a valid email address. |
|
211 if (TextUtils.isEmpty(mUser)) { |
|
212 mUserView.setError(getString(R.string.error_field_required)); |
|
213 focusView = mUserView; |
|
214 cancel = true; |
|
215 } |
|
216 //else if (!mUser.contains("@")) { |
|
217 // mUserView.setError(getString(R.string.error_invalid_email)); |
|
218 // focusView = mUserView; |
|
219 // cancel = true; |
|
220 //} |
|
221 |
|
222 if (cancel) { |
|
223 // There was an error; don't attempt login and focus the first |
|
224 // form field with an error. |
|
225 focusView.requestFocus(); |
|
226 } else { |
|
227 // Show a progress spinner, and kick off a background task to |
|
228 // perform the user login attempt. |
|
229 mLoginStatusMessageView.setText(R.string.login_progress_signing_in); |
|
230 showProgress(true); |
|
231 mAuthTask = new UserLoginTask(); |
|
232 mAuthTask.execute((Void) null); |
|
233 } |
|
234 } |
|
235 |
|
236 /** |
|
237 * Shows the progress UI and hides the login form. |
|
238 */ |
|
239 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) |
|
240 private void showProgress(final boolean show) { |
|
241 // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow |
|
242 // for very easy animations. If available, use these APIs to fade-in |
|
243 // the progress spinner. |
|
244 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { |
|
245 int shortAnimTime = getResources().getInteger( |
|
246 android.R.integer.config_shortAnimTime); |
|
247 |
|
248 mLoginStatusView.setVisibility(View.VISIBLE); |
|
249 mLoginStatusView.animate().setDuration(shortAnimTime) |
|
250 .alpha(show ? 1 : 0) |
|
251 .setListener(new AnimatorListenerAdapter() { |
|
252 @Override |
|
253 public void onAnimationEnd(Animator animation) { |
|
254 mLoginStatusView.setVisibility(show ? View.VISIBLE |
|
255 : View.GONE); |
|
256 } |
|
257 }); |
|
258 |
|
259 mLoginFormView.setVisibility(View.VISIBLE); |
|
260 mLoginFormView.animate().setDuration(shortAnimTime) |
|
261 .alpha(show ? 0 : 1) |
|
262 .setListener(new AnimatorListenerAdapter() { |
|
263 @Override |
|
264 public void onAnimationEnd(Animator animation) { |
|
265 mLoginFormView.setVisibility(show ? View.GONE |
|
266 : View.VISIBLE); |
|
267 } |
|
268 }); |
|
269 } else { |
|
270 // The ViewPropertyAnimator APIs are not available, so simply show |
|
271 // and hide the relevant UI components. |
|
272 mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE); |
|
273 mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); |
|
274 } |
|
275 } |
|
276 |
|
277 |
|
278 protected enum LoginResult { |
|
279 MalformedURLException, |
|
280 GeneralSecurityException, |
|
281 UnkonwnException, |
|
282 WrongCredentials, |
|
283 InvalidResponse, |
|
284 WrongUrl, |
|
285 ConnectionRefused, |
|
286 Success_Calendar, |
|
287 Success_Collection, |
|
288 Account_Already_In_Use |
|
289 } |
|
290 |
|
291 |
|
292 /** |
|
293 * Represents an asynchronous login/registration task used to authenticate |
|
294 * the user. |
|
295 */ |
|
296 public class UserLoginTask extends AsyncTask<Void, Void, LoginResult> { |
|
297 |
|
298 @Override |
|
299 protected LoginResult doInBackground(Void... params) { |
|
300 |
|
301 TestConnectionResult result = null; |
|
302 |
|
303 try { |
|
304 CaldavFacade facade = new CaldavFacade(mUser, mPassword, mURL); |
|
305 String version = ""; |
|
306 try { |
|
307 version = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName; |
|
308 } catch (NameNotFoundException e) { |
|
309 version = "unknown"; |
|
310 e.printStackTrace(); |
|
311 } |
|
312 facade.setVersion(version); |
|
313 result = facade.testConnection(); |
|
314 Log.i(TAG, "testConnection status="+result); |
|
315 } catch (HttpHostConnectException e) { |
|
316 Log.w(TAG,"testConnection", e); |
|
317 return LoginResult.ConnectionRefused; |
|
318 } catch (MalformedURLException e) { |
|
319 Log.w(TAG,"testConnection", e); |
|
320 return LoginResult.MalformedURLException; |
|
321 } catch (UnsupportedEncodingException e) { |
|
322 Log.w(TAG,"testConnection", e); |
|
323 return LoginResult.UnkonwnException; |
|
324 } catch (ParserConfigurationException e) { |
|
325 Log.w(TAG,"testConnection", e); |
|
326 return LoginResult.UnkonwnException; |
|
327 } catch (SAXException e) { |
|
328 Log.w(TAG,"testConnection", e); |
|
329 return LoginResult.InvalidResponse; |
|
330 } catch (IOException e) { |
|
331 Log.w(TAG,"testConnection", e); |
|
332 return LoginResult.UnkonwnException; |
|
333 } catch (URISyntaxException e) { |
|
334 Log.w(TAG,"testConnection", e); |
|
335 return LoginResult.MalformedURLException; |
|
336 } |
|
337 |
|
338 if (result == null) { |
|
339 return LoginResult.UnkonwnException; |
|
340 } |
|
341 |
|
342 switch (result) { |
|
343 |
|
344 case SUCCESS: |
|
345 boolean OldAccount = false; |
|
346 LoginResult Result = LoginResult.Success_Calendar; |
|
347 |
|
348 if (OldAccount) { |
|
349 final Account account = new Account(mUser, ACCOUNT_TYPE); |
|
350 if (mAccountManager.addAccountExplicitly(account, mPassword, null)) { |
|
351 Log.v(TAG,"new account created"); |
|
352 mAccountManager.setUserData(account, USER_DATA_URL_KEY, mURL); |
|
353 } else { |
|
354 Log.v(TAG,"no new account created"); |
|
355 Result = LoginResult.Account_Already_In_Use; |
|
356 } |
|
357 } else { |
|
358 final Account account; |
|
359 if (mAccountname.equals("")) { |
|
360 account = new Account(mUser + ACCOUNT_NAME_SPLITTER + mURL, ACCOUNT_TYPE); |
|
361 } else { |
|
362 account = new Account(mAccountname, ACCOUNT_TYPE); |
|
363 } |
|
364 if (mAccountManager.addAccountExplicitly(account, mPassword, null)) { |
|
365 Log.v(TAG,"new account created"); |
|
366 mAccountManager.setUserData(account, USER_DATA_URL_KEY, mURL); |
|
367 mAccountManager.setUserData(account, USER_DATA_USERNAME, mUser); |
|
368 mAccountManager.setUserData(account, USER_DATA_VERSION, CURRENT_USER_DATA_VERSION); |
|
369 } else { |
|
370 Log.v(TAG,"no new account created"); |
|
371 Result = LoginResult.Account_Already_In_Use; |
|
372 } |
|
373 } |
|
374 |
|
375 return Result; |
|
376 |
|
377 case WRONG_CREDENTIAL: |
|
378 return LoginResult.WrongCredentials; |
|
379 |
|
380 case WRONG_SERVER_STATUS: |
|
381 return LoginResult.InvalidResponse; |
|
382 |
|
383 case WRONG_URL: |
|
384 return LoginResult.WrongUrl; |
|
385 |
|
386 case WRONG_ANSWER: |
|
387 return LoginResult.InvalidResponse; |
|
388 |
|
389 default: |
|
390 return LoginResult.UnkonwnException; |
|
391 |
|
392 } |
|
393 |
|
394 } |
|
395 |
|
396 |
|
397 @Override |
|
398 protected void onPostExecute(final LoginResult result) { |
|
399 mAuthTask = null; |
|
400 showProgress(false); |
|
401 |
|
402 int duration = Toast.LENGTH_SHORT; |
|
403 Toast toast = null; |
|
404 |
|
405 switch (result) { |
|
406 case Success_Calendar: |
|
407 toast = Toast.makeText(getApplicationContext(), R.string.success_calendar, duration); |
|
408 toast.show(); |
|
409 finish(); |
|
410 break; |
|
411 |
|
412 case Success_Collection: |
|
413 toast = Toast.makeText(getApplicationContext(), R.string.success_collection, duration); |
|
414 toast.show(); |
|
415 finish(); |
|
416 break; |
|
417 |
|
418 case MalformedURLException: |
|
419 |
|
420 toast = Toast.makeText(getApplicationContext(), R.string.error_incorrect_url_format, duration); |
|
421 toast.show(); |
|
422 mURLView.setError(getString(R.string.error_incorrect_url_format)); |
|
423 mURLView.requestFocus(); |
|
424 break; |
|
425 case InvalidResponse: |
|
426 toast = Toast.makeText(getApplicationContext(), R.string.error_invalid_server_answer, duration); |
|
427 toast.show(); |
|
428 mURLView.setError(getString(R.string.error_invalid_server_answer)); |
|
429 mURLView.requestFocus(); |
|
430 break; |
|
431 case WrongUrl: |
|
432 toast = Toast.makeText(getApplicationContext(), R.string.error_wrong_url, duration); |
|
433 toast.show(); |
|
434 mURLView.setError(getString(R.string.error_wrong_url)); |
|
435 mURLView.requestFocus(); |
|
436 break; |
|
437 |
|
438 case WrongCredentials: |
|
439 mPasswordView.setError(getString(R.string.error_incorrect_password)); |
|
440 mPasswordView.requestFocus(); |
|
441 break; |
|
442 |
|
443 case ConnectionRefused: |
|
444 toast = Toast.makeText(getApplicationContext(), R.string.error_connection_refused, duration); |
|
445 toast.show(); |
|
446 mURLView.setError(getString(R.string.error_connection_refused)); |
|
447 mURLView.requestFocus(); |
|
448 break; |
|
449 case Account_Already_In_Use: |
|
450 toast = Toast.makeText(getApplicationContext(), R.string.error_account_already_in_use, duration); |
|
451 toast.show(); |
|
452 mURLView.setError(getString(R.string.error_account_already_in_use)); |
|
453 mURLView.requestFocus(); |
|
454 break; |
|
455 default: |
|
456 toast = Toast.makeText(getApplicationContext(), R.string.error_unkown_error, duration); |
|
457 toast.show(); |
|
458 mURLView.setError(getString(R.string.error_unkown_error)); |
|
459 mURLView.requestFocus(); |
|
460 break; |
|
461 } |
|
462 |
|
463 |
|
464 |
|
465 |
|
466 } |
|
467 |
|
468 @Override |
|
469 protected void onCancelled() { |
|
470 mAuthTask = null; |
|
471 showProgress(false); |
|
472 } |
|
473 } |
|
474 } |