/**
* Android ownCloud News
*
* @author David Luhmer
* @copyright 2013 David Luhmer david-dev@live.de
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.luhmer.owncloudnewsreader;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.InputType;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import java.net.MalformedURLException;
import java.net.URL;
import butterknife.Bind;
import butterknife.ButterKnife;
import de.luhmer.owncloud.accountimporter.ImportAccountsDialogFragment;
import de.luhmer.owncloud.accountimporter.helper.AccountImporter;
import de.luhmer.owncloud.accountimporter.helper.OwnCloudAccount;
import de.luhmer.owncloud.accountimporter.interfaces.IAccountImport;
import de.luhmer.owncloudnewsreader.authentication.AuthenticatorActivity;
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm;
import de.luhmer.owncloudnewsreader.reader.HttpJsonRequest;
import de.luhmer.owncloudnewsreader.reader.owncloud.OwnCloudReaderMethods;
import de.luhmer.owncloudnewsreader.reader.owncloud.OwnCloud_Reader;
/**
* Activity which displays a login screen to the user, offering registration as
* well.
*/
public class LoginDialogFragment extends DialogFragment implements IAccountImport {
static LoginDialogFragment instance;
public static LoginDialogFragment getInstance() {
if(instance == null)
instance = new LoginDialogFragment();
return instance;
}
/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
private UserLoginTask mAuthTask = null;
private Activity mActivity;
// Values for email and password at the time of the login attempt.
private String mUsername;
private String mPassword;
private String mOc_root_path;
private boolean mCbDisableHostnameVerification;
private boolean showImportAccountButton;
// UI references.
@Bind(R.id.username) EditText mUsernameView;
@Bind(R.id.password) EditText mPasswordView;
@Bind(R.id.edt_owncloudRootPath) EditText mOc_root_path_View;
@Bind(R.id.cb_AllowAllSSLCertificates) CheckBox mCbDisableHostnameVerificationView;
@Bind(R.id.imgView_ShowPassword) ImageView mImageViewShowPwd;
boolean mPasswordVisible = false;
ProgressDialog mDialogLogin;
@Override
public void accountAccessGranted(OwnCloudAccount account) {
mUsernameView.setText(account.getUsername());
mPasswordView.setText(account.getPassword());
mOc_root_path_View.setText(account.getUrl());
}
public interface LoginSuccessfullListener {
void LoginSucceeded();
}
LoginSuccessfullListener listener;
public LoginDialogFragment() {
}
public void setActivity(Activity mActivity) {
this.mActivity = mActivity;
}
/**
* @param listener the listener to set
*/
public void setListener(LoginSuccessfullListener listener) {
this.listener = listener;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
showImportAccountButton = AccountImporter.findAccounts(getActivity()).size() > 0;
//setRetainInstance(true);
// Build the dialog and set up the button click handlers
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.dialog_signin, null);
ButterKnife.bind(this, view);
builder.setView(view);
builder.setTitle(getString(R.string.action_sign_in_short));
builder.setPositiveButton(getString(R.string.action_sign_in_short), null);
if(showImportAccountButton) {
builder.setNeutralButton(getString(R.string.import_account), null);
}
mImageViewShowPwd.setOnClickListener(ImgViewShowPasswordListener);
mPasswordView.addTextChangedListener(PasswordTextChangedListener);
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
mUsername = mPrefs.getString(SettingsActivity.EDT_USERNAME_STRING, "");
mPassword = mPrefs.getString(SettingsActivity.EDT_PASSWORD_STRING, "");
mOc_root_path = mPrefs.getString(SettingsActivity.EDT_OWNCLOUDROOTPATH_STRING, "");
mCbDisableHostnameVerification = mPrefs.getBoolean(SettingsActivity.CB_DISABLE_HOSTNAME_VERIFICATION_STRING, false);
if(!mPassword.isEmpty()) {
mImageViewShowPwd.setVisibility(View.GONE);
}
// Set up the login form.
mUsernameView.setText(mUsername);
mPasswordView.setText(mPassword);
mOc_root_path_View.setText(mOc_root_path);
mCbDisableHostnameVerificationView.setChecked(mCbDisableHostnameVerification);
mCbDisableHostnameVerificationView.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
mPrefs.edit()
.putBoolean(SettingsActivity.CB_DISABLE_HOSTNAME_VERIFICATION_STRING, isChecked)
.commit();
}
});
AlertDialog dialog = builder.create();
// Set dialog to resize when soft keyboard pops up
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
return dialog;
}
@Override
public void onStart() {
super.onStart();
final AlertDialog dialog = (AlertDialog) getDialog();
// Override the onClickListeners, as the default implementation would dismiss the dialog
if (dialog != null) {
if (showImportAccountButton) {
Button neutralButton = dialog.getButton(DialogInterface.BUTTON_NEUTRAL);
neutralButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ImportAccountsDialogFragment.show(getActivity(), LoginDialogFragment.this);
}
});
// Limit button width to not push positive button out of view
neutralButton.setMaxEms(10);
}
Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
attemptLogin();
}
});
}
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
if(mActivity instanceof AuthenticatorActivity)
mActivity.finish();
}
private TextWatcher PasswordTextChangedListener = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if(s.toString().isEmpty()) {
mImageViewShowPwd.setVisibility(View.VISIBLE);
}
}
};
private View.OnClickListener ImgViewShowPasswordListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
mPasswordVisible = !mPasswordVisible;
if(mPasswordVisible) {
mPasswordView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
} else {
mPasswordView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
}
}
};
private ProgressDialog BuildPendingDialogWhileLoggingIn()
{
ProgressDialog pDialog = new ProgressDialog(getActivity());
pDialog.setTitle(getString(R.string.login_progress_signing_in));
return pDialog;
}
/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
public void attemptLogin() {
if (mAuthTask != null) {
return;
}
// Reset errors.
mUsernameView.setError(null);
mPasswordView.setError(null);
mOc_root_path_View.setError(null);
// Store values at the time of the login attempt.
mUsername = mUsernameView.getText().toString().trim();
mPassword = mPasswordView.getText().toString();
mOc_root_path = mOc_root_path_View.getText().toString().trim();
boolean cancel = false;
View focusView = null;
// Check for a valid password.
if (TextUtils.isEmpty(mPassword)) {
mPasswordView.setError(getString(R.string.error_field_required));
focusView = mPasswordView;
cancel = true;
}
// Check for a valid email address.
if (TextUtils.isEmpty(mUsername)) {
mUsernameView.setError(getString(R.string.error_field_required));
focusView = mUsernameView;
cancel = true;
}
if (TextUtils.isEmpty(mOc_root_path)) {
mOc_root_path_View.setError(getString(R.string.error_field_required));
focusView = mOc_root_path_View;
cancel = true;
} else {
try {
URL url = new URL(mOc_root_path);
if(!url.getProtocol().equals("https"))
ShowAlertDialog(getString(R.string.login_dialog_title_security_warning),
getString(R.string.login_dialog_text_security_warning), getActivity());
} catch (MalformedURLException e) {
mOc_root_path_View.setError(getString(R.string.error_invalid_url));
focusView = mOc_root_path_View;
cancel = true;
//e.printStackTrace();
}
}
if (cancel) {
// There was an error; don't attempt login and focus the first
// form field with an error.
focusView.requestFocus();
} else {
mAuthTask = new UserLoginTask(mUsername, mPassword, mOc_root_path);
mAuthTask.execute((Void) null);
mDialogLogin = BuildPendingDialogWhileLoggingIn();
mDialogLogin.show();
}
}
/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
public class UserLoginTask extends AsyncTask<Void, Void, Integer> {
String username;
String password;
String oc_root_path;
String exception_message = "";
public UserLoginTask(String username, String password, String oc_root_path) {
this.username = username;
this.password = password;
this.oc_root_path = oc_root_path;
//Re-init client in order to set the hostname verifier correctly
HttpJsonRequest.init(getActivity());
HttpJsonRequest.getInstance().setCredentials(username, password, oc_root_path);
OwnCloud_Reader.getInstance().resetApi();
}
@Override
protected Integer doInBackground(Void... params) {
try {
String _version = OwnCloudReaderMethods.GetVersionNumber(HttpJsonRequest.getInstance().getRootUrl());
if(_version != null)
{
_version = _version.replace(".", "");
return Integer.parseInt(_version);
}
} catch (Exception e) {
if(e.getLocalizedMessage() != null)
exception_message = e.getLocalizedMessage();
else if(e instanceof NullPointerException)
exception_message = getString(R.string.login_dialog_text_something_went_wrong);
return -1;
}
return 0;
}
@Override
protected void onPostExecute(final Integer versionCode) {
mAuthTask = null;
mDialogLogin.dismiss();
if(versionCode == -1 && exception_message.equals("Value <!DOCTYPE of type java.lang.String cannot be converted to JSONObject")) {
if(isAdded()) {
ShowAlertDialog(getString(R.string.login_dialog_title_error), getString(R.string.login_dialog_text_not_compatible), getActivity());
}
} else if(versionCode == -1) {
if(isAdded()) {
ShowAlertDialog(getString(R.string.login_dialog_title_error), exception_message, getActivity());
}
} else if(versionCode == 0){
if(isAdded()) {
ShowAlertDialog(getString(R.string.login_dialog_title_error), getString(R.string.login_dialog_text_zero_version_code), getActivity());
}
} else {
//Reset Database
DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(getActivity());
dbConn.resetDatabase();
//dbConn.closeDatabase();
//LoginFragment.this.dismiss();
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
Editor editor = mPrefs.edit();
editor.putString(SettingsActivity.EDT_OWNCLOUDROOTPATH_STRING, oc_root_path);
editor.putString(SettingsActivity.EDT_PASSWORD_STRING, password);
editor.putString(SettingsActivity.EDT_USERNAME_STRING, username);
editor.commit();
if(listener != null)
listener.LoginSucceeded();
LoginDialogFragment.this.getDialog().cancel();
if(mActivity instanceof AuthenticatorActivity)
mActivity.finish();
}
}
@Override
protected void onCancelled() {
mAuthTask = null;
}
}
public static void ShowAlertDialog(String title, String text, Activity activity)
{
// Linkify the message
final SpannableString s = new SpannableString(text);
Linkify.addLinks(s, Linkify.ALL);
AlertDialog aDialog = new AlertDialog.Builder(activity)
.setTitle(title)
.setMessage(s)
.setPositiveButton(activity.getString(android.R.string.ok) , null)
.create();
aDialog.show();
// Make the textview clickable. Must be called after show()
((TextView)aDialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
}
}