Added notification for failures in manual synchronizations; some improvements in...
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / activity / AuthenticatorActivity.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
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.
13 *
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/>.
16 *
17 */
18
19 package com.owncloud.android.ui.activity;
20
21 import java.net.MalformedURLException;
22 import java.net.URL;
23 import java.net.URLEncoder;
24
25 import com.owncloud.android.AccountUtils;
26 import com.owncloud.android.authenticator.AccountAuthenticator;
27 import com.owncloud.android.authenticator.AuthenticationRunnable;
28 import com.owncloud.android.authenticator.ConnectionCheckerRunnable;
29 import com.owncloud.android.authenticator.OnAuthenticationResultListener;
30 import com.owncloud.android.authenticator.OnConnectCheckListener;
31 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
32 import com.owncloud.android.extensions.ExtensionsAvailableActivity;
33 import com.owncloud.android.utils.OwnCloudVersion;
34
35 import android.accounts.Account;
36 import android.accounts.AccountAuthenticatorActivity;
37 import android.accounts.AccountManager;
38 import android.app.Dialog;
39 import android.app.ProgressDialog;
40 import android.content.ContentResolver;
41 import android.content.DialogInterface;
42 import android.content.Intent;
43 import android.content.SharedPreferences;
44 import android.net.Uri;
45 import android.os.Bundle;
46 import android.os.Handler;
47 import android.preference.PreferenceManager;
48 import android.text.InputType;
49 import android.util.Log;
50 import android.view.View;
51 import android.view.View.OnClickListener;
52 import android.view.View.OnFocusChangeListener;
53 import android.view.Window;
54 import android.widget.ImageView;
55 import android.widget.TextView;
56 import com.owncloud.android.R;
57
58 /**
59 * This Activity is used to add an ownCloud account to the App
60 *
61 * @author Bartek Przybylski
62 *
63 */
64 public class AuthenticatorActivity extends AccountAuthenticatorActivity
65 implements OnAuthenticationResultListener, OnConnectCheckListener,
66 OnFocusChangeListener, OnClickListener {
67 private static final int DIALOG_LOGIN_PROGRESS = 0;
68
69 private static final String TAG = "AuthActivity";
70
71 private Thread mAuthThread;
72 private AuthenticationRunnable mAuthRunnable;
73 private ConnectionCheckerRunnable mConnChkRunnable;
74 private final Handler mHandler = new Handler();
75 private String mBaseUrl;
76
77 private static final String STATUS_TEXT = "STATUS_TEXT";
78 private static final String STATUS_ICON = "STATUS_ICON";
79 private static final String STATUS_CORRECT = "STATUS_CORRECT";
80 private static final String IS_SSL_CONN = "IS_SSL_CONN";
81 private int mStatusText, mStatusIcon;
82 private boolean mStatusCorrect, mIsSslConn;
83
84 public static final String PARAM_USERNAME = "param_Username";
85 public static final String PARAM_HOSTNAME = "param_Hostname";
86
87 @Override
88 protected void onCreate(Bundle savedInstanceState) {
89 super.onCreate(savedInstanceState);
90 getWindow().requestFeature(Window.FEATURE_NO_TITLE);
91 setContentView(R.layout.account_setup);
92 ImageView iv = (ImageView) findViewById(R.id.refreshButton);
93 ImageView iv2 = (ImageView) findViewById(R.id.viewPassword);
94 TextView tv = (TextView) findViewById(R.id.host_URL);
95 TextView tv2 = (TextView) findViewById(R.id.account_password);
96
97 if (savedInstanceState != null) {
98 mStatusIcon = savedInstanceState.getInt(STATUS_ICON);
99 mStatusText = savedInstanceState.getInt(STATUS_TEXT);
100 mStatusCorrect = savedInstanceState.getBoolean(STATUS_CORRECT);
101 mIsSslConn = savedInstanceState.getBoolean(IS_SSL_CONN);
102 setResultIconAndText(mStatusIcon, mStatusText);
103 findViewById(R.id.buttonOK).setEnabled(mStatusCorrect);
104 if (!mStatusCorrect)
105 iv.setVisibility(View.VISIBLE);
106 else
107 iv.setVisibility(View.INVISIBLE);
108
109 } else {
110 mStatusText = mStatusIcon = 0;
111 mStatusCorrect = false;
112 mIsSslConn = false;
113 }
114 iv.setOnClickListener(this);
115 iv2.setOnClickListener(this);
116 tv.setOnFocusChangeListener(this);
117 tv2.setOnFocusChangeListener(this);
118 }
119
120 @Override
121 protected void onSaveInstanceState(Bundle outState) {
122 outState.putInt(STATUS_ICON, mStatusIcon);
123 outState.putInt(STATUS_TEXT, mStatusText);
124 outState.putBoolean(STATUS_CORRECT, mStatusCorrect);
125 super.onSaveInstanceState(outState);
126 }
127
128 @Override
129 protected Dialog onCreateDialog(int id) {
130 Dialog dialog = null;
131 switch (id) {
132 case DIALOG_LOGIN_PROGRESS: {
133 ProgressDialog working_dialog = new ProgressDialog(this);
134 working_dialog.setMessage(getResources().getString(
135 R.string.auth_trying_to_login));
136 working_dialog.setIndeterminate(true);
137 working_dialog.setCancelable(true);
138 working_dialog
139 .setOnCancelListener(new DialogInterface.OnCancelListener() {
140 @Override
141 public void onCancel(DialogInterface dialog) {
142 Log.i(TAG, "Login canceled");
143 if (mAuthThread != null) {
144 mAuthThread.interrupt();
145 finish();
146 }
147 }
148 });
149 dialog = working_dialog;
150 break;
151 }
152 default:
153 Log.e(TAG, "Incorrect dialog called with id = " + id);
154 }
155 return dialog;
156 }
157
158 public void onAuthenticationResult(boolean success, String message) {
159 if (success) {
160 TextView username_text = (TextView) findViewById(R.id.account_username), password_text = (TextView) findViewById(R.id.account_password);
161
162 URL url;
163 try {
164 url = new URL(message);
165 } catch (MalformedURLException e) {
166 // should never happen
167 Log.e(getClass().getName(), "Malformed URL: " + message);
168 return;
169 }
170
171 String username = username_text.getText().toString().trim();
172 String accountName = username + "@" + url.getHost();
173 if (url.getPort() >= 0) {
174 accountName += ":" + url.getPort();
175 }
176 Account account = new Account(accountName,
177 AccountAuthenticator.ACCOUNT_TYPE);
178 AccountManager accManager = AccountManager.get(this);
179 accManager.addAccountExplicitly(account, password_text.getText()
180 .toString(), null);
181
182 // Add this account as default in the preferences, if there is none
183 // already
184 Account defaultAccount = AccountUtils
185 .getCurrentOwnCloudAccount(this);
186 if (defaultAccount == null) {
187 SharedPreferences.Editor editor = PreferenceManager
188 .getDefaultSharedPreferences(this).edit();
189 editor.putString("select_oc_account", accountName);
190 editor.commit();
191 }
192
193 final Intent intent = new Intent();
194 intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE,
195 AccountAuthenticator.ACCOUNT_TYPE);
196 intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
197 intent.putExtra(AccountManager.KEY_AUTHTOKEN,
198 AccountAuthenticator.ACCOUNT_TYPE);
199 intent.putExtra(AccountManager.KEY_USERDATA, username);
200
201 accManager.setUserData(account, AccountAuthenticator.KEY_OC_URL,
202 url.toString());
203 accManager.setUserData(account,
204 AccountAuthenticator.KEY_OC_VERSION, mConnChkRunnable
205 .getDiscoveredVersion().toString());
206 accManager.setUserData(account,
207 AccountAuthenticator.KEY_OC_BASE_URL, mBaseUrl);
208
209 setAccountAuthenticatorResult(intent.getExtras());
210 setResult(RESULT_OK, intent);
211 Bundle bundle = new Bundle();
212 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
213 //getContentResolver().startSync(ProviderTableMeta.CONTENT_URI,
214 // bundle);
215 ContentResolver.requestSync(account, "org.owncloud", bundle);
216
217 /*
218 * if
219 * (mConnChkRunnable.getDiscoveredVersion().compareTo(OwnCloudVersion
220 * .owncloud_v2) >= 0) { Intent i = new Intent(this,
221 * ExtensionsAvailableActivity.class); startActivity(i); }
222 */
223
224 finish();
225 } else {
226 try {
227 dismissDialog(DIALOG_LOGIN_PROGRESS);
228 } catch (IllegalArgumentException e) {
229 // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens
230 }
231 TextView tv = (TextView) findViewById(R.id.account_username);
232 tv.setError(message);
233 }
234 }
235 public void onCancelClick(View view) {
236 setResult(RESULT_CANCELED);
237 finish();
238 }
239
240 public void onOkClick(View view) {
241 String prefix = "";
242 String url = ((TextView) findViewById(R.id.host_URL)).getText()
243 .toString().trim();
244 if (mIsSslConn) {
245 prefix = "https://";
246 } else {
247 prefix = "http://";
248 }
249 if (url.toLowerCase().startsWith("http://")
250 || url.toLowerCase().startsWith("https://")) {
251 prefix = "";
252 }
253 continueConnection(prefix);
254 }
255
256 public void onRegisterClick(View view) {
257 Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse("https://owncloud.com/mobile/new"));
258 setResult(RESULT_CANCELED);
259 startActivity(register);
260 }
261
262 private void continueConnection(String prefix) {
263 String url = ((TextView) findViewById(R.id.host_URL)).getText()
264 .toString().trim();
265 String username = ((TextView) findViewById(R.id.account_username))
266 .getText().toString();
267 String password = ((TextView) findViewById(R.id.account_password))
268 .getText().toString();
269 if (url.endsWith("/"))
270 url = url.substring(0, url.length() - 1);
271
272 URL uri = null;
273 String webdav_path = AccountUtils.getWebdavPath(mConnChkRunnable
274 .getDiscoveredVersion());
275 if (webdav_path == null) {
276 onAuthenticationResult(false, getString(R.string.auth_bad_oc_version_title));
277 return;
278 }
279
280 try {
281 mBaseUrl = prefix + url;
282 String url_str = prefix + url + webdav_path;
283 uri = new URL(url_str);
284 } catch (MalformedURLException e) {
285 // should never happen
286 onAuthenticationResult(false, getString(R.string.auth_incorrect_address_title));
287 return;
288 }
289
290 showDialog(DIALOG_LOGIN_PROGRESS);
291 mAuthRunnable = new AuthenticationRunnable(uri, username, password);
292 mAuthRunnable.setOnAuthenticationResultListener(this, mHandler);
293 mAuthThread = new Thread(mAuthRunnable);
294 mAuthThread.start();
295 }
296
297 @Override
298 public void onConnectionCheckResult(ResultType type) {
299 mStatusText = mStatusIcon = 0;
300 mStatusCorrect = false;
301 String t_url = ((TextView) findViewById(R.id.host_URL)).getText()
302 .toString().trim().toLowerCase();
303
304 switch (type) {
305 case OK_SSL:
306 mIsSslConn = true;
307 mStatusIcon = android.R.drawable.ic_secure;
308 mStatusText = R.string.auth_secure_connection;
309 mStatusCorrect = true;
310 break;
311 case OK_NO_SSL:
312 mIsSslConn = false;
313 mStatusCorrect = true;
314 if (t_url.startsWith("http://") ) {
315 mStatusText = R.string.auth_connection_established;
316 mStatusIcon = R.drawable.ic_ok;
317 } else {
318 mStatusText = R.string.auth_nossl_plain_ok_title;
319 mStatusIcon = android.R.drawable.ic_partial_secure;
320 }
321 break;
322 case BAD_OC_VERSION:
323 mStatusIcon = R.drawable.common_error;
324 mStatusText = R.string.auth_bad_oc_version_title;
325 break;
326 case WRONG_CONNECTION:
327 mStatusIcon = R.drawable.common_error;
328 mStatusText = R.string.auth_wrong_connection_title;
329 break;
330 case TIMEOUT:
331 mStatusIcon = R.drawable.common_error;
332 mStatusText = R.string.auth_timeout_title;
333 break;
334 case INCORRECT_ADDRESS:
335 mStatusIcon = R.drawable.common_error;
336 mStatusText = R.string.auth_incorrect_address_title;
337 break;
338 case SSL_UNVERIFIED_SERVER:
339 mStatusIcon = R.drawable.common_error;
340 mStatusText = R.string.auth_ssl_unverified_server_title;
341 break;
342 case SSL_INIT_ERROR:
343 mStatusIcon = R.drawable.common_error;
344 mStatusText = R.string.auth_ssl_general_error_title;
345 break;
346 case HOST_NOT_AVAILABLE:
347 mStatusIcon = R.drawable.common_error;
348 mStatusText = R.string.auth_unknown_host_title;
349 break;
350 case NO_NETWORK_CONNECTION:
351 mStatusIcon = R.drawable.no_network;
352 mStatusText = R.string.auth_no_net_conn_title;
353 break;
354 case INSTANCE_NOT_CONFIGURED:
355 mStatusIcon = R.drawable.common_error;
356 mStatusText = R.string.auth_not_configured_title;
357 break;
358 case UNKNOWN_ERROR:
359 mStatusIcon = R.drawable.common_error;
360 mStatusText = R.string.auth_unknown_error_title;
361 break;
362 case FILE_NOT_FOUND:
363 mStatusIcon = R.drawable.common_error;
364 mStatusText = R.string.auth_incorrect_path_title;
365 break;
366 default:
367 Log.e(TAG, "Incorrect connection checker result type: " + type);
368 }
369 setResultIconAndText(mStatusIcon, mStatusText);
370 if (!mStatusCorrect)
371 findViewById(R.id.refreshButton).setVisibility(View.VISIBLE);
372 else
373 findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE);
374 findViewById(R.id.buttonOK).setEnabled(mStatusCorrect);
375 }
376
377 @Override
378 public void onFocusChange(View view, boolean hasFocus) {
379 if (view.getId() == R.id.host_URL) {
380 if (!hasFocus) {
381 TextView tv = ((TextView) findViewById(R.id.host_URL));
382 String uri = tv.getText().toString().trim();
383 if (uri.length() != 0) {
384 setResultIconAndText(R.drawable.progress_small,
385 R.string.auth_testing_connection);
386 mConnChkRunnable = new ConnectionCheckerRunnable(uri, this);
387 mConnChkRunnable.setListener(this, mHandler);
388 mAuthThread = new Thread(mConnChkRunnable);
389 mAuthThread.start();
390 } else {
391 findViewById(R.id.refreshButton).setVisibility(
392 View.INVISIBLE);
393 setResultIconAndText(0, 0);
394 }
395 } else {
396 // avoids that the 'connect' button can be clicked if the test was previously passed
397 findViewById(R.id.buttonOK).setEnabled(false);
398 }
399 } else if (view.getId() == R.id.account_password) {
400 ImageView iv = (ImageView) findViewById(R.id.viewPassword);
401 if (hasFocus) {
402 iv.setVisibility(View.VISIBLE);
403 } else {
404 TextView v = (TextView) findViewById(R.id.account_password);
405 int input_type = InputType.TYPE_CLASS_TEXT
406 | InputType.TYPE_TEXT_VARIATION_PASSWORD;
407 v.setInputType(input_type);
408 iv.setVisibility(View.INVISIBLE);
409 }
410 }
411 }
412
413 private void setResultIconAndText(int drawable_id, int text_id) {
414 ImageView iv = (ImageView) findViewById(R.id.action_indicator);
415 TextView tv = (TextView) findViewById(R.id.status_text);
416
417 if (drawable_id == 0 && text_id == 0) {
418 iv.setVisibility(View.INVISIBLE);
419 tv.setVisibility(View.INVISIBLE);
420 } else {
421 iv.setImageResource(drawable_id);
422 tv.setText(text_id);
423 iv.setVisibility(View.VISIBLE);
424 tv.setVisibility(View.VISIBLE);
425 }
426 }
427
428 @Override
429 public void onClick(View v) {
430 if (v.getId() == R.id.refreshButton) {
431 onFocusChange(findViewById(R.id.host_URL), false);
432 } else if (v.getId() == R.id.viewPassword) {
433 TextView view = (TextView) findViewById(R.id.account_password);
434 int input_type = InputType.TYPE_CLASS_TEXT
435 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
436 view.setInputType(input_type);
437 }
438 }
439 }