diff --git a/build.gradle b/build.gradle
index be0b562778..0ef1ad7b45 100644
--- a/build.gradle
+++ b/build.gradle
@@ -91,6 +91,7 @@ android {
testInstrumentationRunnerArgument "TEST_SERVER_URL", "\"$System.env.OCTEST_SERVER_BASE_URL\""
multiDexEnabled true
+ vectorDrawables.useSupportLibrary = true
versionCode versionMajor * 10000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild
diff --git a/drawable_resources/first_run_files.svg b/drawable_resources/first_run_files.svg
new file mode 100644
index 0000000000..cce515f084
--- /dev/null
+++ b/drawable_resources/first_run_files.svg
@@ -0,0 +1,56 @@
+
+
diff --git a/drawable_resources/first_run_groupware.svg b/drawable_resources/first_run_groupware.svg
new file mode 100644
index 0000000000..a653389300
--- /dev/null
+++ b/drawable_resources/first_run_groupware.svg
@@ -0,0 +1,56 @@
+
+
diff --git a/drawable_resources/first_run_talk.svg b/drawable_resources/first_run_talk.svg
new file mode 100644
index 0000000000..5c38b27b72
--- /dev/null
+++ b/drawable_resources/first_run_talk.svg
@@ -0,0 +1,66 @@
+
+
+
+
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index b1f12155a7..be8ff7c521 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -254,6 +254,10 @@
android:configChanges="orientation|screenSize|keyboardHidden"/>
+
diff --git a/src/main/java/com/owncloud/android/MainApp.java b/src/main/java/com/owncloud/android/MainApp.java
index 6c253cc529..6009d8a727 100644
--- a/src/main/java/com/owncloud/android/MainApp.java
+++ b/src/main/java/com/owncloud/android/MainApp.java
@@ -662,4 +662,9 @@ public class MainApp extends MultiDexApplication {
}
}
}
+
+ static public int getLastSeenVersionCode(Context context) {
+ SharedPreferences pref = android.preference.PreferenceManager.getDefaultSharedPreferences(context);
+ return pref.getInt(WhatsNewActivity.KEY_LAST_SEEN_VERSION_CODE, 0);
+ }
}
diff --git a/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java b/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java
index 61738a9110..abe3842fb9 100644
--- a/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java
+++ b/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java
@@ -1,2288 +1,2307 @@
-/*
- * ownCloud Android client application
- *
- * @author Bartek Przybylski
- * @author David A. Velasco
- * @author masensio
- * @author Mario Danic
- * Copyright (C) 2012 Bartek Przybylski
- * Copyright (C) 2015 ownCloud Inc.
- * Copyright (C) 2017 Mario Danic
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2,
- * as published by the Free Software Foundation.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * All changes by Mario Danic are distributed under the following terms:
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * at your option) any later version.
- *
- * This program 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 program. If not, see .
- *
- */
-
-package com.owncloud.android.authentication;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.annotation.SuppressLint;
-import android.app.Dialog;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.SharedPreferences;
-import android.content.pm.ActivityInfo;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.net.http.SslError;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.preference.PreferenceManager;
-import android.support.annotation.Nullable;
-import android.support.design.widget.Snackbar;
-import android.support.design.widget.TextInputLayout;
-import android.support.v4.app.DialogFragment;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentTransaction;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.AndroidRuntimeException;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnFocusChangeListener;
-import android.view.View.OnTouchListener;
-import android.view.inputmethod.EditorInfo;
-import android.webkit.CookieManager;
-import android.webkit.CookieSyncManager;
-import android.webkit.HttpAuthHandler;
-import android.webkit.SslErrorHandler;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.ImageButton;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
-
-import com.owncloud.android.MainApp;
-import com.owncloud.android.R;
-import com.owncloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener;
-import com.owncloud.android.lib.common.OwnCloudAccount;
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
-import com.owncloud.android.lib.common.OwnCloudCredentials;
-import com.owncloud.android.lib.common.OwnCloudCredentialsFactory;
-import com.owncloud.android.lib.common.UserInfo;
-import com.owncloud.android.lib.common.accounts.AccountTypeUtils;
-import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
-import com.owncloud.android.lib.common.accounts.AccountUtils.Constants;
-import com.owncloud.android.lib.common.network.CertificateCombinedException;
-import com.owncloud.android.lib.common.network.NetworkUtils;
-import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
-import com.owncloud.android.lib.common.operations.RemoteOperation;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.status.OwnCloudVersion;
-import com.owncloud.android.lib.resources.users.GetRemoteUserInfoOperation;
-import com.owncloud.android.operations.DetectAuthenticationMethodOperation.AuthenticationMethod;
-import com.owncloud.android.operations.GetServerInfoOperation;
-import com.owncloud.android.operations.OAuth2GetAccessToken;
-import com.owncloud.android.services.OperationsService;
-import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
-import com.owncloud.android.ui.components.CustomEditText;
-import com.owncloud.android.ui.dialog.CredentialsDialogFragment;
-import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
-import com.owncloud.android.ui.dialog.SamlWebViewDialog;
-import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
-import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener;
-import com.owncloud.android.utils.DisplayUtils;
-import com.owncloud.android.utils.ErrorMessageAdapter;
-
-import java.io.InputStream;
-import java.net.URLDecoder;
-import java.security.cert.X509Certificate;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * This Activity is used to add an ownCloud account to the App
- */
-public class AuthenticatorActivity extends AccountAuthenticatorActivity
- implements OnRemoteOperationListener, OnFocusChangeListener, OnEditorActionListener,
- SsoWebViewClientListener, OnSslUntrustedCertListener,
- AuthenticatorAsyncTask.OnAuthenticatorTaskListener {
-
- private static final String TAG = AuthenticatorActivity.class.getSimpleName();
-
- public static final String EXTRA_ACTION = "ACTION";
- public static final String EXTRA_ACCOUNT = "ACCOUNT";
-
- private static final String KEY_AUTH_TOKEN_TYPE = "AUTH_TOKEN_TYPE";
-
- private static final String KEY_HOST_URL_TEXT = "HOST_URL_TEXT";
- private static final String KEY_OC_VERSION = "OC_VERSION";
- private static final String KEY_SERVER_VALID = "SERVER_VALID";
- private static final String KEY_SERVER_CHECKED = "SERVER_CHECKED";
- private static final String KEY_SERVER_STATUS_TEXT = "SERVER_STATUS_TEXT";
- private static final String KEY_SERVER_STATUS_ICON = "SERVER_STATUS_ICON";
- private static final String KEY_IS_SSL_CONN = "IS_SSL_CONN";
- private static final String KEY_PASSWORD_EXPOSED = "PASSWORD_VISIBLE";
- private static final String KEY_AUTH_STATUS_TEXT = "AUTH_STATUS_TEXT";
- private static final String KEY_AUTH_STATUS_ICON = "AUTH_STATUS_ICON";
- private static final String KEY_SERVER_AUTH_METHOD = "SERVER_AUTH_METHOD";
- private static final String KEY_WAITING_FOR_OP_ID = "WAITING_FOR_OP_ID";
- private static final String KEY_AUTH_TOKEN = "AUTH_TOKEN";
-
- private static final String AUTH_ON = "on";
- private static final String AUTH_OPTIONAL = "optional";
-
- public static final byte ACTION_CREATE = 0;
- public static final byte ACTION_UPDATE_EXPIRED_TOKEN = 2; // detected by the app
-
- private static final String UNTRUSTED_CERT_DIALOG_TAG = "UNTRUSTED_CERT_DIALOG";
- private static final String SAML_DIALOG_TAG = "SAML_DIALOG";
- private static final String WAIT_DIALOG_TAG = "WAIT_DIALOG";
- private static final String CREDENTIALS_DIALOG_TAG = "CREDENTIALS_DIALOG";
- private static final String KEY_AUTH_IS_FIRST_ATTEMPT_TAG = "KEY_AUTH_IS_FIRST_ATTEMPT";
-
- private static final String KEY_USERNAME = "USERNAME";
- private static final String KEY_PASSWORD = "PASSWORD";
- private static final String KEY_ASYNC_TASK_IN_PROGRESS = "AUTH_IN_PROGRESS";
- private static final String WEB_LOGIN = "/index.php/login/flow";
- public static final String PROTOCOL_SUFFIX = "://";
- public static final String LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":";
- public static final String HTTPS_PROTOCOL = "https://";
- public static final String HTTP_PROTOCOL = "http://";
-
- public static final String REGULAR_SERVER_INPUT_TYPE = "regular";
- public static final String SUBDOMAIN_SERVER_INPUT_TYPE = "prefix";
- public static final String DIRECTORY_SERVER_INPUT_TYPE = "suffix";
-
- /// parameters from EXTRAs in starter Intent
- private byte mAction;
- private Account mAccount;
- private String mAuthTokenType;
-
- /// activity-level references / state
- private final Handler mHandler = new Handler();
- private ServiceConnection mOperationsServiceConnection = null;
- private OperationsServiceBinder mOperationsServiceBinder = null;
- private AccountManager mAccountMgr;
- private Uri mNewCapturedUriFromOAuth2Redirection;
-
- /// Server PRE-Fragment elements
- private CustomEditText mHostUrlInput;
- private View mRefreshButton;
- private TextView mServerStatusView;
-
- private TextWatcher mHostUrlInputWatcher;
- private String mServerStatusText = "";
- private int mServerStatusIcon;
-
- private boolean mServerIsChecked;
- private boolean mServerIsValid;
-
- private GetServerInfoOperation.ServerInfo mServerInfo = new GetServerInfoOperation.ServerInfo();
-
- /// Authentication PRE-Fragment elements
- private CheckBox mOAuth2Check;
- private TextView mOAuthAuthEndpointText;
- private TextView mOAuthTokenEndpointText;
- private EditText mUsernameInput;
- private EditText mPasswordInput;
- private View mOkButton;
- private TextView mAuthStatusView;
- private ImageButton mTestServerButton;
-
- private WebView mLoginWebView;
-
- private String mAuthStatusText = "";
- private int mAuthStatusIcon;
-
- private String mAuthToken = "";
- private AuthenticatorAsyncTask mAsyncTask;
-
- private boolean mIsFirstAuthAttempt;
-
- /// Identifier of operation in progress which result shouldn't be lost
- private long mWaitingForOpId = Long.MAX_VALUE;
-
- private String basicTokenType;
- private String oauthTokenType;
- private String samlTokenType;
-
- private boolean webViewLoginMethod;
- private String webViewUser;
- private String webViewPassword;
- private TextInputLayout mUsernameInputLayout;
- private TextInputLayout mPasswordInputLayout;
- private boolean forceOldLoginMethod;
-
- /**
- * {@inheritDoc}
- *
- * IMPORTANT ENTRY POINT 1: activity is shown to the user
- */
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- //Log_OC.e(TAG, "onCreate init");
- super.onCreate(savedInstanceState);
-
- basicTokenType = AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType(this));
- oauthTokenType = AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType(this));
- samlTokenType = AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType(this));
-
- // delete cookies for webView
- deleteCookies();
-
- // Workaround, for fixing a problem with Android Library Suppor v7 19
- //getWindow().requestFeature(Window.FEATURE_NO_TITLE);
- if (getSupportActionBar() != null) {
- getSupportActionBar().hide();
- getSupportActionBar().setDisplayHomeAsUpEnabled(false);
- getSupportActionBar().setDisplayShowHomeEnabled(false);
- getSupportActionBar().setDisplayShowTitleEnabled(false);
- }
-
- mIsFirstAuthAttempt = true;
-
- /// init activity state
- mAccountMgr = AccountManager.get(this);
- mNewCapturedUriFromOAuth2Redirection = null;
-
- /// get input values
- mAction = getIntent().getByteExtra(EXTRA_ACTION, ACTION_CREATE);
- mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);
- if (savedInstanceState == null) {
- initAuthTokenType();
- } else {
- mAuthTokenType = savedInstanceState.getString(KEY_AUTH_TOKEN_TYPE);
- mWaitingForOpId = savedInstanceState.getLong(KEY_WAITING_FOR_OP_ID);
- mIsFirstAuthAttempt = savedInstanceState.getBoolean(KEY_AUTH_IS_FIRST_ATTEMPT_TAG);
- }
-
- webViewLoginMethod = !TextUtils.isEmpty(getResources().getString(R.string.webview_login_url));
-
- if (webViewLoginMethod) {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- }
-
- /// load user interface
- if (!webViewLoginMethod) {
- setContentView(R.layout.account_setup);
-
- /// initialize general UI elements
- initOverallUi();
-
- findViewById(R.id.centeredRefreshButton).setOnClickListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View v) {
- checkOcServer();
- }
- });
-
- findViewById(R.id.embeddedRefreshButton).setOnClickListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View v) {
- checkOcServer();
- }
- });
-
- /// initialize block to be moved to single Fragment to check server and get info about it
-
- /// initialize block to be moved to single Fragment to retrieve and validate credentials
- initAuthorizationPreFragment(savedInstanceState);
-
- } else {
- setContentView(R.layout.account_setup_webview);
- mLoginWebView = findViewById(R.id.login_webview);
- initWebViewLogin(null);
- }
-
- initServerPreFragment(savedInstanceState);
- }
-
- private void deleteCookies() {
- try {
- CookieSyncManager.createInstance(this);
- CookieManager cookieManager = CookieManager.getInstance();
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- cookieManager.removeAllCookies(null);
- } else {
- cookieManager.removeAllCookie();
- }
- } catch (AndroidRuntimeException e) {
- Log_OC.e(TAG, e.getMessage());
- }
- }
-
- private static String getWebLoginUserAgent() {
- return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) +
- Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL;
- }
-
- @SuppressLint("SetJavaScriptEnabled")
- private void initWebViewLogin(String baseURL) {
- mLoginWebView.setVisibility(View.GONE);
-
- final ProgressBar progressBar = findViewById(R.id.login_webview_progress_bar);
-
- mLoginWebView.getSettings().setAllowFileAccess(false);
- mLoginWebView.getSettings().setJavaScriptEnabled(true);
- mLoginWebView.getSettings().setDomStorageEnabled(true);
- mLoginWebView.getSettings().setUserAgentString(getWebLoginUserAgent());
- mLoginWebView.getSettings().setSaveFormData(false);
- mLoginWebView.getSettings().setSavePassword(false);
-
- Map headers = new HashMap<>();
- headers.put(RemoteOperation.OCS_API_HEADER, RemoteOperation.OCS_API_HEADER_VALUE);
-
- String url;
- if (baseURL != null && !baseURL.isEmpty()) {
- url = baseURL + WEB_LOGIN;
- } else {
- url = getResources().getString(R.string.webview_login_url);
- }
-
- mLoginWebView.loadUrl(url, headers);
-
- setClient(progressBar);
-
- // show snackbar after 60s to switch back to old login method
- new Handler().postDelayed(new Runnable() {
- @Override
- public void run() {
- DisplayUtils.createSnackbar(mLoginWebView, R.string.fallback_weblogin_text, Snackbar.LENGTH_INDEFINITE)
- .setActionTextColor(getResources().getColor(R.color.primary_dark))
- .setAction(R.string.fallback_weblogin_back, new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mLoginWebView.setVisibility(View.INVISIBLE);
- webViewLoginMethod = false;
-
- setContentView(R.layout.account_setup);
-
- // initialize general UI elements
- initOverallUi();
-
- mPasswordInputLayout.setVisibility(View.VISIBLE);
- mUsernameInputLayout.setVisibility(View.VISIBLE);
- mUsernameInput.requestFocus();
- mOAuth2Check.setVisibility(View.INVISIBLE);
- mAuthStatusView.setVisibility(View.INVISIBLE);
- mServerStatusView.setVisibility(View.INVISIBLE);
- mTestServerButton.setVisibility(View.INVISIBLE);
- forceOldLoginMethod = true;
- mOkButton.setVisibility(View.VISIBLE);
-
- initServerPreFragment(null);
-
- mHostUrlInput.setText(baseURL);
-
- checkOcServer();
- }
- }).show();
- }
- }, 60000);
- }
-
- private void setClient(ProgressBar progressBar) {
- mLoginWebView.setWebViewClient(new WebViewClient() {
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- if (url.startsWith(getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/")) {
- parseAndLoginFromWebView(url);
- return true;
- }
- return false;
- }
-
- @Override
- public void onPageFinished(WebView view, String url) {
- super.onPageFinished(view, url);
-
- progressBar.setVisibility(View.GONE);
- mLoginWebView.setVisibility(View.VISIBLE);
- }
-
- @Override
- public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
- X509Certificate cert = SsoWebViewClient.getX509CertificateFromError(error);
-
- try {
- if (cert != null && NetworkUtils.isCertInKnownServersStore(cert, getApplicationContext())) {
- handler.proceed();
- } else {
- showUntrustedCertDialog(cert, error, handler);
- }
- } catch (Exception e) {
- Log_OC.e(TAG, "Cert could not be verified");
- }
- }
-
- public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
- progressBar.setVisibility(View.GONE);
- mLoginWebView.setVisibility(View.VISIBLE);
-
- InputStream resources = getResources().openRawResource(R.raw.custom_error);
- String customError = DisplayUtils.getData(resources);
-
- if (!customError.isEmpty()) {
- mLoginWebView.loadData(customError, "text/html; charset=UTF-8", null);
- }
- }
- });
- }
-
- private void parseAndLoginFromWebView(String dataString) {
- String prefix = getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/";
- LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, dataString);
-
- if (loginUrlInfo != null) {
- mServerInfo.mBaseUrl = AuthenticatorUrlUtils.normalizeUrlSuffix(loginUrlInfo.serverAddress);
- webViewUser = loginUrlInfo.username;
- webViewPassword = loginUrlInfo.password;
- checkOcServer();
- }
- }
-
- private void populateLoginFields(String dataString) throws IllegalArgumentException {
- // check if it is cloud://login/
- if (dataString.startsWith(getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/")) {
- String prefix = getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/";
- LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, dataString);
-
- if (loginUrlInfo != null) {
- mHostUrlInput.setText(loginUrlInfo.serverAddress);
- mUsernameInput.setText(loginUrlInfo.username);
- mPasswordInput.setText(loginUrlInfo.password);
-
- if (loginUrlInfo.serverAddress != null && !mServerIsChecked) {
- onUrlInputFocusLost();
- }
- }
- }
- }
-
- /**
- * parses a URI string and returns a login data object with the information from the URI string.
- *
- * @param prefix URI beginning, e.g. cloud://login/
- * @param dataString the complete URI
- * @return login data
- * @throws IllegalArgumentException when
- */
- public static LoginUrlInfo parseLoginDataUrl(String prefix, String dataString) throws IllegalArgumentException {
- if (dataString.length() < prefix.length()) {
- throw new IllegalArgumentException("Invalid login URL detected");
- }
- LoginUrlInfo loginUrlInfo = new LoginUrlInfo();
-
- // format is basically xxx://login/server:xxx&user:xxx&password while all variables are optional
- String data = dataString.substring(prefix.length());
-
- // parse data
- String[] values = data.split("&");
-
- if (values.length < 1 || values.length > 3) {
- // error illegal number of URL elements detected
- throw new IllegalArgumentException("Illegal number of login URL elements detected: " + values.length);
- }
-
- for (String value : values) {
- if (value.startsWith("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
- loginUrlInfo.username = URLDecoder.decode(
- value.substring(("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
- } else if (value.startsWith("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
- loginUrlInfo.password = URLDecoder.decode(
- value.substring(("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
- } else if (value.startsWith("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
- loginUrlInfo.serverAddress = URLDecoder.decode(
- value.substring(("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
- } else {
- // error illegal URL element detected
- throw new IllegalArgumentException("Illegal magic login URL element detected: " + value);
- }
- }
-
- return loginUrlInfo;
- }
-
- private void initAuthTokenType() {
- mAuthTokenType = getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE);
- if (mAuthTokenType == null) {
- if (mAccount != null) {
- boolean oAuthRequired = mAccountMgr.getUserData(mAccount, Constants.KEY_SUPPORTS_OAUTH2) != null;
- boolean samlWebSsoRequired = (
- mAccountMgr.getUserData
- (mAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO) != null
- );
- mAuthTokenType = chooseAuthTokenType(oAuthRequired, samlWebSsoRequired);
-
- } else {
- boolean oAuthSupported = AUTH_ON.equals(getString(R.string.auth_method_oauth2));
- boolean samlWebSsoSupported = AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso));
- mAuthTokenType = chooseAuthTokenType(oAuthSupported, samlWebSsoSupported);
- }
- }
- }
-
- private String chooseAuthTokenType(boolean oauth, boolean saml) {
- if (saml) {
- return samlTokenType;
- } else if (oauth) {
- return oauthTokenType;
- } else {
- return basicTokenType;
- }
- }
-
-
- /**
- * Configures elements in the user interface under direct control of the Activity.
- */
- private void initOverallUi() {
- mHostUrlInput = findViewById(R.id.hostUrlInput);
- mUsernameInputLayout = findViewById(R.id.input_layout_account_username);
- mPasswordInputLayout = findViewById(R.id.input_layout_account_password);
- mPasswordInput = findViewById(R.id.account_password);
- mUsernameInput = findViewById(R.id.account_username);
- mAuthStatusView = findViewById(R.id.auth_status_text);
- mOAuth2Check = findViewById(R.id.oauth_onOff_check);
- mServerStatusView = findViewById(R.id.server_status_text);
- mTestServerButton = findViewById(R.id.testServerButton);
-
- mOkButton = findViewById(R.id.buttonOK);
- mOkButton.setOnClickListener(v -> onOkClick());
-
- setupWelcomeLink();
- setupInstructionMessage();
-
- mTestServerButton.setVisibility(mAction == ACTION_CREATE ? View.VISIBLE : View.GONE);
- }
-
- private void setupWelcomeLink() {
- Button welcomeLink = findViewById(R.id.welcome_link);
- welcomeLink.setVisibility(mAction == ACTION_CREATE &&
- getResources().getBoolean(R.bool.show_welcome_link) ? View.VISIBLE : View.GONE);
- welcomeLink.setText(getString(R.string.auth_register));
- }
-
- private void setupInstructionMessage() {
- String instructionsMessageText = calculateInstructionMessageText(mAction, mAuthTokenType);
- TextView instructionsView = findViewById(R.id.instructions_message);
-
- if (instructionsMessageText != null) {
- instructionsView.setVisibility(View.VISIBLE);
- instructionsView.setText(instructionsMessageText);
- } else {
- instructionsView.setVisibility(View.GONE);
- }
- }
-
- @Nullable
- private String calculateInstructionMessageText(byte action, String authTokenType) {
- if (action == ACTION_UPDATE_EXPIRED_TOKEN) {
- if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType(this)).equals(authTokenType)) {
- return getString(R.string.auth_expired_oauth_token_toast);
-
- } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType(this))
- .equals(authTokenType)) {
- return getString(R.string.auth_expired_saml_sso_token_toast);
-
- } else {
- return getString(R.string.auth_expired_basic_auth_toast);
- }
- }
-
- return null;
- }
-
- public void onTestServerConnectionClick(View v) {
- checkOcServer();
- }
-
-
- /**
- * @param savedInstanceState Saved activity state, as in {{@link #onCreate(Bundle)}
- */
- private void initServerPreFragment(Bundle savedInstanceState) {
-
- /// step 1 - load and process relevant inputs (resources, intent, savedInstanceState)
- boolean isUrlInputAllowed = getResources().getBoolean(R.bool.show_server_url_input);
- if (savedInstanceState == null) {
- if (mAccount != null) {
- mServerInfo.mBaseUrl = mAccountMgr.getUserData(mAccount, Constants.KEY_OC_BASE_URL);
- // TODO do next in a setter for mBaseUrl
- mServerInfo.mIsSslConn = mServerInfo.mBaseUrl.startsWith(HTTPS_PROTOCOL);
- mServerInfo.mVersion = AccountUtils.getServerVersion(mAccount);
- } else {
- if (!webViewLoginMethod) {
- mServerInfo.mBaseUrl = getString(R.string.server_url).trim();
- } else {
- mServerInfo.mBaseUrl = getString(R.string.webview_login_url).trim();
- }
- mServerInfo.mIsSslConn = mServerInfo.mBaseUrl.startsWith(HTTPS_PROTOCOL);
- }
- } else {
- mServerStatusText = savedInstanceState.getString(KEY_SERVER_STATUS_TEXT);
- mServerStatusIcon = savedInstanceState.getInt(KEY_SERVER_STATUS_ICON);
-
- mServerIsValid = savedInstanceState.getBoolean(KEY_SERVER_VALID);
- mServerIsChecked = savedInstanceState.getBoolean(KEY_SERVER_CHECKED);
-
- // TODO parcelable
- mServerInfo.mIsSslConn = savedInstanceState.getBoolean(KEY_IS_SSL_CONN);
- mServerInfo.mBaseUrl = savedInstanceState.getString(KEY_HOST_URL_TEXT);
- String ocVersion = savedInstanceState.getString(KEY_OC_VERSION);
- if (ocVersion != null) {
- mServerInfo.mVersion = new OwnCloudVersion(ocVersion);
- }
- mServerInfo.mAuthMethod = AuthenticationMethod.valueOf(
- savedInstanceState.getString(KEY_SERVER_AUTH_METHOD));
-
- }
-
- if (!webViewLoginMethod) {
- /// step 2 - set properties of UI elements (text, visibility, enabled...)
- mHostUrlInput = findViewById(R.id.hostUrlInput);
- // Convert IDN to Unicode
- mHostUrlInput.setText(DisplayUtils.convertIdn(mServerInfo.mBaseUrl, false));
- if (mAction != ACTION_CREATE) {
- /// lock things that should not change
- mHostUrlInput.setEnabled(false);
- mHostUrlInput.setFocusable(false);
- }
- if (isUrlInputAllowed) {
- mRefreshButton = findViewById(R.id.embeddedRefreshButton);
- } else {
- findViewById(R.id.hostUrlFrame).setVisibility(View.GONE);
- mRefreshButton = findViewById(R.id.centeredRefreshButton);
- }
- showRefreshButton(mServerIsChecked && !mServerIsValid &&
- mWaitingForOpId > Integer.MAX_VALUE);
- mServerStatusView = findViewById(R.id.server_status_text);
- showServerStatus();
-
- /// step 3 - bind some listeners and options
- mHostUrlInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
- mHostUrlInput.setOnEditorActionListener(this);
-
- /// step 4 - create listeners that will be bound at onResume
- mHostUrlInputWatcher = new TextWatcher() {
-
- @Override
- public void afterTextChanged(Editable s) {
- if (mOkButton.isEnabled() &&
- !mServerInfo.mBaseUrl.equals(
- AuthenticatorUrlUtils.normalizeUrl(s.toString(), mServerInfo.mIsSslConn))) {
- mOkButton.setEnabled(false);
- }
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // not used at the moment
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- if (mAuthStatusIcon != 0) {
- Log_OC.d(TAG, "onTextChanged: hiding authentication status");
- mAuthStatusIcon = 0;
- mAuthStatusText = "";
- showAuthStatus();
- }
- }
- };
-
-
- // TODO find out if this is really necessary, or if it can done in a different way
- findViewById(R.id.scroll).setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View view, MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN &&
- AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(
- MainApp.getAccountType(getBaseContext())).equals(mAuthTokenType) &&
- mHostUrlInput.hasFocus()) {
- checkOcServer();
- }
- return false;
- }
- });
- }
- }
-
-
- /**
- * @param savedInstanceState Saved activity state, as in {{@link #onCreate(Bundle)}
- */
- private void initAuthorizationPreFragment(Bundle savedInstanceState) {
-
- /// step 0 - get UI elements in layout
- mOAuth2Check = findViewById(R.id.oauth_onOff_check);
- mOAuthAuthEndpointText = findViewById(R.id.oAuthEntryPoint_1);
- mOAuthTokenEndpointText = findViewById(R.id.oAuthEntryPoint_2);
- mUsernameInput = findViewById(R.id.account_username);
- mPasswordInput = findViewById(R.id.account_password);
- mAuthStatusView = findViewById(R.id.auth_status_text);
-
- /// step 1 - load and process relevant inputs (resources, intent, savedInstanceState)
- String presetUserName = null;
- boolean isPasswordExposed = false;
- if (savedInstanceState == null) {
- if (mAccount != null) {
- presetUserName = com.owncloud.android.lib.common.accounts.AccountUtils.getUsernameForAccount(mAccount);
- }
- } else {
- isPasswordExposed = savedInstanceState.getBoolean(KEY_PASSWORD_EXPOSED, false);
- mAuthStatusText = savedInstanceState.getString(KEY_AUTH_STATUS_TEXT);
- mAuthStatusIcon = savedInstanceState.getInt(KEY_AUTH_STATUS_ICON);
- mAuthToken = savedInstanceState.getString(KEY_AUTH_TOKEN);
- }
-
- /// step 2 - set properties of UI elements (text, visibility, enabled...)
- mOAuth2Check.setChecked(
- AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType(this)).equals(mAuthTokenType));
- if (presetUserName != null) {
- mUsernameInput.setText(presetUserName);
- }
- if (mAction != ACTION_CREATE) {
- mUsernameInput.setEnabled(false);
- mUsernameInput.setFocusable(false);
- }
- mPasswordInput.setText(""); // clean password to avoid social hacking
- if (isPasswordExposed) {
- showPassword();
- }
- updateAuthenticationPreFragmentVisibility();
- showAuthStatus();
- mOkButton.setEnabled(mServerIsValid);
-
-
- /// step 3 - bind listeners
- // bindings for password input field
- mPasswordInput.setOnFocusChangeListener(this);
- mPasswordInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
- mPasswordInput.setOnEditorActionListener(this);
- mPasswordInput.setOnTouchListener(new RightDrawableOnTouchListener() {
- @Override
- public boolean onDrawableTouch(final MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_UP) {
- AuthenticatorActivity.this.onViewPasswordClick();
- }
- return true;
- }
- });
-
- }
-
-
- /**
- * Changes the visibility of input elements depending on
- * the current authorization method.
- */
- private void updateAuthenticationPreFragmentVisibility() {
- if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType(this)).equals(mAuthTokenType)) {
- // SAML-based web Single Sign On
- mOAuth2Check.setVisibility(View.GONE);
- mOAuthAuthEndpointText.setVisibility(View.GONE);
- mOAuthTokenEndpointText.setVisibility(View.GONE);
- mUsernameInput.setVisibility(View.GONE);
- mPasswordInput.setVisibility(View.GONE);
-
- } else {
- if (mAction == ACTION_CREATE &&
- AUTH_OPTIONAL.equals(getString(R.string.auth_method_oauth2))) {
- mOAuth2Check.setVisibility(View.VISIBLE);
- } else {
- mOAuth2Check.setVisibility(View.GONE);
- }
-
- if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType(this)).equals(mAuthTokenType)) {
- // OAuth 2 authorization
- mOAuthAuthEndpointText.setVisibility(View.VISIBLE);
- mOAuthTokenEndpointText.setVisibility(View.VISIBLE);
- mUsernameInput.setVisibility(View.GONE);
- mPasswordInput.setVisibility(View.GONE);
-
- } else {
- // basic HTTP authorization
- mOAuthAuthEndpointText.setVisibility(View.GONE);
- mOAuthTokenEndpointText.setVisibility(View.GONE);
- mUsernameInput.setVisibility(View.VISIBLE);
- mPasswordInput.setVisibility(View.VISIBLE);
- }
- }
- }
-
-
- /**
- * Saves relevant state before {@link #onPause()}
- *
- * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag,
- * intended to defer the processing of the redirection caught in
- * {@link #onNewIntent(Intent)} until {@link #onResume()}
- *
- * See {@link super#onSaveInstanceState(Bundle)}
- */
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- //Log_OC.e(TAG, "onSaveInstanceState init" );
- super.onSaveInstanceState(outState);
-
- /// global state
- outState.putString(KEY_AUTH_TOKEN_TYPE, mAuthTokenType);
- outState.putLong(KEY_WAITING_FOR_OP_ID, mWaitingForOpId);
-
- if (!webViewLoginMethod) {
- /// Server PRE-fragment state
- outState.putString(KEY_SERVER_STATUS_TEXT, mServerStatusText);
- outState.putInt(KEY_SERVER_STATUS_ICON, mServerStatusIcon);
- outState.putBoolean(KEY_SERVER_CHECKED, mServerIsChecked);
- outState.putBoolean(KEY_SERVER_VALID, mServerIsValid);
-
- /// Authentication PRE-fragment state
- outState.putBoolean(KEY_PASSWORD_EXPOSED, isPasswordVisible());
- outState.putInt(KEY_AUTH_STATUS_ICON, mAuthStatusIcon);
- outState.putString(KEY_AUTH_STATUS_TEXT, mAuthStatusText);
- outState.putString(KEY_AUTH_TOKEN, mAuthToken);
- }
-
- outState.putBoolean(KEY_IS_SSL_CONN, mServerInfo.mIsSslConn);
- outState.putString(KEY_HOST_URL_TEXT, mServerInfo.mBaseUrl);
- if (mServerInfo.mVersion != null) {
- outState.putString(KEY_OC_VERSION, mServerInfo.mVersion.getVersion());
- }
- outState.putString(KEY_SERVER_AUTH_METHOD, mServerInfo.mAuthMethod.name());
-
- /// authentication
- outState.putBoolean(KEY_AUTH_IS_FIRST_ATTEMPT_TAG, mIsFirstAuthAttempt);
-
- /// AsyncTask (User and password)
- if (!webViewLoginMethod) {
- outState.putString(KEY_USERNAME, mUsernameInput.getText().toString().trim());
- outState.putString(KEY_PASSWORD, mPasswordInput.getText().toString());
- }
-
- if (mAsyncTask != null) {
- mAsyncTask.cancel(true);
- outState.putBoolean(KEY_ASYNC_TASK_IN_PROGRESS, true);
- } else {
- outState.putBoolean(KEY_ASYNC_TASK_IN_PROGRESS, false);
- }
- mAsyncTask = null;
-
- //Log_OC.e(TAG, "onSaveInstanceState end" );
- }
-
- @Override
- public void onRestoreInstanceState(Bundle savedInstanceState) {
- super.onRestoreInstanceState(savedInstanceState);
-
- mServerIsChecked = savedInstanceState.getBoolean(KEY_SERVER_CHECKED, false);
-
- // AsyncTask
- boolean inProgress = savedInstanceState.getBoolean(KEY_ASYNC_TASK_IN_PROGRESS);
- if (inProgress) {
- String username = savedInstanceState.getString(KEY_USERNAME);
- String password = savedInstanceState.getString(KEY_PASSWORD);
-
- OwnCloudCredentials credentials = null;
- if (basicTokenType.equals(mAuthTokenType)) {
- credentials = OwnCloudCredentialsFactory.newBasicCredentials(username, password);
-
- } else if (oauthTokenType.equals(mAuthTokenType)) {
- credentials = OwnCloudCredentialsFactory.newBearerCredentials(mAuthToken);
-
- }
- accessRootFolder(credentials);
- }
- }
-
- /**
- * The redirection triggered by the OAuth authentication server as response to the
- * GET AUTHORIZATION request is caught here.
- *
- * To make this possible, this activity needs to be qualified with android:launchMode =
- * "singleTask" in the AndroidManifest.xml file.
- */
- @Override
- protected void onNewIntent(Intent intent) {
- Log_OC.d(TAG, "onNewIntent()");
- Uri data = intent.getData();
- if (data != null && data.toString().startsWith(getString(R.string.oauth2_redirect_uri))) {
- mNewCapturedUriFromOAuth2Redirection = data;
- }
- }
-
-
- /**
- * The redirection triggered by the OAuth authentication server as response to the
- * GET AUTHORIZATION, and deferred in {@link #onNewIntent(Intent)}, is processed here.
- */
- @Override
- protected void onResume() {
- super.onResume();
-
- if (!webViewLoginMethod) {
- // bound here to avoid spurious changes triggered by Android on device rotations
- mHostUrlInput.setOnFocusChangeListener(this);
- mHostUrlInput.addTextChangedListener(mHostUrlInputWatcher);
-
- if (mNewCapturedUriFromOAuth2Redirection != null) {
- getOAuth2AccessTokenFromCapturedRedirection();
- }
-
- String dataString = getIntent().getDataString();
- if (dataString != null) {
- try {
- populateLoginFields(dataString);
- } catch (IllegalArgumentException e) {
- DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.auth_illegal_login_used);
- Log_OC.e(TAG, "Illegal login data URL used, no Login pre-fill!", e);
- }
- }
- }
-
- // bind to Operations Service
- mOperationsServiceConnection = new OperationsServiceConnection();
- if (!bindService(new Intent(this, OperationsService.class),
- mOperationsServiceConnection,
- Context.BIND_AUTO_CREATE)) {
- DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.error_cant_bind_to_operations_service);
- finish();
- }
-
- if (mOperationsServiceBinder != null) {
- doOnResumeAndBound();
- }
- }
-
-
- @Override
- protected void onPause() {
- if (mOperationsServiceBinder != null) {
- mOperationsServiceBinder.removeOperationListener(this);
- }
-
- if (!webViewLoginMethod) {
- mHostUrlInput.removeTextChangedListener(mHostUrlInputWatcher);
- mHostUrlInput.setOnFocusChangeListener(null);
- }
-
- super.onPause();
- }
-
- @Override
- protected void onDestroy() {
-
- mHostUrlInputWatcher = null;
-
- if (mOperationsServiceConnection != null) {
- unbindService(mOperationsServiceConnection);
- mOperationsServiceBinder = null;
- }
-
- if (webViewLoginMethod) {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
- }
-
- super.onDestroy();
- }
-
- /**
- * Parses the redirection with the response to the GET AUTHORIZATION request to the
- * oAuth server and requests for the access token (GET ACCESS TOKEN)
- */
- private void getOAuth2AccessTokenFromCapturedRedirection() {
- /// Parse data from OAuth redirection
- String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery();
- mNewCapturedUriFromOAuth2Redirection = null;
-
- /// Showing the dialog with instructions for the user.
- IndeterminateProgressDialog dialog =
- IndeterminateProgressDialog.newInstance(R.string.auth_getting_authorization, true);
- dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
-
- /// GET ACCESS TOKEN to the oAuth server
- Intent getServerInfoIntent = new Intent();
- getServerInfoIntent.setAction(OperationsService.ACTION_OAUTH2_GET_ACCESS_TOKEN);
-
- getServerInfoIntent.putExtra(
- OperationsService.EXTRA_SERVER_URL,
- mOAuthTokenEndpointText.getText().toString().trim());
-
- getServerInfoIntent.putExtra(
- OperationsService.EXTRA_OAUTH2_QUERY_PARAMETERS,
- queryParameters);
-
- if (mOperationsServiceBinder != null) {
- //Log_OC.e(TAG, "getting access token..." );
- mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);
- }
- }
-
-
- /**
- * Handles the change of focus on the text inputs for the server URL and the password
- */
- public void onFocusChange(View view, boolean hasFocus) {
- if (view.getId() == R.id.hostUrlInput) {
- if (!hasFocus) {
- onUrlInputFocusLost();
- } else {
- showRefreshButton(false);
- }
-
- } else if (view.getId() == R.id.account_password) {
- onPasswordFocusChanged(hasFocus);
- }
- }
-
-
- /**
- * Handles changes in focus on the text input for the server URL.
- *
- * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to
- * other field. The operation to check the existence of the server in the entered URL is
- * started.
- *
- * When hasFocus: user 'comes back' to write again the server URL.
- */
- private void onUrlInputFocusLost() {
- if (!mServerInfo.mBaseUrl.equals(
- AuthenticatorUrlUtils.normalizeUrl(mHostUrlInput.getText().toString(), mServerInfo.mIsSslConn))) {
- // check server again only if the user changed something in the field
- checkOcServer();
- } else {
- mOkButton.setEnabled(mServerIsValid);
- showRefreshButton(!mServerIsValid);
- }
- }
-
-
- private void checkOcServer() {
- String uri;
- if (mHostUrlInput != null) {
- uri = mHostUrlInput.getText().toString().trim();
- mOkButton.setEnabled(false);
- showRefreshButton(false);
- } else {
- uri = mServerInfo.mBaseUrl;
- }
-
- mServerIsValid = false;
- mServerIsChecked = false;
- mServerInfo = new GetServerInfoOperation.ServerInfo();
-
- if (uri.length() != 0) {
- if (mHostUrlInput != null) {
- uri = AuthenticatorUrlUtils.stripIndexPhpOrAppsFiles(uri);
- mHostUrlInput.setText(uri);
- }
-
- // Handle internationalized domain names
- try {
- uri = DisplayUtils.convertIdn(uri, true);
- } catch (IllegalArgumentException ex) {
- // Let Owncloud library check the error of the malformed URI
- Log_OC.e(TAG, "Error converting internationalized domain name " + uri, ex);
- }
-
- if (mHostUrlInput != null) {
- mServerStatusText = getResources().getString(R.string.auth_testing_connection);
- mServerStatusIcon = R.drawable.progress_small;
- showServerStatus();
- }
-
- Intent getServerInfoIntent = new Intent();
- getServerInfoIntent.setAction(OperationsService.ACTION_GET_SERVER_INFO);
- getServerInfoIntent.putExtra(OperationsService.EXTRA_SERVER_URL,
- AuthenticatorUrlUtils.normalizeUrlSuffix(uri));
-
- if (mOperationsServiceBinder != null) {
- mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);
- } else {
- Log_OC.e(TAG, "Server check tried with OperationService unbound!");
- }
-
- } else {
- mServerStatusText = "";
- mServerStatusIcon = 0;
- if (!webViewLoginMethod) {
- showServerStatus();
- }
- }
- }
-
-
- /**
- * Handles changes in focus on the text input for the password (basic authorization).
- *
- * When (hasFocus), the button to toggle password visibility is shown.
- *
- * When (!hasFocus), the button is made invisible and the password is hidden.
- *
- * @param hasFocus 'True' if focus is received, 'false' if is lost
- */
-
- private void onPasswordFocusChanged(boolean hasFocus) {
- if (hasFocus) {
- showViewPasswordButton();
- } else {
- hidePassword();
- hidePasswordButton();
- }
- }
-
-
- private void showViewPasswordButton() {
- int drawable = R.drawable.ic_view;
- if (isPasswordVisible()) {
- drawable = R.drawable.ic_hide;
- }
- mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, drawable, 0);
- }
-
- private boolean isPasswordVisible() {
- return ((mPasswordInput.getInputType() & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) ==
- InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
- }
-
- private void hidePasswordButton() {
- mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
- }
-
- private void showPassword() {
- mPasswordInput.setInputType(
- InputType.TYPE_CLASS_TEXT |
- InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD |
- InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
- );
- showViewPasswordButton();
- }
-
- private void hidePassword() {
- mPasswordInput.setInputType(
- InputType.TYPE_CLASS_TEXT |
- InputType.TYPE_TEXT_VARIATION_PASSWORD |
- InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
- );
- showViewPasswordButton();
- }
-
- /**
- * Checks the credentials of the user in the root of the ownCloud server
- * before creating a new local account.
- *
- * For basic authorization, a check of existence of the root folder is
- * performed.
- *
- * For OAuth, starts the flow to get an access token; the credentials test
- * is postponed until it is available.
- *
- * IMPORTANT ENTRY POINT 4
- */
- public void onOkClick() {
- // this check should be unnecessary
- if (mServerInfo.mVersion == null ||
- !mServerInfo.mVersion.isVersionValid() ||
- mServerInfo.mBaseUrl == null ||
- mServerInfo.mBaseUrl.length() == 0) {
- mServerStatusIcon = R.drawable.ic_alert;
- mServerStatusText = getResources().getString(R.string.auth_wtf_reenter_URL);
- showServerStatus();
- mOkButton.setEnabled(false);
- return;
- }
-
- if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType(this)).equals(mAuthTokenType)) {
- startOauthorization();
- } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType(this))
- .equals(mAuthTokenType)) {
- startSamlBasedFederatedSingleSignOnAuthorization();
- } else {
- checkBasicAuthorization(null, null);
- }
- }
-
-
- /**
- * Tests the credentials entered by the user performing a check of existence on
- * the root folder of the ownCloud server.
- */
- private void checkBasicAuthorization(@Nullable String webViewUsername, @Nullable String webViewPassword) {
- /// get basic credentials entered by user
- String username;
- String password;
- if (!webViewLoginMethod) {
- username = mUsernameInput.getText().toString().trim();
- password = mPasswordInput.getText().toString();
- } else {
- username = webViewUsername;
- password = webViewPassword;
- }
-
- /// be gentle with the user
- IndeterminateProgressDialog dialog =
- IndeterminateProgressDialog.newInstance(R.string.auth_trying_to_login, true);
- dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
-
- /// validate credentials accessing the root folder
- OwnCloudCredentials credentials = OwnCloudCredentialsFactory.newBasicCredentials(username, password);
- accessRootFolder(credentials);
- }
-
- private void accessRootFolder(OwnCloudCredentials credentials) {
- mAsyncTask = new AuthenticatorAsyncTask(this);
- Object[] params = {mServerInfo.mBaseUrl, credentials};
- mAsyncTask.execute(params);
- }
-
-
- /**
- * Starts the OAuth 'grant type' flow to get an access token, with
- * a GET AUTHORIZATION request to the BUILT-IN authorization server.
- */
- private void startOauthorization() {
- // be gentle with the user
- mAuthStatusIcon = R.drawable.progress_small;
- mAuthStatusText = getResources().getString(R.string.oauth_login_connection);
- showAuthStatus();
-
- // GET AUTHORIZATION request
- Uri uri = Uri.parse(mOAuthAuthEndpointText.getText().toString().trim());
- Uri.Builder uriBuilder = uri.buildUpon();
- uriBuilder.appendQueryParameter(
- OAuth2Constants.KEY_RESPONSE_TYPE, getString(R.string.oauth2_response_type)
- );
- uriBuilder.appendQueryParameter(
- OAuth2Constants.KEY_REDIRECT_URI, getString(R.string.oauth2_redirect_uri)
- );
- uriBuilder.appendQueryParameter(
- OAuth2Constants.KEY_CLIENT_ID, getString(R.string.oauth2_client_id)
- );
- uriBuilder.appendQueryParameter(
- OAuth2Constants.KEY_SCOPE, getString(R.string.oauth2_scope)
- );
- uri = uriBuilder.build();
- Log_OC.d(TAG, "Starting browser to view " + uri.toString());
- Intent i = new Intent(Intent.ACTION_VIEW, uri);
- startActivity(i);
- }
-
-
- /**
- * Starts the Web Single Sign On flow to get access to the root folder
- * in the server.
- */
- private void startSamlBasedFederatedSingleSignOnAuthorization() {
- /// be gentle with the user
- mAuthStatusIcon = R.drawable.progress_small;
- mAuthStatusText = getResources().getString(R.string.auth_connecting_auth_server);
- showAuthStatus();
-
- /// Show SAML-based SSO web dialog
- String targetUrl = mServerInfo.mBaseUrl
- + AuthenticatorUrlUtils.getWebdavPath(mServerInfo.mVersion, mAuthTokenType, this);
- SamlWebViewDialog dialog = SamlWebViewDialog.newInstance(targetUrl, targetUrl);
- dialog.show(getSupportFragmentManager(), SAML_DIALOG_TAG);
- }
-
- /**
- * Callback method invoked when a RemoteOperation executed by this Activity finishes.
- *
- * Dispatches the operation flow to the right method.
- */
- @Override
- public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
-
- if (operation instanceof GetServerInfoOperation) {
- if (operation.hashCode() == mWaitingForOpId) {
- onGetServerInfoFinish(result);
- } // else nothing ; only the last check operation is considered;
- // multiple can be started if the user amends a URL quickly
-
- } else if (operation instanceof OAuth2GetAccessToken) {
- onGetOAuthAccessTokenFinish(result);
-
- } else if (operation instanceof GetRemoteUserInfoOperation) {
- onGetUserNameFinish(result);
- }
-
- }
-
- private void onGetUserNameFinish(RemoteOperationResult result) {
- mWaitingForOpId = Long.MAX_VALUE;
- if (result.isSuccess()) {
- boolean success = false;
- String username;
- if (result.getData().get(0) instanceof UserInfo) {
- username = ((UserInfo) result.getData().get(0)).getDisplayName();
- } else {
- username = (String) result.getData().get(0);
- }
-
- if (mAction == ACTION_CREATE) {
- if (!webViewLoginMethod) {
- mUsernameInput.setText(username);
- }
- success = createAccount(result);
- } else {
-
- if (!webViewLoginMethod && !mUsernameInput.getText().toString().trim().equals(username)) {
- // fail - not a new account, but an existing one; disallow
- result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_THE_SAME);
- mAuthToken = "";
- updateAuthStatusIconAndText(result);
- showAuthStatus();
- Log_OC.d(TAG, result.getLogMessage());
- } else {
- try {
- updateAccountAuthentication();
- success = true;
-
- } catch (AccountNotFoundException e) {
- Log_OC.e(TAG, "Account " + mAccount + " was removed!", e);
- DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.auth_account_does_not_exist);
- finish();
- }
- }
- }
-
- if (success) {
- finish();
- }
- } else {
- if (!webViewLoginMethod) {
- int statusText = result.getCode() == ResultCode.MAINTENANCE_MODE ? R.string.maintenance_mode : R.string.auth_fail_get_user_name;
- updateStatusIconFailUserName(statusText);
- showAuthStatus();
- }
- Log_OC.e(TAG, "Access to user name failed: " + result.getLogMessage());
- }
-
- }
-
- /**
- * Processes the result of the server check performed when the user finishes the enter of the
- * server URL.
- *
- * @param result Result of the check.
- */
- private void onGetServerInfoFinish(RemoteOperationResult result) {
- /// update activity state
- mServerIsChecked = true;
- mWaitingForOpId = Long.MAX_VALUE;
-
- // update server status, but don't show it yet
- if (!webViewLoginMethod) {
- updateServerStatusIconAndText(result);
- }
-
- if (result.isSuccess()) {
- /// SUCCESS means:
- // 1. connection succeeded, and we know if it's SSL or not
- // 2. server is installed
- // 3. we got the server version
- // 4. we got the authentication method required by the server
- mServerInfo = (GetServerInfoOperation.ServerInfo) (result.getData().get(0));
-
- // show outdated warning
- if (getResources().getBoolean(R.bool.show_outdated_server_warning) &&
- mServerInfo.mVersion.compareTo(MainApp.OUTDATED_SERVER_VERSION) < 0) {
- DisplayUtils.showServerOutdatedSnackbar(this);
- }
-
- webViewLoginMethod = mServerInfo.mVersion.isWebLoginSupported() && !forceOldLoginMethod;
-
- if (webViewUser != null && !webViewUser.isEmpty() &&
- webViewPassword != null && !webViewPassword.isEmpty()) {
- checkBasicAuthorization(webViewUser, webViewPassword);
- } else if (webViewLoginMethod) {
- // hide old login
- setOldLoginVisibility(View.GONE);
-
- setContentView(R.layout.account_setup_webview);
- mLoginWebView = findViewById(R.id.login_webview);
- initWebViewLogin(mServerInfo.mBaseUrl);
- } else {
- // show old login
- setOldLoginVisibility(View.VISIBLE);
- }
-
- if (!authSupported(mServerInfo.mAuthMethod)) {
-
- if (!webViewLoginMethod) {
- // overrides updateServerStatusIconAndText()
- updateServerStatusIconNoRegularAuth();
- }
- mServerIsValid = false;
-
- } else {
- mServerIsValid = true;
- }
-
- } else {
- mServerIsValid = false;
- }
-
- // refresh UI
- if (!webViewLoginMethod) {
- showRefreshButton(!mServerIsValid);
- showServerStatus();
- mOkButton.setEnabled(mServerIsValid);
- }
-
- if (!mServerIsValid) {
- // hide old login
- setOldLoginVisibility(View.GONE);
- }
-
- /// very special case (TODO: move to a common place for all the remote operations)
- if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) {
- showUntrustedCertDialog(result);
- }
- }
-
- private void setOldLoginVisibility(int visible) {
- mOkButton.setVisibility(visible);
- mUsernameInputLayout.setVisibility(visible);
- mPasswordInputLayout.setVisibility(visible);
- }
-
- private boolean authSupported(AuthenticationMethod authMethod) {
- return ((basicTokenType.equals(mAuthTokenType) &&
- AuthenticationMethod.BASIC_HTTP_AUTH.equals(authMethod)) ||
- (oauthTokenType.equals(mAuthTokenType) &&
- AuthenticationMethod.BEARER_TOKEN.equals(authMethod)) ||
- (samlTokenType.equals(mAuthTokenType) &&
- AuthenticationMethod.SAML_WEB_SSO.equals(authMethod))
- );
- }
-
- /**
- * Chooses the right icon and text to show to the user for the received operation result.
- *
- * @param result Result of a remote operation performed in this activity
- */
- private void updateServerStatusIconAndText(RemoteOperationResult result) {
- mServerStatusIcon = R.drawable.ic_alert; // the most common case in the switch below
-
- switch (result.getCode()) {
- case OK_SSL:
- mServerStatusIcon = R.drawable.ic_lock_white;
- mServerStatusText = getResources().getString(R.string.auth_secure_connection);
- break;
-
- case OK_NO_SSL:
- case OK:
- if (mHostUrlInput.getText().toString().trim().toLowerCase(Locale.ROOT).startsWith(HTTP_PROTOCOL)) {
- mServerStatusText = getResources().getString(R.string.auth_connection_established);
- mServerStatusIcon = R.drawable.ic_ok;
- } else {
- mServerStatusText = getResources().getString(R.string.auth_nossl_plain_ok_title);
- mServerStatusIcon = R.drawable.ic_lock_open_white;
- }
- break;
-
- case NO_NETWORK_CONNECTION:
- mServerStatusIcon = R.drawable.no_network;
- mServerStatusText = getResources().getString(R.string.auth_no_net_conn_title);
- break;
-
- case SSL_RECOVERABLE_PEER_UNVERIFIED:
- mServerStatusText = getResources().getString(R.string.auth_ssl_unverified_server_title);
- break;
- case BAD_OC_VERSION:
- mServerStatusText = getResources().getString(R.string.auth_bad_oc_version_title);
- break;
- case WRONG_CONNECTION:
- mServerStatusText = getResources().getString(R.string.auth_wrong_connection_title);
- break;
- case TIMEOUT:
- mServerStatusText = getResources().getString(R.string.auth_timeout_title);
- break;
- case INCORRECT_ADDRESS:
- mServerStatusText = getResources().getString(R.string.auth_incorrect_address_title);
- break;
- case SSL_ERROR:
- mServerStatusText = getResources().getString(R.string.auth_ssl_general_error_title);
- break;
- case UNAUTHORIZED:
- mServerStatusText = getResources().getString(R.string.auth_unauthorized);
- break;
- case HOST_NOT_AVAILABLE:
- mServerStatusText = getResources().getString(R.string.auth_unknown_host_title);
- break;
- case INSTANCE_NOT_CONFIGURED:
- mServerStatusText = getResources().getString(R.string.auth_not_configured_title);
- break;
- case FILE_NOT_FOUND:
- mServerStatusText = getResources().getString(R.string.auth_incorrect_path_title);
- break;
- case OAUTH2_ERROR:
- mServerStatusText = getResources().getString(R.string.auth_oauth_error);
- break;
- case OAUTH2_ERROR_ACCESS_DENIED:
- mServerStatusText = getResources().getString(R.string.auth_oauth_error_access_denied);
- break;
- case UNHANDLED_HTTP_CODE:
- mServerStatusText = getResources().getString(R.string.auth_unknown_error_http_title);
- break;
- case UNKNOWN_ERROR:
- if (result.getException() != null &&
- !TextUtils.isEmpty(result.getException().getMessage())) {
- mServerStatusText = getResources().getString(
- R.string.auth_unknown_error_exception_title,
- result.getException().getMessage()
- );
- } else {
- mServerStatusText = getResources().getString(R.string.auth_unknown_error_title);
- }
- break;
- case OK_REDIRECT_TO_NON_SECURE_CONNECTION:
- mServerStatusIcon = R.drawable.ic_lock_open_white;
- mServerStatusText = getResources().getString(R.string.auth_redirect_non_secure_connection_title);
- break;
- case MAINTENANCE_MODE:
- mServerStatusText = getResources().getString(R.string.maintenance_mode);
- break;
- case UNTRUSTED_DOMAIN:
- mServerStatusText = getResources().getString(R.string.untrusted_domain);
- break;
- default:
- mServerStatusText = "";
- mServerStatusIcon = 0;
- break;
- }
- }
-
-
- /**
- * Chooses the right icon and text to show to the user for the received operation result.
- *
- * @param result Result of a remote operation performed in this activity
- */
- private void updateAuthStatusIconAndText(RemoteOperationResult result) {
- mAuthStatusIcon = R.drawable.ic_alert; // the most common case in the switch below
-
- switch (result.getCode()) {
- case OK_SSL:
- mAuthStatusIcon = R.drawable.ic_lock_white;
- mAuthStatusText = getResources().getString(R.string.auth_secure_connection);
- break;
-
- case OK_NO_SSL:
- case OK:
- if (mHostUrlInput.getText().toString().trim().toLowerCase(Locale.ROOT).startsWith(HTTP_PROTOCOL)) {
- mAuthStatusText = getResources().getString(R.string.auth_connection_established);
- mAuthStatusIcon = R.drawable.ic_ok;
- } else {
- mAuthStatusText = getResources().getString(R.string.auth_nossl_plain_ok_title);
- mAuthStatusIcon = R.drawable.ic_lock_open_white;
- }
- break;
-
- case NO_NETWORK_CONNECTION:
- mAuthStatusIcon = R.drawable.no_network;
- mAuthStatusText = getResources().getString(R.string.auth_no_net_conn_title);
- break;
-
- case SSL_RECOVERABLE_PEER_UNVERIFIED:
- mAuthStatusText = getResources().getString(R.string.auth_ssl_unverified_server_title);
- break;
- case TIMEOUT:
- mAuthStatusText = getResources().getString(R.string.auth_timeout_title);
- break;
- case HOST_NOT_AVAILABLE:
- mAuthStatusText = getResources().getString(R.string.auth_unknown_host_title);
- break;
- case UNHANDLED_HTTP_CODE:
- default:
- mAuthStatusText = ErrorMessageAdapter.getErrorCauseMessage(result, null, getResources());
- }
- }
-
- private void updateStatusIconFailUserName(int failedStatusText) {
- mAuthStatusIcon = R.drawable.ic_alert;
- mAuthStatusText = getResources().getString(failedStatusText);
- }
-
- private void updateServerStatusIconNoRegularAuth() {
- mServerStatusIcon = R.drawable.ic_alert;
- mServerStatusText = getResources().getString(R.string.auth_can_not_auth_against_server);
- }
-
- /**
- * Processes the result of the request for and access token send
- * to an OAuth authorization server.
- *
- * @param result Result of the operation.
- */
- private void onGetOAuthAccessTokenFinish(RemoteOperationResult result) {
- mWaitingForOpId = Long.MAX_VALUE;
- dismissDialog(WAIT_DIALOG_TAG);
-
- if (result.isSuccess()) {
- /// be gentle with the user
- IndeterminateProgressDialog dialog =
- IndeterminateProgressDialog.newInstance(R.string.auth_trying_to_login, true);
- dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
-
- /// time to test the retrieved access token on the ownCloud server
- @SuppressWarnings("unchecked")
- Map tokens = (Map) (result.getData().get(0));
- mAuthToken = tokens.get(OAuth2Constants.KEY_ACCESS_TOKEN);
- Log_OC.d(TAG, "Got ACCESS TOKEN: " + mAuthToken);
-
- /// validate token accessing to root folder / getting session
- OwnCloudCredentials credentials = OwnCloudCredentialsFactory.newBearerCredentials(
- mAuthToken);
- accessRootFolder(credentials);
-
- } else {
- updateAuthStatusIconAndText(result);
- showAuthStatus();
- Log_OC.d(TAG, "Access failed: " + result.getLogMessage());
- }
- }
-
-
- /**
- * Processes the result of the access check performed to try the user credentials.
- *
- * Creates a new account through the AccountManager.
- *
- * @param result Result of the operation.
- */
- @Override
- public void onAuthenticatorTaskCallback(RemoteOperationResult result) {
- mWaitingForOpId = Long.MAX_VALUE;
- dismissDialog(WAIT_DIALOG_TAG);
- mAsyncTask = null;
-
- if (result.isSuccess()) {
- Log_OC.d(TAG, "Successful access - time to save the account");
-
- boolean success = false;
-
- if (mAction == ACTION_CREATE) {
- success = createAccount(result);
-
- } else {
- try {
- updateAccountAuthentication();
- success = true;
-
- } catch (AccountNotFoundException e) {
- Log_OC.e(TAG, "Account " + mAccount + " was removed!", e);
- DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.auth_account_does_not_exist);
- finish();
- }
- }
-
- // Reset webView
- webViewPassword = null;
- webViewUser = null;
- forceOldLoginMethod = false;
- deleteCookies();
-
- if (success) {
- finish();
- } else {
- // init webView again
- if (mLoginWebView != null) {
- mLoginWebView.setVisibility(View.GONE);
- }
- setContentView(R.layout.account_setup);
-
- initOverallUi();
-
- CustomEditText serverAddressField = findViewById(R.id.hostUrlInput);
- serverAddressField.setText(mServerInfo.mBaseUrl);
-
- findViewById(R.id.oauth_onOff_check).setVisibility(View.GONE);
- findViewById(R.id.server_status_text).setVisibility(View.GONE);
- mAuthStatusView = findViewById(R.id.auth_status_text);
-
- showAuthStatus();
- }
-
- } else if (result.isServerFail() || result.isException()) {
- /// server errors or exceptions in authorization take to requiring a new check of
- /// the server
- mServerIsChecked = true;
- mServerIsValid = false;
- mServerInfo = new GetServerInfoOperation.ServerInfo();
-
- // update status icon and text
- updateServerStatusIconAndText(result);
- showServerStatus();
- mAuthStatusIcon = 0;
- mAuthStatusText = "";
- if (!webViewLoginMethod) {
- showAuthStatus();
-
- // update input controls state
- showRefreshButton(true);
- mOkButton.setEnabled(false);
- }
-
- // very special case (TODO: move to a common place for all the remote operations)
- if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) {
- showUntrustedCertDialog(result);
- }
-
- } else { // authorization fail due to client side - probably wrong credentials
- if (webViewLoginMethod) {
- mLoginWebView = findViewById(R.id.login_webview);
- initWebViewLogin(mServerInfo.mBaseUrl);
-
- DisplayUtils.showSnackMessage(this, mLoginWebView, R.string.auth_access_failed, result.getLogMessage());
- } else {
- updateAuthStatusIconAndText(result);
- showAuthStatus();
- }
- // reset webview
- webViewPassword = null;
- webViewUser = null;
- deleteCookies();
-
- Log_OC.d(TAG, "Access failed: " + result.getLogMessage());
- }
- }
-
-
- /**
- * Updates the authentication token.
- *
- * Sets the proper response so that the AccountAuthenticator that started this activity
- * saves a new authorization token for mAccount.
- *
- * Kills the session kept by OwnCloudClientManager so that a new one will created with
- * the new credentials when needed.
- */
- private void updateAccountAuthentication() throws AccountNotFoundException {
- String accountType = MainApp.getAccountType(this);
-
- Bundle response = new Bundle();
- response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
- response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type);
-
- if (AccountTypeUtils.getAuthTokenTypeAccessToken(accountType).equals(mAuthTokenType)) {
- response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken);
- // the next line is necessary, notifications are calling directly to the
- // AuthenticatorActivity to update, without AccountManager intervention
- mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken);
-
- } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(accountType).equals(mAuthTokenType)) {
- response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken);
- // the next line is necessary; by now, notifications are calling directly to the
- // AuthenticatorActivity to update, without AccountManager intervention
- mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken);
-
- } else {
- if (!webViewLoginMethod) {
- response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString());
- mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString());
- } else {
- response.putString(AccountManager.KEY_AUTHTOKEN, webViewPassword);
- mAccountMgr.setPassword(mAccount, webViewPassword);
- }
- }
-
- // remove managed clients for this account to enforce creation with fresh credentials
- OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, this);
- OwnCloudClientManagerFactory.getDefaultSingleton().removeClientFor(ocAccount);
-
- setAccountAuthenticatorResult(response);
- final Intent intent = new Intent();
- intent.putExtras(response);
- setResult(RESULT_OK, intent);
-
- }
-
-
- /**
- * Creates a new account through the Account Authenticator that started this activity.
- *
- * This makes the account permanent.
- *
- * TODO Decide how to name the OAuth accounts
- */
- @SuppressFBWarnings("DMI")
- private boolean createAccount(RemoteOperationResult authResult) {
- String accountType = MainApp.getAccountType(this);
-
- // create and save new ownCloud account
- boolean isOAuth = AccountTypeUtils.getAuthTokenTypeAccessToken(accountType).equals(mAuthTokenType);
- boolean isSaml = AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(accountType).equals(mAuthTokenType);
-
- String lastPermanentLocation = authResult.getLastPermanentLocation();
- if (lastPermanentLocation != null) {
- mServerInfo.mBaseUrl = AuthenticatorUrlUtils.trimWebdavSuffix(lastPermanentLocation);
- }
-
- Uri uri = Uri.parse(mServerInfo.mBaseUrl);
- String username;
- if (!webViewLoginMethod) {
- username = mUsernameInput.getText().toString().trim();
- } else {
- username = webViewUser;
- }
- if (isOAuth) {
- username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong();
- }
-
- String accountName = com.owncloud.android.lib.common.accounts.AccountUtils.buildAccountName(uri, username);
- Account newAccount = new Account(accountName, accountType);
- if (AccountUtils.exists(newAccount, getApplicationContext())) {
- // fail - not a new account, but an existing one; disallow
- RemoteOperationResult result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_NEW);
-
- updateAuthStatusIconAndText(result);
- showAuthStatus();
-
- Log_OC.d(TAG, result.getLogMessage());
- return false;
-
- } else {
- mAccount = newAccount;
-
- if (isOAuth || isSaml) {
- // with external authorizations, the password is never input in the app
- mAccountMgr.addAccountExplicitly(mAccount, "", null);
- } else {
- if (!webViewLoginMethod) {
- mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null);
- } else {
- mAccountMgr.addAccountExplicitly(mAccount, webViewPassword, null);
- }
- }
-
- // include account version with the new account
- mAccountMgr.setUserData(mAccount, Constants.KEY_OC_ACCOUNT_VERSION,
- Integer.toString(AccountUtils.ACCOUNT_VERSION));
-
- /// add the new account as default in preferences, if there is none already
- Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this);
- if (defaultAccount == null) {
- SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
- editor.putString("select_oc_account", accountName);
- editor.apply();
- }
-
- /// prepare result to return to the Authenticator
- // TODO check again what the Authenticator makes with it; probably has the same
- // effect as addAccountExplicitly, but it's not well done
- final Intent intent = new Intent();
- intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType);
- intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
- intent.putExtra(AccountManager.KEY_USERDATA, username);
- if (isOAuth || isSaml) {
- mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken);
- }
- /// add user data to the new account; TODO probably can be done in the last parameter
- // addAccountExplicitly, or in KEY_USERDATA
- mAccountMgr.setUserData(mAccount, Constants.KEY_OC_VERSION, mServerInfo.mVersion.getVersion());
- mAccountMgr.setUserData(mAccount, Constants.KEY_OC_BASE_URL, mServerInfo.mBaseUrl);
-
- if (authResult.getData() != null) {
- try {
- UserInfo userInfo = (UserInfo) authResult.getData().get(0);
- mAccountMgr.setUserData(mAccount, Constants.KEY_DISPLAY_NAME, userInfo.getDisplayName());
- mAccountMgr.setUserData(mAccount, Constants.KEY_USER_ID, userInfo.getId());
- } catch (ClassCastException c) {
- Log_OC.w(TAG, "Couldn't get display name for " + username);
- }
- } else {
- Log_OC.w(TAG, "Couldn't get display name for " + username);
- }
-
- if (isSaml) {
- mAccountMgr.setUserData(mAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO, "TRUE");
- } else if (isOAuth) {
- mAccountMgr.setUserData(mAccount, Constants.KEY_SUPPORTS_OAUTH2, "TRUE");
- }
-
- setAccountAuthenticatorResult(intent.getExtras());
- setResult(RESULT_OK, intent);
-
- return true;
- }
- }
-
-
- /**
- * Starts and activity to open the 'new account' page in the ownCloud web site
- *
- * @param view 'Account register' button
- */
- public void onRegisterClick(View view) {
- Intent register = new Intent(
- Intent.ACTION_VIEW, Uri.parse(getString(R.string.welcome_link_url))
- );
- setResult(RESULT_CANCELED);
- startActivity(register);
- }
-
-
- /**
- * Updates the content and visibility state of the icon and text associated
- * to the last check on the ownCloud server.
- */
- private void showServerStatus() {
- if (mServerStatusIcon == 0 && "".equals(mServerStatusText)) {
- mServerStatusView.setVisibility(View.INVISIBLE);
- } else {
- mServerStatusView.setText(mServerStatusText);
- mServerStatusView.setCompoundDrawablesWithIntrinsicBounds(mServerStatusIcon, 0, 0, 0);
- mServerStatusView.setVisibility(View.VISIBLE);
- }
- }
-
-
- /**
- * Updates the content and visibility state of the icon and text associated
- * to the interactions with the OAuth authorization server.
- */
- private void showAuthStatus() {
- if (mAuthStatusIcon == 0 && "".equals(mAuthStatusText)) {
- mAuthStatusView.setVisibility(View.INVISIBLE);
- } else {
- mAuthStatusView.setText(mAuthStatusText);
- mAuthStatusView.setCompoundDrawablesWithIntrinsicBounds(mAuthStatusIcon, 0, 0, 0);
- mAuthStatusView.setVisibility(View.VISIBLE);
- }
- }
-
-
- private void showRefreshButton(boolean show) {
- if (webViewLoginMethod && mRefreshButton != null) {
- if (show) {
- mRefreshButton.setVisibility(View.VISIBLE);
- } else {
- mRefreshButton.setVisibility(View.GONE);
- }
- }
- }
-
- /**
- * Called when the eye icon in the password field is clicked.
- *
- * Toggles the visibility of the password in the field.
- */
- public void onViewPasswordClick() {
- int selectionStart = mPasswordInput.getSelectionStart();
- int selectionEnd = mPasswordInput.getSelectionEnd();
- if (isPasswordVisible()) {
- hidePassword();
- } else {
- showPassword();
- }
- mPasswordInput.setSelection(selectionStart, selectionEnd);
- }
-
-
- /**
- * Called when the checkbox for OAuth authorization is clicked.
- *
- * Hides or shows the input fields for user & password.
- *
- * @param view 'View password' 'button'
- */
- public void onCheckClick(View view) {
- CheckBox oAuth2Check = (CheckBox) view;
- if (oAuth2Check.isChecked()) {
- mAuthTokenType = oauthTokenType;
- } else {
- mAuthTokenType = basicTokenType;
- }
- updateAuthenticationPreFragmentVisibility();
- }
-
-
- /**
- * Called when the 'action' button in an IME is pressed ('enter' in software keyboard).
- *
- * Used to trigger the authentication check when the user presses 'enter' after writing the
- * password, or to throw the server test when the only field on screen is the URL input field.
- */
- @Override
- public boolean onEditorAction(TextView inputField, int actionId, KeyEvent event) {
- if (actionId == EditorInfo.IME_ACTION_DONE && inputField != null &&
- inputField.equals(mPasswordInput)) {
- if (mOkButton.isEnabled()) {
- mOkButton.performClick();
- }
-
- } else if (actionId == EditorInfo.IME_ACTION_NEXT && inputField != null &&
- inputField.equals(mHostUrlInput)) {
- checkOcServer();
- }
- return false; // always return false to grant that the software keyboard is hidden anyway
- }
-
-
- private abstract static class RightDrawableOnTouchListener implements OnTouchListener {
-
- private int fuzz = 75;
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean onTouch(View view, MotionEvent event) {
- Drawable rightDrawable = null;
- if (view instanceof TextView) {
- Drawable[] drawables = ((TextView) view).getCompoundDrawables();
- if (drawables.length > 2) {
- rightDrawable = drawables[2];
- }
- }
- if (rightDrawable != null) {
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- final Rect bounds = rightDrawable.getBounds();
- if (x >= (view.getRight() - bounds.width() - fuzz) &&
- x <= (view.getRight() - view.getPaddingRight() + fuzz) &&
- y >= (view.getPaddingTop() - fuzz) &&
- y <= (view.getHeight() - view.getPaddingBottom()) + fuzz) {
-
- return onDrawableTouch(event);
- }
- }
- return false;
- }
-
- public abstract boolean onDrawableTouch(final MotionEvent event);
- }
-
-
- private void getRemoteUserNameOperation(String sessionCookie) {
-
- Intent getUserNameIntent = new Intent();
- getUserNameIntent.setAction(OperationsService.ACTION_GET_USER_NAME);
- getUserNameIntent.putExtra(OperationsService.EXTRA_SERVER_URL, mServerInfo.mBaseUrl);
- getUserNameIntent.putExtra(OperationsService.EXTRA_COOKIE, sessionCookie);
-
- if (mOperationsServiceBinder != null) {
- mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getUserNameIntent);
- }
- }
-
-
- @Override
- public void onSsoFinished(String sessionCookie) {
- if (sessionCookie != null && sessionCookie.length() > 0) {
- Log_OC.d(TAG, "Successful SSO - time to save the account");
- mAuthToken = sessionCookie;
- getRemoteUserNameOperation(sessionCookie);
- Fragment fd = getSupportFragmentManager().findFragmentByTag(SAML_DIALOG_TAG);
- if (fd instanceof DialogFragment) {
- Dialog d = ((DialogFragment) fd).getDialog();
- if (d != null && d.isShowing()) {
- d.dismiss();
- }
- }
-
- } else {
- // TODO - show fail
- Log_OC.d(TAG, "SSO failed");
- }
-
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType(this)).equals(mAuthTokenType) &&
- mHostUrlInput.hasFocus() && event.getAction() == MotionEvent.ACTION_DOWN) {
- checkOcServer();
- }
- return super.onTouchEvent(event);
- }
-
-
- /**
- * Show untrusted cert dialog
- */
- public void showUntrustedCertDialog(X509Certificate x509Certificate, SslError error, SslErrorHandler handler) {
- // Show a dialog with the certificate info
- SslUntrustedCertDialog dialog;
- if (x509Certificate == null) {
- dialog = SslUntrustedCertDialog.newInstanceForEmptySslError(error, handler);
- } else {
- dialog = SslUntrustedCertDialog.newInstanceForFullSslError(x509Certificate, error, handler);
- }
- FragmentManager fm = getSupportFragmentManager();
- FragmentTransaction ft = fm.beginTransaction();
- ft.addToBackStack(null);
- dialog.show(ft, UNTRUSTED_CERT_DIALOG_TAG);
- }
-
-
- /**
- * Show untrusted cert dialog
- */
- private void showUntrustedCertDialog(RemoteOperationResult result) {
- // Show a dialog with the certificate info
- SslUntrustedCertDialog dialog = SslUntrustedCertDialog.
- newInstanceForFullSslError((CertificateCombinedException) result.getException());
- FragmentManager fm = getSupportFragmentManager();
- FragmentTransaction ft = fm.beginTransaction();
- ft.addToBackStack(null);
- dialog.show(ft, UNTRUSTED_CERT_DIALOG_TAG);
-
- }
-
- /**
- * Called from SslValidatorDialog when a new server certificate was correctly saved.
- */
- public void onSavedCertificate() {
- Fragment fd = getSupportFragmentManager().findFragmentByTag(SAML_DIALOG_TAG);
- if (fd == null) {
- // if SAML dialog is not shown,
- // the SslDialog was shown due to an SSL error in the server check
- checkOcServer();
- }
- }
-
- /**
- * Called from SslValidatorDialog when a new server certificate could not be saved
- * when the user requested it.
- */
- @Override
- public void onFailedSavingCertificate() {
- dismissDialog(SAML_DIALOG_TAG);
- DisplayUtils.showSnackMessage(this, R.string.ssl_validator_not_saved);
- }
-
- @Override
- public void onCancelCertificate() {
- dismissDialog(SAML_DIALOG_TAG);
- }
-
-
- private void doOnResumeAndBound() {
- //Log_OC.e(TAG, "registering to listen for operation callbacks" );
- mOperationsServiceBinder.addOperationListener(AuthenticatorActivity.this, mHandler);
- if (mWaitingForOpId <= Integer.MAX_VALUE) {
- mOperationsServiceBinder.dispatchResultIfFinished((int) mWaitingForOpId, this);
- }
-
- if (!webViewLoginMethod && mHostUrlInput.getText() != null && mHostUrlInput.getText().length() > 0
- && !mServerIsChecked) {
- checkOcServer();
- }
- }
-
-
- private void dismissDialog(String dialogTag) {
- Fragment frag = getSupportFragmentManager().findFragmentByTag(dialogTag);
- if (frag instanceof DialogFragment) {
- DialogFragment dialog = (DialogFragment) frag;
-
- try {
- dialog.dismiss();
- } catch (IllegalStateException e) {
- Log_OC.e(TAG, e.getMessage());
- dialog.dismissAllowingStateLoss();
- }
- }
- }
-
-
- /**
- * Implements callback methods for service binding.
- */
- private class OperationsServiceConnection implements ServiceConnection {
-
- @Override
- public void onServiceConnected(ComponentName component, IBinder service) {
- if (component.equals(
- new ComponentName(AuthenticatorActivity.this, OperationsService.class)
- )) {
- mOperationsServiceBinder = (OperationsServiceBinder) service;
-
- doOnResumeAndBound();
-
- }
-
- }
-
- @Override
- public void onServiceDisconnected(ComponentName component) {
- if (component.equals(
- new ComponentName(AuthenticatorActivity.this, OperationsService.class)
- )) {
- Log_OC.e(TAG, "Operations service crashed");
- mOperationsServiceBinder = null;
- }
- }
- }
-
- /**
- * Create and show dialog for request authentication to the user
- *
- * @param webView Web view to embed into the authentication dialog.
- * @param handler Object responsible for catching and recovering HTTP authentication fails.
- */
- public void createAuthenticationDialog(WebView webView, HttpAuthHandler handler) {
-
- // Show a dialog with the certificate info
- CredentialsDialogFragment dialog = CredentialsDialogFragment.newInstanceForCredentials(webView, handler);
- FragmentManager fm = getSupportFragmentManager();
- FragmentTransaction ft = fm.beginTransaction();
- ft.addToBackStack(null);
- dialog.setCancelable(false);
- dialog.show(ft, CREDENTIALS_DIALOG_TAG);
-
- if (!mIsFirstAuthAttempt) {
- DisplayUtils.showSnackMessage(this, R.string.saml_authentication_wrong_pass);
- } else {
- mIsFirstAuthAttempt = false;
- }
- }
-
- /**
- * For retrieving the clicking on authentication cancel button.
- */
- public void doNegativeAuthenticationDialogClick() {
- mIsFirstAuthAttempt = true;
- }
-}
+/*
+ * ownCloud Android client application
+ *
+ * @author Bartek Przybylski
+ * @author David A. Velasco
+ * @author masensio
+ * @author Mario Danic
+ * Copyright (C) 2012 Bartek Przybylski
+ * Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2017 Mario Danic
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * All changes by Mario Danic are distributed under the following terms:
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program 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 program. If not, see .
+ *
+ */
+
+package com.owncloud.android.authentication;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.net.http.SslError;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.support.annotation.Nullable;
+import android.support.design.widget.Snackbar;
+import android.support.design.widget.TextInputLayout;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AndroidRuntimeException;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnFocusChangeListener;
+import android.view.View.OnTouchListener;
+import android.view.inputmethod.EditorInfo;
+import android.webkit.CookieManager;
+import android.webkit.CookieSyncManager;
+import android.webkit.HttpAuthHandler;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.owncloud.android.MainApp;
+import com.owncloud.android.R;
+import com.owncloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+import com.owncloud.android.lib.common.OwnCloudCredentials;
+import com.owncloud.android.lib.common.OwnCloudCredentialsFactory;
+import com.owncloud.android.lib.common.UserInfo;
+import com.owncloud.android.lib.common.accounts.AccountTypeUtils;
+import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
+import com.owncloud.android.lib.common.accounts.AccountUtils.Constants;
+import com.owncloud.android.lib.common.network.CertificateCombinedException;
+import com.owncloud.android.lib.common.network.NetworkUtils;
+import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.status.OwnCloudVersion;
+import com.owncloud.android.lib.resources.users.GetRemoteUserInfoOperation;
+import com.owncloud.android.operations.DetectAuthenticationMethodOperation.AuthenticationMethod;
+import com.owncloud.android.operations.GetServerInfoOperation;
+import com.owncloud.android.operations.OAuth2GetAccessToken;
+import com.owncloud.android.services.OperationsService;
+import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
+import com.owncloud.android.ui.activity.FirstRunActivity;
+import com.owncloud.android.ui.components.CustomEditText;
+import com.owncloud.android.ui.dialog.CredentialsDialogFragment;
+import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
+import com.owncloud.android.ui.dialog.SamlWebViewDialog;
+import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
+import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.ErrorMessageAdapter;
+
+import java.io.InputStream;
+import java.net.URLDecoder;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * This Activity is used to add an ownCloud account to the App
+ */
+public class AuthenticatorActivity extends AccountAuthenticatorActivity
+ implements OnRemoteOperationListener, OnFocusChangeListener, OnEditorActionListener,
+ SsoWebViewClientListener, OnSslUntrustedCertListener,
+ AuthenticatorAsyncTask.OnAuthenticatorTaskListener {
+
+ private static final String TAG = AuthenticatorActivity.class.getSimpleName();
+
+ public static final String EXTRA_ACTION = "ACTION";
+ public static final String EXTRA_ACCOUNT = "ACCOUNT";
+ public static final String EXTRA_USE_PROVIDER_AS_WEBLOGIN = "USE_PROVIDER_AS_WEBLOGIN";
+
+ private static final String KEY_AUTH_TOKEN_TYPE = "AUTH_TOKEN_TYPE";
+
+ private static final String KEY_HOST_URL_TEXT = "HOST_URL_TEXT";
+ private static final String KEY_OC_VERSION = "OC_VERSION";
+ private static final String KEY_SERVER_VALID = "SERVER_VALID";
+ private static final String KEY_SERVER_CHECKED = "SERVER_CHECKED";
+ private static final String KEY_SERVER_STATUS_TEXT = "SERVER_STATUS_TEXT";
+ private static final String KEY_SERVER_STATUS_ICON = "SERVER_STATUS_ICON";
+ private static final String KEY_IS_SSL_CONN = "IS_SSL_CONN";
+ private static final String KEY_PASSWORD_EXPOSED = "PASSWORD_VISIBLE";
+ private static final String KEY_AUTH_STATUS_TEXT = "AUTH_STATUS_TEXT";
+ private static final String KEY_AUTH_STATUS_ICON = "AUTH_STATUS_ICON";
+ private static final String KEY_SERVER_AUTH_METHOD = "SERVER_AUTH_METHOD";
+ private static final String KEY_WAITING_FOR_OP_ID = "WAITING_FOR_OP_ID";
+ private static final String KEY_AUTH_TOKEN = "AUTH_TOKEN";
+
+ private static final String AUTH_ON = "on";
+ private static final String AUTH_OPTIONAL = "optional";
+
+ public static final byte ACTION_CREATE = 0;
+ public static final byte ACTION_UPDATE_EXPIRED_TOKEN = 2; // detected by the app
+
+ private static final String UNTRUSTED_CERT_DIALOG_TAG = "UNTRUSTED_CERT_DIALOG";
+ private static final String SAML_DIALOG_TAG = "SAML_DIALOG";
+ private static final String WAIT_DIALOG_TAG = "WAIT_DIALOG";
+ private static final String CREDENTIALS_DIALOG_TAG = "CREDENTIALS_DIALOG";
+ private static final String KEY_AUTH_IS_FIRST_ATTEMPT_TAG = "KEY_AUTH_IS_FIRST_ATTEMPT";
+
+ private static final String KEY_USERNAME = "USERNAME";
+ private static final String KEY_PASSWORD = "PASSWORD";
+ private static final String KEY_ASYNC_TASK_IN_PROGRESS = "AUTH_IN_PROGRESS";
+ private static final String WEB_LOGIN = "/index.php/login/flow";
+ public static final String PROTOCOL_SUFFIX = "://";
+ public static final String LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":";
+ public static final String HTTPS_PROTOCOL = "https://";
+ public static final String HTTP_PROTOCOL = "http://";
+
+ public static final String REGULAR_SERVER_INPUT_TYPE = "regular";
+ public static final String SUBDOMAIN_SERVER_INPUT_TYPE = "prefix";
+ public static final String DIRECTORY_SERVER_INPUT_TYPE = "suffix";
+
+ /// parameters from EXTRAs in starter Intent
+ private byte mAction;
+ private Account mAccount;
+ private String mAuthTokenType;
+
+ /// activity-level references / state
+ private final Handler mHandler = new Handler();
+ private ServiceConnection mOperationsServiceConnection = null;
+ private OperationsServiceBinder mOperationsServiceBinder = null;
+ private AccountManager mAccountMgr;
+ private Uri mNewCapturedUriFromOAuth2Redirection;
+
+ /// Server PRE-Fragment elements
+ private CustomEditText mHostUrlInput;
+ private View mRefreshButton;
+ private TextView mServerStatusView;
+
+ private TextWatcher mHostUrlInputWatcher;
+ private String mServerStatusText = "";
+ private int mServerStatusIcon;
+
+ private boolean mServerIsChecked;
+ private boolean mServerIsValid;
+
+ private GetServerInfoOperation.ServerInfo mServerInfo = new GetServerInfoOperation.ServerInfo();
+
+ /// Authentication PRE-Fragment elements
+ private CheckBox mOAuth2Check;
+ private TextView mOAuthAuthEndpointText;
+ private TextView mOAuthTokenEndpointText;
+ private EditText mUsernameInput;
+ private EditText mPasswordInput;
+ private View mOkButton;
+ private TextView mAuthStatusView;
+ private ImageButton mTestServerButton;
+
+ private WebView mLoginWebView;
+
+ private String mAuthStatusText = "";
+ private int mAuthStatusIcon;
+
+ private String mAuthToken = "";
+ private AuthenticatorAsyncTask mAsyncTask;
+
+ private boolean mIsFirstAuthAttempt;
+
+ /// Identifier of operation in progress which result shouldn't be lost
+ private long mWaitingForOpId = Long.MAX_VALUE;
+
+ private String basicTokenType;
+ private String oauthTokenType;
+ private String samlTokenType;
+
+ private boolean webViewLoginMethod;
+ private String webViewUser;
+ private String webViewPassword;
+ private TextInputLayout mUsernameInputLayout;
+ private TextInputLayout mPasswordInputLayout;
+ private boolean forceOldLoginMethod;
+
+ /**
+ * {@inheritDoc}
+ *
+ * IMPORTANT ENTRY POINT 1: activity is shown to the user
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ //Log_OC.e(TAG, "onCreate init");
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ FirstRunActivity.runIfNeeded(this);
+ }
+
+ basicTokenType = AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType(this));
+ oauthTokenType = AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType(this));
+ samlTokenType = AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType(this));
+
+ // delete cookies for webView
+ deleteCookies();
+
+ // Workaround, for fixing a problem with Android Library Support v7 19
+ //getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().hide();
+ getSupportActionBar().setDisplayHomeAsUpEnabled(false);
+ getSupportActionBar().setDisplayShowHomeEnabled(false);
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+ }
+
+ mIsFirstAuthAttempt = true;
+
+ /// init activity state
+ mAccountMgr = AccountManager.get(this);
+ mNewCapturedUriFromOAuth2Redirection = null;
+
+ /// get input values
+ mAction = getIntent().getByteExtra(EXTRA_ACTION, ACTION_CREATE);
+ mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);
+ if (savedInstanceState == null) {
+ initAuthTokenType();
+ } else {
+ mAuthTokenType = savedInstanceState.getString(KEY_AUTH_TOKEN_TYPE);
+ mWaitingForOpId = savedInstanceState.getLong(KEY_WAITING_FOR_OP_ID);
+ mIsFirstAuthAttempt = savedInstanceState.getBoolean(KEY_AUTH_IS_FIRST_ATTEMPT_TAG);
+ }
+
+ String webloginUrl;
+ boolean showLegacyLogin;
+ if (getIntent().getBooleanExtra(EXTRA_USE_PROVIDER_AS_WEBLOGIN, false)) {
+ webViewLoginMethod = true;
+ webloginUrl = getString(R.string.provider_registration_server);
+ showLegacyLogin = false;
+ } else {
+ webViewLoginMethod = !TextUtils.isEmpty(getResources().getString(R.string.webview_login_url));
+ webloginUrl = null;
+ showLegacyLogin = true;
+ }
+
+ if (webViewLoginMethod) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+
+ /// load user interface
+ if (!webViewLoginMethod) {
+ setContentView(R.layout.account_setup);
+
+ /// initialize general UI elements
+ initOverallUi();
+
+ findViewById(R.id.centeredRefreshButton).setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ checkOcServer();
+ }
+ });
+
+ findViewById(R.id.embeddedRefreshButton).setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ checkOcServer();
+ }
+ });
+
+ /// initialize block to be moved to single Fragment to check server and get info about it
+
+ /// initialize block to be moved to single Fragment to retrieve and validate credentials
+ initAuthorizationPreFragment(savedInstanceState);
+
+ } else {
+ setContentView(R.layout.account_setup_webview);
+ mLoginWebView = findViewById(R.id.login_webview);
+ initWebViewLogin(webloginUrl, showLegacyLogin);
+ }
+
+ initServerPreFragment(savedInstanceState);
+ }
+
+ private void deleteCookies() {
+ try {
+ CookieSyncManager.createInstance(this);
+ CookieManager cookieManager = CookieManager.getInstance();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ cookieManager.removeAllCookies(null);
+ } else {
+ cookieManager.removeAllCookie();
+ }
+ } catch (AndroidRuntimeException e) {
+ Log_OC.e(TAG, e.getMessage());
+ }
+ }
+
+ private static String getWebLoginUserAgent() {
+ return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) +
+ Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL;
+ }
+
+ @SuppressLint("SetJavaScriptEnabled")
+ private void initWebViewLogin(String baseURL, boolean showLegacyLogin) {
+ mLoginWebView.setVisibility(View.GONE);
+
+ final ProgressBar progressBar = findViewById(R.id.login_webview_progress_bar);
+
+ mLoginWebView.getSettings().setAllowFileAccess(false);
+ mLoginWebView.getSettings().setJavaScriptEnabled(true);
+ mLoginWebView.getSettings().setDomStorageEnabled(true);
+ mLoginWebView.getSettings().setUserAgentString(getWebLoginUserAgent());
+ mLoginWebView.getSettings().setSaveFormData(false);
+ mLoginWebView.getSettings().setSavePassword(false);
+
+ Map headers = new HashMap<>();
+ headers.put(RemoteOperation.OCS_API_HEADER, RemoteOperation.OCS_API_HEADER_VALUE);
+
+ String url;
+ if (baseURL != null && !baseURL.isEmpty()) {
+ url = baseURL;
+ } else {
+ url = getResources().getString(R.string.webview_login_url);
+ }
+
+ mLoginWebView.loadUrl(url, headers);
+
+ setClient(progressBar);
+
+ // show snackbar after 60s to switch back to old login method
+ if (showLegacyLogin) {
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ DisplayUtils.createSnackbar(mLoginWebView, R.string.fallback_weblogin_text, Snackbar.LENGTH_INDEFINITE)
+ .setActionTextColor(getResources().getColor(R.color.primary_dark))
+ .setAction(R.string.fallback_weblogin_back, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mLoginWebView.setVisibility(View.INVISIBLE);
+ webViewLoginMethod = false;
+
+ setContentView(R.layout.account_setup);
+
+ // initialize general UI elements
+ initOverallUi();
+
+ mPasswordInputLayout.setVisibility(View.VISIBLE);
+ mUsernameInputLayout.setVisibility(View.VISIBLE);
+ mUsernameInput.requestFocus();
+ mOAuth2Check.setVisibility(View.INVISIBLE);
+ mAuthStatusView.setVisibility(View.INVISIBLE);
+ mServerStatusView.setVisibility(View.INVISIBLE);
+ mTestServerButton.setVisibility(View.INVISIBLE);
+ forceOldLoginMethod = true;
+ mOkButton.setVisibility(View.VISIBLE);
+
+ initServerPreFragment(null);
+
+ mHostUrlInput.setText(baseURL);
+
+ checkOcServer();
+ }
+ }).show();
+ }
+ }, 60 * 1000);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (mLoginWebView != null && event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK:
+ if (mLoginWebView.canGoBack()) {
+ mLoginWebView.goBack();
+ } else {
+ finish();
+ }
+ return true;
+ }
+
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ private void setClient(ProgressBar progressBar) {
+ mLoginWebView.setWebViewClient(new WebViewClient() {
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ if (url.startsWith(getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/")) {
+ parseAndLoginFromWebView(url);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+
+ progressBar.setVisibility(View.GONE);
+ mLoginWebView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+ X509Certificate cert = SsoWebViewClient.getX509CertificateFromError(error);
+
+ try {
+ if (cert != null && NetworkUtils.isCertInKnownServersStore(cert, getApplicationContext())) {
+ handler.proceed();
+ } else {
+ showUntrustedCertDialog(cert, error, handler);
+ }
+ } catch (Exception e) {
+ Log_OC.e(TAG, "Cert could not be verified");
+ }
+ }
+
+ public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
+ progressBar.setVisibility(View.GONE);
+ mLoginWebView.setVisibility(View.VISIBLE);
+
+ InputStream resources = getResources().openRawResource(R.raw.custom_error);
+ String customError = DisplayUtils.getData(resources);
+
+ if (!customError.isEmpty()) {
+ mLoginWebView.loadData(customError, "text/html; charset=UTF-8", null);
+ }
+ }
+ });
+ }
+
+ private void parseAndLoginFromWebView(String dataString) {
+ String prefix = getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/";
+ LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, dataString);
+
+ if (loginUrlInfo != null) {
+ mServerInfo.mBaseUrl = AuthenticatorUrlUtils.normalizeUrlSuffix(loginUrlInfo.serverAddress);
+ webViewUser = loginUrlInfo.username;
+ webViewPassword = loginUrlInfo.password;
+ checkOcServer();
+ }
+ }
+
+ private void populateLoginFields(String dataString) throws IllegalArgumentException {
+ // check if it is cloud://login/
+ if (dataString.startsWith(getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/")) {
+ String prefix = getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/";
+ LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, dataString);
+
+ if (loginUrlInfo != null) {
+ mHostUrlInput.setText(loginUrlInfo.serverAddress);
+ mUsernameInput.setText(loginUrlInfo.username);
+ mPasswordInput.setText(loginUrlInfo.password);
+
+ if (loginUrlInfo.serverAddress != null && !mServerIsChecked) {
+ onUrlInputFocusLost();
+ }
+ }
+ }
+ }
+
+ /**
+ * parses a URI string and returns a login data object with the information from the URI string.
+ *
+ * @param prefix URI beginning, e.g. cloud://login/
+ * @param dataString the complete URI
+ * @return login data
+ * @throws IllegalArgumentException when
+ */
+ public static LoginUrlInfo parseLoginDataUrl(String prefix, String dataString) throws IllegalArgumentException {
+ if (dataString.length() < prefix.length()) {
+ throw new IllegalArgumentException("Invalid login URL detected");
+ }
+ LoginUrlInfo loginUrlInfo = new LoginUrlInfo();
+
+ // format is basically xxx://login/server:xxx&user:xxx&password while all variables are optional
+ String data = dataString.substring(prefix.length());
+
+ // parse data
+ String[] values = data.split("&");
+
+ if (values.length < 1 || values.length > 3) {
+ // error illegal number of URL elements detected
+ throw new IllegalArgumentException("Illegal number of login URL elements detected: " + values.length);
+ }
+
+ for (String value : values) {
+ if (value.startsWith("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
+ loginUrlInfo.username = URLDecoder.decode(
+ value.substring(("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
+ } else if (value.startsWith("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
+ loginUrlInfo.password = URLDecoder.decode(
+ value.substring(("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
+ } else if (value.startsWith("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
+ loginUrlInfo.serverAddress = URLDecoder.decode(
+ value.substring(("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
+ } else {
+ // error illegal URL element detected
+ throw new IllegalArgumentException("Illegal magic login URL element detected: " + value);
+ }
+ }
+
+ return loginUrlInfo;
+ }
+
+ private void initAuthTokenType() {
+ mAuthTokenType = getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE);
+ if (mAuthTokenType == null) {
+ if (mAccount != null) {
+ boolean oAuthRequired = mAccountMgr.getUserData(mAccount, Constants.KEY_SUPPORTS_OAUTH2) != null;
+ boolean samlWebSsoRequired = (
+ mAccountMgr.getUserData
+ (mAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO) != null
+ );
+ mAuthTokenType = chooseAuthTokenType(oAuthRequired, samlWebSsoRequired);
+
+ } else {
+ boolean oAuthSupported = AUTH_ON.equals(getString(R.string.auth_method_oauth2));
+ boolean samlWebSsoSupported = AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso));
+ mAuthTokenType = chooseAuthTokenType(oAuthSupported, samlWebSsoSupported);
+ }
+ }
+ }
+
+ private String chooseAuthTokenType(boolean oauth, boolean saml) {
+ if (saml) {
+ return samlTokenType;
+ } else if (oauth) {
+ return oauthTokenType;
+ } else {
+ return basicTokenType;
+ }
+ }
+
+
+ /**
+ * Configures elements in the user interface under direct control of the Activity.
+ */
+ private void initOverallUi() {
+ mHostUrlInput = findViewById(R.id.hostUrlInput);
+ mUsernameInputLayout = findViewById(R.id.input_layout_account_username);
+ mPasswordInputLayout = findViewById(R.id.input_layout_account_password);
+ mPasswordInput = findViewById(R.id.account_password);
+ mUsernameInput = findViewById(R.id.account_username);
+ mAuthStatusView = findViewById(R.id.auth_status_text);
+ mOAuth2Check = findViewById(R.id.oauth_onOff_check);
+ mServerStatusView = findViewById(R.id.server_status_text);
+ mTestServerButton = findViewById(R.id.testServerButton);
+
+ mOkButton = findViewById(R.id.buttonOK);
+ mOkButton.setOnClickListener(v -> onOkClick());
+
+ setupInstructionMessage();
+
+ mTestServerButton.setVisibility(mAction == ACTION_CREATE ? View.VISIBLE : View.GONE);
+ }
+
+
+ private void setupInstructionMessage() {
+ String instructionsMessageText = calculateInstructionMessageText(mAction, mAuthTokenType);
+ TextView instructionsView = findViewById(R.id.instructions_message);
+
+ if (instructionsMessageText != null) {
+ instructionsView.setVisibility(View.VISIBLE);
+ instructionsView.setText(instructionsMessageText);
+ } else {
+ instructionsView.setVisibility(View.GONE);
+ }
+ }
+
+ @Nullable
+ private String calculateInstructionMessageText(byte action, String authTokenType) {
+ if (action == ACTION_UPDATE_EXPIRED_TOKEN) {
+ if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType(this)).equals(authTokenType)) {
+ return getString(R.string.auth_expired_oauth_token_toast);
+
+ } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType(this))
+ .equals(authTokenType)) {
+ return getString(R.string.auth_expired_saml_sso_token_toast);
+
+ } else {
+ return getString(R.string.auth_expired_basic_auth_toast);
+ }
+ }
+
+ return null;
+ }
+
+ public void onTestServerConnectionClick(View v) {
+ checkOcServer();
+ }
+
+
+ /**
+ * @param savedInstanceState Saved activity state, as in {{@link #onCreate(Bundle)}
+ */
+ private void initServerPreFragment(Bundle savedInstanceState) {
+
+ /// step 1 - load and process relevant inputs (resources, intent, savedInstanceState)
+ boolean isUrlInputAllowed = getResources().getBoolean(R.bool.show_server_url_input);
+ if (savedInstanceState == null) {
+ if (mAccount != null) {
+ mServerInfo.mBaseUrl = mAccountMgr.getUserData(mAccount, Constants.KEY_OC_BASE_URL);
+ // TODO do next in a setter for mBaseUrl
+ mServerInfo.mIsSslConn = mServerInfo.mBaseUrl.startsWith(HTTPS_PROTOCOL);
+ mServerInfo.mVersion = AccountUtils.getServerVersion(mAccount);
+ } else {
+ if (!webViewLoginMethod) {
+ mServerInfo.mBaseUrl = getString(R.string.server_url).trim();
+ } else {
+ mServerInfo.mBaseUrl = getString(R.string.webview_login_url).trim();
+ }
+ mServerInfo.mIsSslConn = mServerInfo.mBaseUrl.startsWith(HTTPS_PROTOCOL);
+ }
+ } else {
+ mServerStatusText = savedInstanceState.getString(KEY_SERVER_STATUS_TEXT);
+ mServerStatusIcon = savedInstanceState.getInt(KEY_SERVER_STATUS_ICON);
+
+ mServerIsValid = savedInstanceState.getBoolean(KEY_SERVER_VALID);
+ mServerIsChecked = savedInstanceState.getBoolean(KEY_SERVER_CHECKED);
+
+ // TODO parcelable
+ mServerInfo.mIsSslConn = savedInstanceState.getBoolean(KEY_IS_SSL_CONN);
+ mServerInfo.mBaseUrl = savedInstanceState.getString(KEY_HOST_URL_TEXT);
+ String ocVersion = savedInstanceState.getString(KEY_OC_VERSION);
+ if (ocVersion != null) {
+ mServerInfo.mVersion = new OwnCloudVersion(ocVersion);
+ }
+ mServerInfo.mAuthMethod = AuthenticationMethod.valueOf(
+ savedInstanceState.getString(KEY_SERVER_AUTH_METHOD));
+
+ }
+
+ if (!webViewLoginMethod) {
+ /// step 2 - set properties of UI elements (text, visibility, enabled...)
+ mHostUrlInput = findViewById(R.id.hostUrlInput);
+ // Convert IDN to Unicode
+ mHostUrlInput.setText(DisplayUtils.convertIdn(mServerInfo.mBaseUrl, false));
+ if (mAction != ACTION_CREATE) {
+ /// lock things that should not change
+ mHostUrlInput.setEnabled(false);
+ mHostUrlInput.setFocusable(false);
+ }
+ if (isUrlInputAllowed) {
+ mRefreshButton = findViewById(R.id.embeddedRefreshButton);
+ } else {
+ findViewById(R.id.hostUrlFrame).setVisibility(View.GONE);
+ mRefreshButton = findViewById(R.id.centeredRefreshButton);
+ }
+ showRefreshButton(mServerIsChecked && !mServerIsValid &&
+ mWaitingForOpId > Integer.MAX_VALUE);
+ mServerStatusView = findViewById(R.id.server_status_text);
+ showServerStatus();
+
+ /// step 3 - bind some listeners and options
+ mHostUrlInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
+ mHostUrlInput.setOnEditorActionListener(this);
+
+ /// step 4 - create listeners that will be bound at onResume
+ mHostUrlInputWatcher = new TextWatcher() {
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (mOkButton.isEnabled() &&
+ !mServerInfo.mBaseUrl.equals(
+ AuthenticatorUrlUtils.normalizeUrl(s.toString(), mServerInfo.mIsSslConn))) {
+ mOkButton.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // not used at the moment
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ if (mAuthStatusIcon != 0) {
+ Log_OC.d(TAG, "onTextChanged: hiding authentication status");
+ mAuthStatusIcon = 0;
+ mAuthStatusText = "";
+ showAuthStatus();
+ }
+ }
+ };
+
+
+ // TODO find out if this is really necessary, or if it can done in a different way
+ findViewById(R.id.scroll).setOnTouchListener(new OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN &&
+ AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(
+ MainApp.getAccountType(getBaseContext())).equals(mAuthTokenType) &&
+ mHostUrlInput.hasFocus()) {
+ checkOcServer();
+ }
+ return false;
+ }
+ });
+ }
+ }
+
+
+ /**
+ * @param savedInstanceState Saved activity state, as in {{@link #onCreate(Bundle)}
+ */
+ private void initAuthorizationPreFragment(Bundle savedInstanceState) {
+
+ /// step 0 - get UI elements in layout
+ mOAuth2Check = findViewById(R.id.oauth_onOff_check);
+ mOAuthAuthEndpointText = findViewById(R.id.oAuthEntryPoint_1);
+ mOAuthTokenEndpointText = findViewById(R.id.oAuthEntryPoint_2);
+ mUsernameInput = findViewById(R.id.account_username);
+ mPasswordInput = findViewById(R.id.account_password);
+ mAuthStatusView = findViewById(R.id.auth_status_text);
+
+ /// step 1 - load and process relevant inputs (resources, intent, savedInstanceState)
+ String presetUserName = null;
+ boolean isPasswordExposed = false;
+ if (savedInstanceState == null) {
+ if (mAccount != null) {
+ presetUserName = com.owncloud.android.lib.common.accounts.AccountUtils.getUsernameForAccount(mAccount);
+ }
+ } else {
+ isPasswordExposed = savedInstanceState.getBoolean(KEY_PASSWORD_EXPOSED, false);
+ mAuthStatusText = savedInstanceState.getString(KEY_AUTH_STATUS_TEXT);
+ mAuthStatusIcon = savedInstanceState.getInt(KEY_AUTH_STATUS_ICON);
+ mAuthToken = savedInstanceState.getString(KEY_AUTH_TOKEN);
+ }
+
+ /// step 2 - set properties of UI elements (text, visibility, enabled...)
+ mOAuth2Check.setChecked(
+ AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType(this)).equals(mAuthTokenType));
+ if (presetUserName != null) {
+ mUsernameInput.setText(presetUserName);
+ }
+ if (mAction != ACTION_CREATE) {
+ mUsernameInput.setEnabled(false);
+ mUsernameInput.setFocusable(false);
+ }
+ mPasswordInput.setText(""); // clean password to avoid social hacking
+ if (isPasswordExposed) {
+ showPassword();
+ }
+ updateAuthenticationPreFragmentVisibility();
+ showAuthStatus();
+ mOkButton.setEnabled(mServerIsValid);
+
+
+ /// step 3 - bind listeners
+ // bindings for password input field
+ mPasswordInput.setOnFocusChangeListener(this);
+ mPasswordInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
+ mPasswordInput.setOnEditorActionListener(this);
+ mPasswordInput.setOnTouchListener(new RightDrawableOnTouchListener() {
+ @Override
+ public boolean onDrawableTouch(final MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ AuthenticatorActivity.this.onViewPasswordClick();
+ }
+ return true;
+ }
+ });
+
+ }
+
+
+ /**
+ * Changes the visibility of input elements depending on
+ * the current authorization method.
+ */
+ private void updateAuthenticationPreFragmentVisibility() {
+ if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType(this)).equals(mAuthTokenType)) {
+ // SAML-based web Single Sign On
+ mOAuth2Check.setVisibility(View.GONE);
+ mOAuthAuthEndpointText.setVisibility(View.GONE);
+ mOAuthTokenEndpointText.setVisibility(View.GONE);
+ mUsernameInput.setVisibility(View.GONE);
+ mPasswordInput.setVisibility(View.GONE);
+
+ } else {
+ if (mAction == ACTION_CREATE &&
+ AUTH_OPTIONAL.equals(getString(R.string.auth_method_oauth2))) {
+ mOAuth2Check.setVisibility(View.VISIBLE);
+ } else {
+ mOAuth2Check.setVisibility(View.GONE);
+ }
+
+ if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType(this)).equals(mAuthTokenType)) {
+ // OAuth 2 authorization
+ mOAuthAuthEndpointText.setVisibility(View.VISIBLE);
+ mOAuthTokenEndpointText.setVisibility(View.VISIBLE);
+ mUsernameInput.setVisibility(View.GONE);
+ mPasswordInput.setVisibility(View.GONE);
+
+ } else {
+ // basic HTTP authorization
+ mOAuthAuthEndpointText.setVisibility(View.GONE);
+ mOAuthTokenEndpointText.setVisibility(View.GONE);
+ mUsernameInput.setVisibility(View.VISIBLE);
+ mPasswordInput.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+
+ /**
+ * Saves relevant state before {@link #onPause()}
+ *
+ * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag,
+ * intended to defer the processing of the redirection caught in
+ * {@link #onNewIntent(Intent)} until {@link #onResume()}
+ *
+ * See {@link super#onSaveInstanceState(Bundle)}
+ */
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ //Log_OC.e(TAG, "onSaveInstanceState init" );
+ super.onSaveInstanceState(outState);
+
+ /// global state
+ outState.putString(KEY_AUTH_TOKEN_TYPE, mAuthTokenType);
+ outState.putLong(KEY_WAITING_FOR_OP_ID, mWaitingForOpId);
+
+ if (!webViewLoginMethod) {
+ /// Server PRE-fragment state
+ outState.putString(KEY_SERVER_STATUS_TEXT, mServerStatusText);
+ outState.putInt(KEY_SERVER_STATUS_ICON, mServerStatusIcon);
+ outState.putBoolean(KEY_SERVER_CHECKED, mServerIsChecked);
+ outState.putBoolean(KEY_SERVER_VALID, mServerIsValid);
+
+ /// Authentication PRE-fragment state
+ outState.putBoolean(KEY_PASSWORD_EXPOSED, isPasswordVisible());
+ outState.putInt(KEY_AUTH_STATUS_ICON, mAuthStatusIcon);
+ outState.putString(KEY_AUTH_STATUS_TEXT, mAuthStatusText);
+ outState.putString(KEY_AUTH_TOKEN, mAuthToken);
+ }
+
+ outState.putBoolean(KEY_IS_SSL_CONN, mServerInfo.mIsSslConn);
+ outState.putString(KEY_HOST_URL_TEXT, mServerInfo.mBaseUrl);
+ if (mServerInfo.mVersion != null) {
+ outState.putString(KEY_OC_VERSION, mServerInfo.mVersion.getVersion());
+ }
+ outState.putString(KEY_SERVER_AUTH_METHOD, mServerInfo.mAuthMethod.name());
+
+ /// authentication
+ outState.putBoolean(KEY_AUTH_IS_FIRST_ATTEMPT_TAG, mIsFirstAuthAttempt);
+
+ /// AsyncTask (User and password)
+ if (!webViewLoginMethod) {
+ outState.putString(KEY_USERNAME, mUsernameInput.getText().toString().trim());
+ outState.putString(KEY_PASSWORD, mPasswordInput.getText().toString());
+ }
+
+ if (mAsyncTask != null) {
+ mAsyncTask.cancel(true);
+ outState.putBoolean(KEY_ASYNC_TASK_IN_PROGRESS, true);
+ } else {
+ outState.putBoolean(KEY_ASYNC_TASK_IN_PROGRESS, false);
+ }
+ mAsyncTask = null;
+
+ //Log_OC.e(TAG, "onSaveInstanceState end" );
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+
+ mServerIsChecked = savedInstanceState.getBoolean(KEY_SERVER_CHECKED, false);
+
+ // AsyncTask
+ boolean inProgress = savedInstanceState.getBoolean(KEY_ASYNC_TASK_IN_PROGRESS);
+ if (inProgress) {
+ String username = savedInstanceState.getString(KEY_USERNAME);
+ String password = savedInstanceState.getString(KEY_PASSWORD);
+
+ OwnCloudCredentials credentials = null;
+ if (basicTokenType.equals(mAuthTokenType)) {
+ credentials = OwnCloudCredentialsFactory.newBasicCredentials(username, password);
+
+ } else if (oauthTokenType.equals(mAuthTokenType)) {
+ credentials = OwnCloudCredentialsFactory.newBearerCredentials(mAuthToken);
+
+ }
+ accessRootFolder(credentials);
+ }
+ }
+
+ /**
+ * The redirection triggered by the OAuth authentication server as response to the
+ * GET AUTHORIZATION request is caught here.
+ *
+ * To make this possible, this activity needs to be qualified with android:launchMode =
+ * "singleTask" in the AndroidManifest.xml file.
+ */
+ @Override
+ protected void onNewIntent(Intent intent) {
+ Log_OC.d(TAG, "onNewIntent()");
+ Uri data = intent.getData();
+ if (data != null && data.toString().startsWith(getString(R.string.oauth2_redirect_uri))) {
+ mNewCapturedUriFromOAuth2Redirection = data;
+ }
+
+ if (intent.getBooleanExtra(EXTRA_USE_PROVIDER_AS_WEBLOGIN, false)) {
+ webViewLoginMethod = true;
+ setContentView(R.layout.account_setup_webview);
+ mLoginWebView = findViewById(R.id.login_webview);
+ initWebViewLogin(getString(R.string.provider_registration_server), false);
+ }
+ }
+
+
+ /**
+ * The redirection triggered by the OAuth authentication server as response to the
+ * GET AUTHORIZATION, and deferred in {@link #onNewIntent(Intent)}, is processed here.
+ */
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (!webViewLoginMethod) {
+ // bound here to avoid spurious changes triggered by Android on device rotations
+ mHostUrlInput.setOnFocusChangeListener(this);
+ mHostUrlInput.addTextChangedListener(mHostUrlInputWatcher);
+
+ if (mNewCapturedUriFromOAuth2Redirection != null) {
+ getOAuth2AccessTokenFromCapturedRedirection();
+ }
+
+ String dataString = getIntent().getDataString();
+ if (dataString != null) {
+ try {
+ populateLoginFields(dataString);
+ } catch (IllegalArgumentException e) {
+ DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.auth_illegal_login_used);
+ Log_OC.e(TAG, "Illegal login data URL used, no Login pre-fill!", e);
+ }
+ }
+ }
+
+ // bind to Operations Service
+ mOperationsServiceConnection = new OperationsServiceConnection();
+ if (!bindService(new Intent(this, OperationsService.class),
+ mOperationsServiceConnection,
+ Context.BIND_AUTO_CREATE)) {
+ DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.error_cant_bind_to_operations_service);
+ finish();
+ }
+
+ if (mOperationsServiceBinder != null) {
+ doOnResumeAndBound();
+ }
+ }
+
+
+ @Override
+ protected void onPause() {
+ if (mOperationsServiceBinder != null) {
+ mOperationsServiceBinder.removeOperationListener(this);
+ }
+
+ if (!webViewLoginMethod) {
+ mHostUrlInput.removeTextChangedListener(mHostUrlInputWatcher);
+ mHostUrlInput.setOnFocusChangeListener(null);
+ }
+
+ super.onPause();
+ }
+
+ @Override
+ protected void onDestroy() {
+
+ mHostUrlInputWatcher = null;
+
+ if (mOperationsServiceConnection != null) {
+ unbindService(mOperationsServiceConnection);
+ mOperationsServiceBinder = null;
+ }
+
+ if (webViewLoginMethod) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
+ }
+
+ super.onDestroy();
+ }
+
+ /**
+ * Parses the redirection with the response to the GET AUTHORIZATION request to the
+ * oAuth server and requests for the access token (GET ACCESS TOKEN)
+ */
+ private void getOAuth2AccessTokenFromCapturedRedirection() {
+ /// Parse data from OAuth redirection
+ String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery();
+ mNewCapturedUriFromOAuth2Redirection = null;
+
+ /// Showing the dialog with instructions for the user.
+ IndeterminateProgressDialog dialog =
+ IndeterminateProgressDialog.newInstance(R.string.auth_getting_authorization, true);
+ dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
+
+ /// GET ACCESS TOKEN to the oAuth server
+ Intent getServerInfoIntent = new Intent();
+ getServerInfoIntent.setAction(OperationsService.ACTION_OAUTH2_GET_ACCESS_TOKEN);
+
+ getServerInfoIntent.putExtra(
+ OperationsService.EXTRA_SERVER_URL,
+ mOAuthTokenEndpointText.getText().toString().trim());
+
+ getServerInfoIntent.putExtra(
+ OperationsService.EXTRA_OAUTH2_QUERY_PARAMETERS,
+ queryParameters);
+
+ if (mOperationsServiceBinder != null) {
+ //Log_OC.e(TAG, "getting access token..." );
+ mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);
+ }
+ }
+
+
+ /**
+ * Handles the change of focus on the text inputs for the server URL and the password
+ */
+ public void onFocusChange(View view, boolean hasFocus) {
+ if (view.getId() == R.id.hostUrlInput) {
+ if (!hasFocus) {
+ onUrlInputFocusLost();
+ } else {
+ showRefreshButton(false);
+ }
+
+ } else if (view.getId() == R.id.account_password) {
+ onPasswordFocusChanged(hasFocus);
+ }
+ }
+
+
+ /**
+ * Handles changes in focus on the text input for the server URL.
+ *
+ * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to
+ * other field. The operation to check the existence of the server in the entered URL is
+ * started.
+ *
+ * When hasFocus: user 'comes back' to write again the server URL.
+ */
+ private void onUrlInputFocusLost() {
+ if (!mServerInfo.mBaseUrl.equals(
+ AuthenticatorUrlUtils.normalizeUrl(mHostUrlInput.getText().toString(), mServerInfo.mIsSslConn))) {
+ // check server again only if the user changed something in the field
+ checkOcServer();
+ } else {
+ mOkButton.setEnabled(mServerIsValid);
+ showRefreshButton(!mServerIsValid);
+ }
+ }
+
+
+ private void checkOcServer() {
+ String uri;
+ if (mHostUrlInput != null && !mHostUrlInput.getText().toString().isEmpty()) {
+ uri = mHostUrlInput.getText().toString().trim();
+ mOkButton.setEnabled(false);
+ showRefreshButton(false);
+ } else {
+ uri = mServerInfo.mBaseUrl;
+ }
+
+ mServerIsValid = false;
+ mServerIsChecked = false;
+ mServerInfo = new GetServerInfoOperation.ServerInfo();
+
+ if (uri.length() != 0) {
+ if (mHostUrlInput != null) {
+ uri = AuthenticatorUrlUtils.stripIndexPhpOrAppsFiles(uri);
+ mHostUrlInput.setText(uri);
+ }
+
+ // Handle internationalized domain names
+ try {
+ uri = DisplayUtils.convertIdn(uri, true);
+ } catch (IllegalArgumentException ex) {
+ // Let Owncloud library check the error of the malformed URI
+ Log_OC.e(TAG, "Error converting internationalized domain name " + uri, ex);
+ }
+
+ if (mHostUrlInput != null) {
+ mServerStatusText = getResources().getString(R.string.auth_testing_connection);
+ mServerStatusIcon = R.drawable.progress_small;
+ showServerStatus();
+ }
+
+ Intent getServerInfoIntent = new Intent();
+ getServerInfoIntent.setAction(OperationsService.ACTION_GET_SERVER_INFO);
+ getServerInfoIntent.putExtra(OperationsService.EXTRA_SERVER_URL,
+ AuthenticatorUrlUtils.normalizeUrlSuffix(uri));
+
+ if (mOperationsServiceBinder != null) {
+ mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);
+ } else {
+ Log_OC.e(TAG, "Server check tried with OperationService unbound!");
+ }
+
+ } else {
+ mServerStatusText = "";
+ mServerStatusIcon = 0;
+ if (!webViewLoginMethod) {
+ showServerStatus();
+ }
+ }
+ }
+
+
+ /**
+ * Handles changes in focus on the text input for the password (basic authorization).
+ *
+ * When (hasFocus), the button to toggle password visibility is shown.
+ *
+ * When (!hasFocus), the button is made invisible and the password is hidden.
+ *
+ * @param hasFocus 'True' if focus is received, 'false' if is lost
+ */
+
+ private void onPasswordFocusChanged(boolean hasFocus) {
+ if (hasFocus) {
+ showViewPasswordButton();
+ } else {
+ hidePassword();
+ hidePasswordButton();
+ }
+ }
+
+
+ private void showViewPasswordButton() {
+ int drawable = R.drawable.ic_view;
+ if (isPasswordVisible()) {
+ drawable = R.drawable.ic_hide;
+ }
+ mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, drawable, 0);
+ }
+
+ private boolean isPasswordVisible() {
+ return ((mPasswordInput.getInputType() & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) ==
+ InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
+ }
+
+ private void hidePasswordButton() {
+ mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+ }
+
+ private void showPassword() {
+ mPasswordInput.setInputType(
+ InputType.TYPE_CLASS_TEXT |
+ InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD |
+ InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+ );
+ showViewPasswordButton();
+ }
+
+ private void hidePassword() {
+ mPasswordInput.setInputType(
+ InputType.TYPE_CLASS_TEXT |
+ InputType.TYPE_TEXT_VARIATION_PASSWORD |
+ InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+ );
+ showViewPasswordButton();
+ }
+
+ /**
+ * Checks the credentials of the user in the root of the ownCloud server
+ * before creating a new local account.
+ *
+ * For basic authorization, a check of existence of the root folder is
+ * performed.
+ *
+ * For OAuth, starts the flow to get an access token; the credentials test
+ * is postponed until it is available.
+ *
+ * IMPORTANT ENTRY POINT 4
+ */
+ public void onOkClick() {
+ // this check should be unnecessary
+ if (mServerInfo.mVersion == null ||
+ !mServerInfo.mVersion.isVersionValid() ||
+ mServerInfo.mBaseUrl == null ||
+ mServerInfo.mBaseUrl.length() == 0) {
+ mServerStatusIcon = R.drawable.ic_alert;
+ mServerStatusText = getResources().getString(R.string.auth_wtf_reenter_URL);
+ showServerStatus();
+ mOkButton.setEnabled(false);
+ return;
+ }
+
+ if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType(this)).equals(mAuthTokenType)) {
+ startOauthorization();
+ } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType(this))
+ .equals(mAuthTokenType)) {
+ startSamlBasedFederatedSingleSignOnAuthorization();
+ } else {
+ checkBasicAuthorization(null, null);
+ }
+ }
+
+
+ /**
+ * Tests the credentials entered by the user performing a check of existence on
+ * the root folder of the ownCloud server.
+ */
+ private void checkBasicAuthorization(@Nullable String webViewUsername, @Nullable String webViewPassword) {
+ /// get basic credentials entered by user
+ String username;
+ String password;
+ if (!webViewLoginMethod) {
+ username = mUsernameInput.getText().toString().trim();
+ password = mPasswordInput.getText().toString();
+ } else {
+ username = webViewUsername;
+ password = webViewPassword;
+ }
+
+ /// be gentle with the user
+ IndeterminateProgressDialog dialog =
+ IndeterminateProgressDialog.newInstance(R.string.auth_trying_to_login, true);
+ dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
+
+ /// validate credentials accessing the root folder
+ OwnCloudCredentials credentials = OwnCloudCredentialsFactory.newBasicCredentials(username, password);
+ accessRootFolder(credentials);
+ }
+
+ private void accessRootFolder(OwnCloudCredentials credentials) {
+ mAsyncTask = new AuthenticatorAsyncTask(this);
+ Object[] params = {mServerInfo.mBaseUrl, credentials};
+ mAsyncTask.execute(params);
+ }
+
+
+ /**
+ * Starts the OAuth 'grant type' flow to get an access token, with
+ * a GET AUTHORIZATION request to the BUILT-IN authorization server.
+ */
+ private void startOauthorization() {
+ // be gentle with the user
+ mAuthStatusIcon = R.drawable.progress_small;
+ mAuthStatusText = getResources().getString(R.string.oauth_login_connection);
+ showAuthStatus();
+
+ // GET AUTHORIZATION request
+ Uri uri = Uri.parse(mOAuthAuthEndpointText.getText().toString().trim());
+ Uri.Builder uriBuilder = uri.buildUpon();
+ uriBuilder.appendQueryParameter(
+ OAuth2Constants.KEY_RESPONSE_TYPE, getString(R.string.oauth2_response_type)
+ );
+ uriBuilder.appendQueryParameter(
+ OAuth2Constants.KEY_REDIRECT_URI, getString(R.string.oauth2_redirect_uri)
+ );
+ uriBuilder.appendQueryParameter(
+ OAuth2Constants.KEY_CLIENT_ID, getString(R.string.oauth2_client_id)
+ );
+ uriBuilder.appendQueryParameter(
+ OAuth2Constants.KEY_SCOPE, getString(R.string.oauth2_scope)
+ );
+ uri = uriBuilder.build();
+ Log_OC.d(TAG, "Starting browser to view " + uri.toString());
+ Intent i = new Intent(Intent.ACTION_VIEW, uri);
+ startActivity(i);
+ }
+
+
+ /**
+ * Starts the Web Single Sign On flow to get access to the root folder
+ * in the server.
+ */
+ private void startSamlBasedFederatedSingleSignOnAuthorization() {
+ /// be gentle with the user
+ mAuthStatusIcon = R.drawable.progress_small;
+ mAuthStatusText = getResources().getString(R.string.auth_connecting_auth_server);
+ showAuthStatus();
+
+ /// Show SAML-based SSO web dialog
+ String targetUrl = mServerInfo.mBaseUrl
+ + AuthenticatorUrlUtils.getWebdavPath(mServerInfo.mVersion, mAuthTokenType, this);
+ SamlWebViewDialog dialog = SamlWebViewDialog.newInstance(targetUrl, targetUrl);
+ dialog.show(getSupportFragmentManager(), SAML_DIALOG_TAG);
+ }
+
+ /**
+ * Callback method invoked when a RemoteOperation executed by this Activity finishes.
+ *
+ * Dispatches the operation flow to the right method.
+ */
+ @Override
+ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
+
+ if (operation instanceof GetServerInfoOperation) {
+ if (operation.hashCode() == mWaitingForOpId) {
+ onGetServerInfoFinish(result);
+ } // else nothing ; only the last check operation is considered;
+ // multiple can be started if the user amends a URL quickly
+
+ } else if (operation instanceof OAuth2GetAccessToken) {
+ onGetOAuthAccessTokenFinish(result);
+
+ } else if (operation instanceof GetRemoteUserInfoOperation) {
+ onGetUserNameFinish(result);
+ }
+
+ }
+
+ private void onGetUserNameFinish(RemoteOperationResult result) {
+ mWaitingForOpId = Long.MAX_VALUE;
+ if (result.isSuccess()) {
+ boolean success = false;
+ String username;
+ if (result.getData().get(0) instanceof UserInfo) {
+ username = ((UserInfo) result.getData().get(0)).getDisplayName();
+ } else {
+ username = (String) result.getData().get(0);
+ }
+
+ if (mAction == ACTION_CREATE) {
+ if (!webViewLoginMethod) {
+ mUsernameInput.setText(username);
+ }
+ success = createAccount(result);
+ } else {
+
+ if (!webViewLoginMethod && !mUsernameInput.getText().toString().trim().equals(username)) {
+ // fail - not a new account, but an existing one; disallow
+ result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_THE_SAME);
+ mAuthToken = "";
+ updateAuthStatusIconAndText(result);
+ showAuthStatus();
+ Log_OC.d(TAG, result.getLogMessage());
+ } else {
+ try {
+ updateAccountAuthentication();
+ success = true;
+
+ } catch (AccountNotFoundException e) {
+ Log_OC.e(TAG, "Account " + mAccount + " was removed!", e);
+ DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.auth_account_does_not_exist);
+ finish();
+ }
+ }
+ }
+
+ if (success) {
+ finish();
+ }
+ } else {
+ if (!webViewLoginMethod) {
+ int statusText = result.getCode() == ResultCode.MAINTENANCE_MODE ? R.string.maintenance_mode : R.string.auth_fail_get_user_name;
+ updateStatusIconFailUserName(statusText);
+ showAuthStatus();
+ }
+ Log_OC.e(TAG, "Access to user name failed: " + result.getLogMessage());
+ }
+
+ }
+
+ /**
+ * Processes the result of the server check performed when the user finishes the enter of the
+ * server URL.
+ *
+ * @param result Result of the check.
+ */
+ private void onGetServerInfoFinish(RemoteOperationResult result) {
+ /// update activity state
+ mServerIsChecked = true;
+ mWaitingForOpId = Long.MAX_VALUE;
+
+ // update server status, but don't show it yet
+ if (!webViewLoginMethod) {
+ updateServerStatusIconAndText(result);
+ }
+
+ if (result.isSuccess()) {
+ /// SUCCESS means:
+ // 1. connection succeeded, and we know if it's SSL or not
+ // 2. server is installed
+ // 3. we got the server version
+ // 4. we got the authentication method required by the server
+ mServerInfo = (GetServerInfoOperation.ServerInfo) (result.getData().get(0));
+
+ // show outdated warning
+ if (getResources().getBoolean(R.bool.show_outdated_server_warning) &&
+ mServerInfo.mVersion.compareTo(MainApp.OUTDATED_SERVER_VERSION) < 0) {
+ DisplayUtils.showServerOutdatedSnackbar(this);
+ }
+
+ webViewLoginMethod = mServerInfo.mVersion.isWebLoginSupported() && !forceOldLoginMethod;
+
+ if (webViewUser != null && !webViewUser.isEmpty() &&
+ webViewPassword != null && !webViewPassword.isEmpty()) {
+ checkBasicAuthorization(webViewUser, webViewPassword);
+ } else if (webViewLoginMethod) {
+ // hide old login
+ setOldLoginVisibility(View.GONE);
+
+ setContentView(R.layout.account_setup_webview);
+ mLoginWebView = findViewById(R.id.login_webview);
+ initWebViewLogin(mServerInfo.mBaseUrl + WEB_LOGIN, true);
+ } else {
+ // show old login
+ setOldLoginVisibility(View.VISIBLE);
+ }
+
+ if (!authSupported(mServerInfo.mAuthMethod)) {
+
+ if (!webViewLoginMethod) {
+ // overrides updateServerStatusIconAndText()
+ updateServerStatusIconNoRegularAuth();
+ }
+ mServerIsValid = false;
+
+ } else {
+ mServerIsValid = true;
+ }
+
+ } else {
+ mServerIsValid = false;
+ }
+
+ // refresh UI
+ if (!webViewLoginMethod) {
+ showRefreshButton(!mServerIsValid);
+ showServerStatus();
+ mOkButton.setEnabled(mServerIsValid);
+ }
+
+ if (!mServerIsValid) {
+ // hide old login
+ setOldLoginVisibility(View.GONE);
+ }
+
+ /// very special case (TODO: move to a common place for all the remote operations)
+ if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) {
+ showUntrustedCertDialog(result);
+ }
+ }
+
+ private void setOldLoginVisibility(int visible) {
+ mOkButton.setVisibility(visible);
+ mUsernameInputLayout.setVisibility(visible);
+ mPasswordInputLayout.setVisibility(visible);
+ }
+
+ private boolean authSupported(AuthenticationMethod authMethod) {
+ return ((basicTokenType.equals(mAuthTokenType) &&
+ AuthenticationMethod.BASIC_HTTP_AUTH.equals(authMethod)) ||
+ (oauthTokenType.equals(mAuthTokenType) &&
+ AuthenticationMethod.BEARER_TOKEN.equals(authMethod)) ||
+ (samlTokenType.equals(mAuthTokenType) &&
+ AuthenticationMethod.SAML_WEB_SSO.equals(authMethod))
+ );
+ }
+
+ /**
+ * Chooses the right icon and text to show to the user for the received operation result.
+ *
+ * @param result Result of a remote operation performed in this activity
+ */
+ private void updateServerStatusIconAndText(RemoteOperationResult result) {
+ mServerStatusIcon = R.drawable.ic_alert; // the most common case in the switch below
+
+ switch (result.getCode()) {
+ case OK_SSL:
+ mServerStatusIcon = R.drawable.ic_lock_white;
+ mServerStatusText = getResources().getString(R.string.auth_secure_connection);
+ break;
+
+ case OK_NO_SSL:
+ case OK:
+ if (mHostUrlInput.getText().toString().trim().toLowerCase(Locale.ROOT).startsWith(HTTP_PROTOCOL)) {
+ mServerStatusText = getResources().getString(R.string.auth_connection_established);
+ mServerStatusIcon = R.drawable.ic_ok;
+ } else {
+ mServerStatusText = getResources().getString(R.string.auth_nossl_plain_ok_title);
+ mServerStatusIcon = R.drawable.ic_lock_open_white;
+ }
+ break;
+
+ case NO_NETWORK_CONNECTION:
+ mServerStatusIcon = R.drawable.no_network;
+ mServerStatusText = getResources().getString(R.string.auth_no_net_conn_title);
+ break;
+
+ case SSL_RECOVERABLE_PEER_UNVERIFIED:
+ mServerStatusText = getResources().getString(R.string.auth_ssl_unverified_server_title);
+ break;
+ case BAD_OC_VERSION:
+ mServerStatusText = getResources().getString(R.string.auth_bad_oc_version_title);
+ break;
+ case WRONG_CONNECTION:
+ mServerStatusText = getResources().getString(R.string.auth_wrong_connection_title);
+ break;
+ case TIMEOUT:
+ mServerStatusText = getResources().getString(R.string.auth_timeout_title);
+ break;
+ case INCORRECT_ADDRESS:
+ mServerStatusText = getResources().getString(R.string.auth_incorrect_address_title);
+ break;
+ case SSL_ERROR:
+ mServerStatusText = getResources().getString(R.string.auth_ssl_general_error_title);
+ break;
+ case UNAUTHORIZED:
+ mServerStatusText = getResources().getString(R.string.auth_unauthorized);
+ break;
+ case HOST_NOT_AVAILABLE:
+ mServerStatusText = getResources().getString(R.string.auth_unknown_host_title);
+ break;
+ case INSTANCE_NOT_CONFIGURED:
+ mServerStatusText = getResources().getString(R.string.auth_not_configured_title);
+ break;
+ case FILE_NOT_FOUND:
+ mServerStatusText = getResources().getString(R.string.auth_incorrect_path_title);
+ break;
+ case OAUTH2_ERROR:
+ mServerStatusText = getResources().getString(R.string.auth_oauth_error);
+ break;
+ case OAUTH2_ERROR_ACCESS_DENIED:
+ mServerStatusText = getResources().getString(R.string.auth_oauth_error_access_denied);
+ break;
+ case UNHANDLED_HTTP_CODE:
+ mServerStatusText = getResources().getString(R.string.auth_unknown_error_http_title);
+ break;
+ case UNKNOWN_ERROR:
+ if (result.getException() != null &&
+ !TextUtils.isEmpty(result.getException().getMessage())) {
+ mServerStatusText = getResources().getString(
+ R.string.auth_unknown_error_exception_title,
+ result.getException().getMessage()
+ );
+ } else {
+ mServerStatusText = getResources().getString(R.string.auth_unknown_error_title);
+ }
+ break;
+ case OK_REDIRECT_TO_NON_SECURE_CONNECTION:
+ mServerStatusIcon = R.drawable.ic_lock_open_white;
+ mServerStatusText = getResources().getString(R.string.auth_redirect_non_secure_connection_title);
+ break;
+ case MAINTENANCE_MODE:
+ mServerStatusText = getResources().getString(R.string.maintenance_mode);
+ break;
+ case UNTRUSTED_DOMAIN:
+ mServerStatusText = getResources().getString(R.string.untrusted_domain);
+ break;
+ default:
+ mServerStatusText = "";
+ mServerStatusIcon = 0;
+ break;
+ }
+ }
+
+
+ /**
+ * Chooses the right icon and text to show to the user for the received operation result.
+ *
+ * @param result Result of a remote operation performed in this activity
+ */
+ private void updateAuthStatusIconAndText(RemoteOperationResult result) {
+ mAuthStatusIcon = R.drawable.ic_alert; // the most common case in the switch below
+
+ switch (result.getCode()) {
+ case OK_SSL:
+ mAuthStatusIcon = R.drawable.ic_lock_white;
+ mAuthStatusText = getResources().getString(R.string.auth_secure_connection);
+ break;
+
+ case OK_NO_SSL:
+ case OK:
+ if (mHostUrlInput.getText().toString().trim().toLowerCase(Locale.ROOT).startsWith(HTTP_PROTOCOL)) {
+ mAuthStatusText = getResources().getString(R.string.auth_connection_established);
+ mAuthStatusIcon = R.drawable.ic_ok;
+ } else {
+ mAuthStatusText = getResources().getString(R.string.auth_nossl_plain_ok_title);
+ mAuthStatusIcon = R.drawable.ic_lock_open_white;
+ }
+ break;
+
+ case NO_NETWORK_CONNECTION:
+ mAuthStatusIcon = R.drawable.no_network;
+ mAuthStatusText = getResources().getString(R.string.auth_no_net_conn_title);
+ break;
+
+ case SSL_RECOVERABLE_PEER_UNVERIFIED:
+ mAuthStatusText = getResources().getString(R.string.auth_ssl_unverified_server_title);
+ break;
+ case TIMEOUT:
+ mAuthStatusText = getResources().getString(R.string.auth_timeout_title);
+ break;
+ case HOST_NOT_AVAILABLE:
+ mAuthStatusText = getResources().getString(R.string.auth_unknown_host_title);
+ break;
+ case UNHANDLED_HTTP_CODE:
+ default:
+ mAuthStatusText = ErrorMessageAdapter.getErrorCauseMessage(result, null, getResources());
+ }
+ }
+
+ private void updateStatusIconFailUserName(int failedStatusText) {
+ mAuthStatusIcon = R.drawable.ic_alert;
+ mAuthStatusText = getResources().getString(failedStatusText);
+ }
+
+ private void updateServerStatusIconNoRegularAuth() {
+ mServerStatusIcon = R.drawable.ic_alert;
+ mServerStatusText = getResources().getString(R.string.auth_can_not_auth_against_server);
+ }
+
+ /**
+ * Processes the result of the request for and access token send
+ * to an OAuth authorization server.
+ *
+ * @param result Result of the operation.
+ */
+ private void onGetOAuthAccessTokenFinish(RemoteOperationResult result) {
+ mWaitingForOpId = Long.MAX_VALUE;
+ dismissDialog(WAIT_DIALOG_TAG);
+
+ if (result.isSuccess()) {
+ /// be gentle with the user
+ IndeterminateProgressDialog dialog =
+ IndeterminateProgressDialog.newInstance(R.string.auth_trying_to_login, true);
+ dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
+
+ /// time to test the retrieved access token on the ownCloud server
+ @SuppressWarnings("unchecked")
+ Map tokens = (Map) (result.getData().get(0));
+ mAuthToken = tokens.get(OAuth2Constants.KEY_ACCESS_TOKEN);
+ Log_OC.d(TAG, "Got ACCESS TOKEN: " + mAuthToken);
+
+ /// validate token accessing to root folder / getting session
+ OwnCloudCredentials credentials = OwnCloudCredentialsFactory.newBearerCredentials(
+ mAuthToken);
+ accessRootFolder(credentials);
+
+ } else {
+ updateAuthStatusIconAndText(result);
+ showAuthStatus();
+ Log_OC.d(TAG, "Access failed: " + result.getLogMessage());
+ }
+ }
+
+
+ /**
+ * Processes the result of the access check performed to try the user credentials.
+ *
+ * Creates a new account through the AccountManager.
+ *
+ * @param result Result of the operation.
+ */
+ @Override
+ public void onAuthenticatorTaskCallback(RemoteOperationResult result) {
+ mWaitingForOpId = Long.MAX_VALUE;
+ dismissDialog(WAIT_DIALOG_TAG);
+ mAsyncTask = null;
+
+ if (result.isSuccess()) {
+ Log_OC.d(TAG, "Successful access - time to save the account");
+
+ boolean success = false;
+
+ if (mAction == ACTION_CREATE) {
+ success = createAccount(result);
+
+ } else {
+ try {
+ updateAccountAuthentication();
+ success = true;
+
+ } catch (AccountNotFoundException e) {
+ Log_OC.e(TAG, "Account " + mAccount + " was removed!", e);
+ DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.auth_account_does_not_exist);
+ finish();
+ }
+ }
+
+ // Reset webView
+ webViewPassword = null;
+ webViewUser = null;
+ forceOldLoginMethod = false;
+ deleteCookies();
+
+ if (success) {
+ finish();
+ } else {
+ // init webView again
+ if (mLoginWebView != null) {
+ mLoginWebView.setVisibility(View.GONE);
+ }
+ setContentView(R.layout.account_setup);
+
+ initOverallUi();
+
+ CustomEditText serverAddressField = findViewById(R.id.hostUrlInput);
+ serverAddressField.setText(mServerInfo.mBaseUrl);
+
+ findViewById(R.id.oauth_onOff_check).setVisibility(View.GONE);
+ findViewById(R.id.server_status_text).setVisibility(View.GONE);
+ mAuthStatusView = findViewById(R.id.auth_status_text);
+
+ showAuthStatus();
+ }
+
+ } else if (result.isServerFail() || result.isException()) {
+ /// server errors or exceptions in authorization take to requiring a new check of
+ /// the server
+ mServerIsChecked = true;
+ mServerIsValid = false;
+ mServerInfo = new GetServerInfoOperation.ServerInfo();
+
+ // update status icon and text
+ updateServerStatusIconAndText(result);
+ showServerStatus();
+ mAuthStatusIcon = 0;
+ mAuthStatusText = "";
+ if (!webViewLoginMethod) {
+ showAuthStatus();
+
+ // update input controls state
+ showRefreshButton(true);
+ mOkButton.setEnabled(false);
+ }
+
+ // very special case (TODO: move to a common place for all the remote operations)
+ if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) {
+ showUntrustedCertDialog(result);
+ }
+
+ } else { // authorization fail due to client side - probably wrong credentials
+ if (webViewLoginMethod) {
+ mLoginWebView = findViewById(R.id.login_webview);
+ initWebViewLogin(mServerInfo.mBaseUrl + WEB_LOGIN, true);
+
+ DisplayUtils.showSnackMessage(this, mLoginWebView, R.string.auth_access_failed, result.getLogMessage());
+ } else {
+ updateAuthStatusIconAndText(result);
+ showAuthStatus();
+ }
+ // reset webview
+ webViewPassword = null;
+ webViewUser = null;
+ deleteCookies();
+
+ Log_OC.d(TAG, "Access failed: " + result.getLogMessage());
+ }
+ }
+
+
+ /**
+ * Updates the authentication token.
+ *
+ * Sets the proper response so that the AccountAuthenticator that started this activity
+ * saves a new authorization token for mAccount.
+ *
+ * Kills the session kept by OwnCloudClientManager so that a new one will created with
+ * the new credentials when needed.
+ */
+ private void updateAccountAuthentication() throws AccountNotFoundException {
+ String accountType = MainApp.getAccountType(this);
+
+ Bundle response = new Bundle();
+ response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
+ response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type);
+
+ if (AccountTypeUtils.getAuthTokenTypeAccessToken(accountType).equals(mAuthTokenType)) {
+ response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken);
+ // the next line is necessary, notifications are calling directly to the
+ // AuthenticatorActivity to update, without AccountManager intervention
+ mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken);
+
+ } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(accountType).equals(mAuthTokenType)) {
+ response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken);
+ // the next line is necessary; by now, notifications are calling directly to the
+ // AuthenticatorActivity to update, without AccountManager intervention
+ mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken);
+
+ } else {
+ if (!webViewLoginMethod) {
+ response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString());
+ mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString());
+ } else {
+ response.putString(AccountManager.KEY_AUTHTOKEN, webViewPassword);
+ mAccountMgr.setPassword(mAccount, webViewPassword);
+ }
+ }
+
+ // remove managed clients for this account to enforce creation with fresh credentials
+ OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, this);
+ OwnCloudClientManagerFactory.getDefaultSingleton().removeClientFor(ocAccount);
+
+ setAccountAuthenticatorResult(response);
+ final Intent intent = new Intent();
+ intent.putExtras(response);
+ setResult(RESULT_OK, intent);
+
+ }
+
+
+ /**
+ * Creates a new account through the Account Authenticator that started this activity.
+ *
+ * This makes the account permanent.
+ *
+ * TODO Decide how to name the OAuth accounts
+ */
+ @SuppressFBWarnings("DMI")
+ private boolean createAccount(RemoteOperationResult authResult) {
+ String accountType = MainApp.getAccountType(this);
+
+ // create and save new ownCloud account
+ boolean isOAuth = AccountTypeUtils.getAuthTokenTypeAccessToken(accountType).equals(mAuthTokenType);
+ boolean isSaml = AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(accountType).equals(mAuthTokenType);
+
+ String lastPermanentLocation = authResult.getLastPermanentLocation();
+ if (lastPermanentLocation != null) {
+ mServerInfo.mBaseUrl = AuthenticatorUrlUtils.trimWebdavSuffix(lastPermanentLocation);
+ }
+
+ Uri uri = Uri.parse(mServerInfo.mBaseUrl);
+ String username;
+ if (!webViewLoginMethod) {
+ username = mUsernameInput.getText().toString().trim();
+ } else {
+ username = webViewUser;
+ }
+ if (isOAuth) {
+ username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong();
+ }
+
+ String accountName = com.owncloud.android.lib.common.accounts.AccountUtils.buildAccountName(uri, username);
+ Account newAccount = new Account(accountName, accountType);
+ if (AccountUtils.exists(newAccount, getApplicationContext())) {
+ // fail - not a new account, but an existing one; disallow
+ RemoteOperationResult result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_NEW);
+
+ updateAuthStatusIconAndText(result);
+ showAuthStatus();
+
+ Log_OC.d(TAG, result.getLogMessage());
+ return false;
+
+ } else {
+ mAccount = newAccount;
+
+ if (isOAuth || isSaml) {
+ // with external authorizations, the password is never input in the app
+ mAccountMgr.addAccountExplicitly(mAccount, "", null);
+ } else {
+ if (!webViewLoginMethod) {
+ mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null);
+ } else {
+ mAccountMgr.addAccountExplicitly(mAccount, webViewPassword, null);
+ }
+ }
+
+ // include account version with the new account
+ mAccountMgr.setUserData(mAccount, Constants.KEY_OC_ACCOUNT_VERSION,
+ Integer.toString(AccountUtils.ACCOUNT_VERSION));
+
+ /// add the new account as default in preferences, if there is none already
+ Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this);
+ if (defaultAccount == null) {
+ SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
+ editor.putString("select_oc_account", accountName);
+ editor.apply();
+ }
+
+ /// prepare result to return to the Authenticator
+ // TODO check again what the Authenticator makes with it; probably has the same
+ // effect as addAccountExplicitly, but it's not well done
+ final Intent intent = new Intent();
+ intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType);
+ intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
+ intent.putExtra(AccountManager.KEY_USERDATA, username);
+ if (isOAuth || isSaml) {
+ mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken);
+ }
+ /// add user data to the new account; TODO probably can be done in the last parameter
+ // addAccountExplicitly, or in KEY_USERDATA
+ mAccountMgr.setUserData(mAccount, Constants.KEY_OC_VERSION, mServerInfo.mVersion.getVersion());
+ mAccountMgr.setUserData(mAccount, Constants.KEY_OC_BASE_URL, mServerInfo.mBaseUrl);
+
+ if (authResult.getData() != null) {
+ try {
+ UserInfo userInfo = (UserInfo) authResult.getData().get(0);
+ mAccountMgr.setUserData(mAccount, Constants.KEY_DISPLAY_NAME, userInfo.getDisplayName());
+ mAccountMgr.setUserData(mAccount, Constants.KEY_USER_ID, userInfo.getId());
+ } catch (ClassCastException c) {
+ Log_OC.w(TAG, "Couldn't get display name for " + username);
+ }
+ } else {
+ Log_OC.w(TAG, "Couldn't get display name for " + username);
+ }
+
+ if (isSaml) {
+ mAccountMgr.setUserData(mAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO, "TRUE");
+ } else if (isOAuth) {
+ mAccountMgr.setUserData(mAccount, Constants.KEY_SUPPORTS_OAUTH2, "TRUE");
+ }
+
+ setAccountAuthenticatorResult(intent.getExtras());
+ setResult(RESULT_OK, intent);
+
+ return true;
+ }
+ }
+
+ /**
+ * Updates the content and visibility state of the icon and text associated
+ * to the last check on the ownCloud server.
+ */
+ private void showServerStatus() {
+ if (mServerStatusIcon == 0 && "".equals(mServerStatusText)) {
+ mServerStatusView.setVisibility(View.INVISIBLE);
+ } else {
+ mServerStatusView.setText(mServerStatusText);
+ mServerStatusView.setCompoundDrawablesWithIntrinsicBounds(mServerStatusIcon, 0, 0, 0);
+ mServerStatusView.setVisibility(View.VISIBLE);
+ }
+ }
+
+
+ /**
+ * Updates the content and visibility state of the icon and text associated
+ * to the interactions with the OAuth authorization server.
+ */
+ private void showAuthStatus() {
+ if (mAuthStatusIcon == 0 && "".equals(mAuthStatusText)) {
+ mAuthStatusView.setVisibility(View.INVISIBLE);
+ } else {
+ mAuthStatusView.setText(mAuthStatusText);
+ mAuthStatusView.setCompoundDrawablesWithIntrinsicBounds(mAuthStatusIcon, 0, 0, 0);
+ mAuthStatusView.setVisibility(View.VISIBLE);
+ }
+ }
+
+
+ private void showRefreshButton(boolean show) {
+ if (webViewLoginMethod && mRefreshButton != null) {
+ if (show) {
+ mRefreshButton.setVisibility(View.VISIBLE);
+ } else {
+ mRefreshButton.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ /**
+ * Called when the eye icon in the password field is clicked.
+ *
+ * Toggles the visibility of the password in the field.
+ */
+ public void onViewPasswordClick() {
+ int selectionStart = mPasswordInput.getSelectionStart();
+ int selectionEnd = mPasswordInput.getSelectionEnd();
+ if (isPasswordVisible()) {
+ hidePassword();
+ } else {
+ showPassword();
+ }
+ mPasswordInput.setSelection(selectionStart, selectionEnd);
+ }
+
+
+ /**
+ * Called when the checkbox for OAuth authorization is clicked.
+ *
+ * Hides or shows the input fields for user & password.
+ *
+ * @param view 'View password' 'button'
+ */
+ public void onCheckClick(View view) {
+ CheckBox oAuth2Check = (CheckBox) view;
+ if (oAuth2Check.isChecked()) {
+ mAuthTokenType = oauthTokenType;
+ } else {
+ mAuthTokenType = basicTokenType;
+ }
+ updateAuthenticationPreFragmentVisibility();
+ }
+
+
+ /**
+ * Called when the 'action' button in an IME is pressed ('enter' in software keyboard).
+ *
+ * Used to trigger the authentication check when the user presses 'enter' after writing the
+ * password, or to throw the server test when the only field on screen is the URL input field.
+ */
+ @Override
+ public boolean onEditorAction(TextView inputField, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_DONE && inputField != null &&
+ inputField.equals(mPasswordInput)) {
+ if (mOkButton.isEnabled()) {
+ mOkButton.performClick();
+ }
+
+ } else if ((actionId == EditorInfo.IME_ACTION_NEXT || actionId == EditorInfo.IME_NULL)
+ && inputField != null && inputField.equals(mHostUrlInput)) {
+ checkOcServer();
+ }
+ return false; // always return false to grant that the software keyboard is hidden anyway
+ }
+
+
+ private abstract static class RightDrawableOnTouchListener implements OnTouchListener {
+
+ private int fuzz = 75;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ Drawable rightDrawable = null;
+ if (view instanceof TextView) {
+ Drawable[] drawables = ((TextView) view).getCompoundDrawables();
+ if (drawables.length > 2) {
+ rightDrawable = drawables[2];
+ }
+ }
+ if (rightDrawable != null) {
+ final int x = (int) event.getX();
+ final int y = (int) event.getY();
+ final Rect bounds = rightDrawable.getBounds();
+ if (x >= (view.getRight() - bounds.width() - fuzz) &&
+ x <= (view.getRight() - view.getPaddingRight() + fuzz) &&
+ y >= (view.getPaddingTop() - fuzz) &&
+ y <= (view.getHeight() - view.getPaddingBottom()) + fuzz) {
+
+ return onDrawableTouch(event);
+ }
+ }
+ return false;
+ }
+
+ public abstract boolean onDrawableTouch(final MotionEvent event);
+ }
+
+
+ private void getRemoteUserNameOperation(String sessionCookie) {
+
+ Intent getUserNameIntent = new Intent();
+ getUserNameIntent.setAction(OperationsService.ACTION_GET_USER_NAME);
+ getUserNameIntent.putExtra(OperationsService.EXTRA_SERVER_URL, mServerInfo.mBaseUrl);
+ getUserNameIntent.putExtra(OperationsService.EXTRA_COOKIE, sessionCookie);
+
+ if (mOperationsServiceBinder != null) {
+ mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getUserNameIntent);
+ }
+ }
+
+
+ @Override
+ public void onSsoFinished(String sessionCookie) {
+ if (sessionCookie != null && sessionCookie.length() > 0) {
+ Log_OC.d(TAG, "Successful SSO - time to save the account");
+ mAuthToken = sessionCookie;
+ getRemoteUserNameOperation(sessionCookie);
+ Fragment fd = getSupportFragmentManager().findFragmentByTag(SAML_DIALOG_TAG);
+ if (fd instanceof DialogFragment) {
+ Dialog d = ((DialogFragment) fd).getDialog();
+ if (d != null && d.isShowing()) {
+ d.dismiss();
+ }
+ }
+
+ } else {
+ // TODO - show fail
+ Log_OC.d(TAG, "SSO failed");
+ }
+
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType(this)).equals(mAuthTokenType) &&
+ mHostUrlInput.hasFocus() && event.getAction() == MotionEvent.ACTION_DOWN) {
+ checkOcServer();
+ }
+ return super.onTouchEvent(event);
+ }
+
+
+ /**
+ * Show untrusted cert dialog
+ */
+ public void showUntrustedCertDialog(X509Certificate x509Certificate, SslError error, SslErrorHandler handler) {
+ // Show a dialog with the certificate info
+ SslUntrustedCertDialog dialog;
+ if (x509Certificate == null) {
+ dialog = SslUntrustedCertDialog.newInstanceForEmptySslError(error, handler);
+ } else {
+ dialog = SslUntrustedCertDialog.newInstanceForFullSslError(x509Certificate, error, handler);
+ }
+ FragmentManager fm = getSupportFragmentManager();
+ FragmentTransaction ft = fm.beginTransaction();
+ ft.addToBackStack(null);
+ dialog.show(ft, UNTRUSTED_CERT_DIALOG_TAG);
+ }
+
+
+ /**
+ * Show untrusted cert dialog
+ */
+ private void showUntrustedCertDialog(RemoteOperationResult result) {
+ // Show a dialog with the certificate info
+ SslUntrustedCertDialog dialog = SslUntrustedCertDialog.
+ newInstanceForFullSslError((CertificateCombinedException) result.getException());
+ FragmentManager fm = getSupportFragmentManager();
+ FragmentTransaction ft = fm.beginTransaction();
+ ft.addToBackStack(null);
+ dialog.show(ft, UNTRUSTED_CERT_DIALOG_TAG);
+
+ }
+
+ /**
+ * Called from SslValidatorDialog when a new server certificate was correctly saved.
+ */
+ public void onSavedCertificate() {
+ Fragment fd = getSupportFragmentManager().findFragmentByTag(SAML_DIALOG_TAG);
+ if (fd == null) {
+ // if SAML dialog is not shown,
+ // the SslDialog was shown due to an SSL error in the server check
+ checkOcServer();
+ }
+ }
+
+ /**
+ * Called from SslValidatorDialog when a new server certificate could not be saved
+ * when the user requested it.
+ */
+ @Override
+ public void onFailedSavingCertificate() {
+ dismissDialog(SAML_DIALOG_TAG);
+ DisplayUtils.showSnackMessage(this, R.string.ssl_validator_not_saved);
+ }
+
+ @Override
+ public void onCancelCertificate() {
+ dismissDialog(SAML_DIALOG_TAG);
+ }
+
+
+ private void doOnResumeAndBound() {
+ //Log_OC.e(TAG, "registering to listen for operation callbacks" );
+ mOperationsServiceBinder.addOperationListener(AuthenticatorActivity.this, mHandler);
+ if (mWaitingForOpId <= Integer.MAX_VALUE) {
+ mOperationsServiceBinder.dispatchResultIfFinished((int) mWaitingForOpId, this);
+ }
+
+ if (!webViewLoginMethod && mHostUrlInput.getText() != null && mHostUrlInput.getText().length() > 0
+ && !mServerIsChecked) {
+ checkOcServer();
+ }
+ }
+
+
+ private void dismissDialog(String dialogTag) {
+ Fragment frag = getSupportFragmentManager().findFragmentByTag(dialogTag);
+ if (frag instanceof DialogFragment) {
+ DialogFragment dialog = (DialogFragment) frag;
+
+ try {
+ dialog.dismiss();
+ } catch (IllegalStateException e) {
+ Log_OC.e(TAG, e.getMessage());
+ dialog.dismissAllowingStateLoss();
+ }
+ }
+ }
+
+
+ /**
+ * Implements callback methods for service binding.
+ */
+ private class OperationsServiceConnection implements ServiceConnection {
+
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder service) {
+ if (component.equals(
+ new ComponentName(AuthenticatorActivity.this, OperationsService.class)
+ )) {
+ mOperationsServiceBinder = (OperationsServiceBinder) service;
+
+ doOnResumeAndBound();
+
+ }
+
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ if (component.equals(
+ new ComponentName(AuthenticatorActivity.this, OperationsService.class)
+ )) {
+ Log_OC.e(TAG, "Operations service crashed");
+ mOperationsServiceBinder = null;
+ }
+ }
+ }
+
+ /**
+ * Create and show dialog for request authentication to the user
+ *
+ * @param webView Web view to embed into the authentication dialog.
+ * @param handler Object responsible for catching and recovering HTTP authentication fails.
+ */
+ public void createAuthenticationDialog(WebView webView, HttpAuthHandler handler) {
+
+ // Show a dialog with the certificate info
+ CredentialsDialogFragment dialog = CredentialsDialogFragment.newInstanceForCredentials(webView, handler);
+ FragmentManager fm = getSupportFragmentManager();
+ FragmentTransaction ft = fm.beginTransaction();
+ ft.addToBackStack(null);
+ dialog.setCancelable(false);
+ dialog.show(ft, CREDENTIALS_DIALOG_TAG);
+
+ if (!mIsFirstAuthAttempt) {
+ DisplayUtils.showSnackMessage(this, R.string.saml_authentication_wrong_pass);
+ } else {
+ mIsFirstAuthAttempt = false;
+ }
+ }
+
+ /**
+ * For retrieving the clicking on authentication cancel button.
+ */
+ public void doNegativeAuthenticationDialogClick() {
+ mIsFirstAuthAttempt = true;
+ }
+}
diff --git a/src/main/java/com/owncloud/android/features/FeatureItem.java b/src/main/java/com/owncloud/android/features/FeatureItem.java
new file mode 100644
index 0000000000..e22ce9ca08
--- /dev/null
+++ b/src/main/java/com/owncloud/android/features/FeatureItem.java
@@ -0,0 +1,115 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Bartosz Przybylski
+ * Copyright (C) 2015 Bartosz Przybylski
+ * Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2016 Nextcloud.
+ *
+ * This program 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 program 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 program. If not, see .
+ */
+
+package com.owncloud.android.features;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.owncloud.android.R;
+
+/**
+ * @author Bartosz Przybylski
+ * @author Tobias Kaminsky
+ */
+public class FeatureItem implements Parcelable {
+ private static final int DO_NOT_SHOW = -1;
+ private int image;
+ private int titleText;
+ private int contentText;
+ private boolean contentCentered;
+ private boolean bulletList;
+
+ public FeatureItem(int image, int titleText, int contentText, boolean contentCentered, boolean bulletList) {
+ this.image = image;
+ this.titleText = titleText;
+ this.contentText = contentText;
+ this.contentCentered = contentCentered;
+ this.bulletList = bulletList;
+ }
+
+ public boolean shouldShowImage() {
+ return image != DO_NOT_SHOW;
+ }
+
+ public int getImage() {
+ return image;
+ }
+
+ public boolean shouldShowTitleText() {
+ return titleText != DO_NOT_SHOW && titleText != R.string.empty;
+ }
+
+ public int getTitleText() {
+ return titleText;
+ }
+
+ public boolean shouldShowContentText() {
+ return contentText != DO_NOT_SHOW && contentText != R.string.empty;
+ }
+
+ public int getContentText() {
+ return contentText;
+ }
+
+ public boolean shouldContentCentered() {
+ return contentCentered;
+ }
+
+ public boolean shouldShowBulletPointList() {
+ return bulletList;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(image);
+ dest.writeInt(titleText);
+ dest.writeInt(contentText);
+ dest.writeByte((byte) (contentCentered ? 1 : 0));
+ dest.writeByte((byte) (bulletList ? 1 : 0));
+ }
+
+ private FeatureItem(Parcel p) {
+ image = p.readInt();
+ titleText = p.readInt();
+ contentText = p.readInt();
+ contentCentered = p.readByte() == 1;
+ bulletList = p.readByte() == 1;
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ @Override
+ public Object createFromParcel(Parcel source) {
+ return new FeatureItem(source);
+ }
+
+ @Override
+ public Object[] newArray(int size) {
+ return new FeatureItem[size];
+ }
+ };
+}
diff --git a/src/main/java/com/owncloud/android/features/FeatureList.java b/src/main/java/com/owncloud/android/features/FeatureList.java
deleted file mode 100644
index 8c8ac7344b..0000000000
--- a/src/main/java/com/owncloud/android/features/FeatureList.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Bartosz Przybylski
- * Copyright (C) 2015 Bartosz Przybylski
- * Copyright (C) 2015 ownCloud Inc.
- * Copyright (C) 2016 Nextcloud.
- *
- * This program 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 program 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 program. If not, see .
- */
-
-package com.owncloud.android.features;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.owncloud.android.MainApp;
-import com.owncloud.android.R;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * @author Bartosz Przybylski
- */
-public class FeatureList {
- private static final boolean SHOW_ON_FIRST_RUN = true;
- private static final boolean SHOW_ON_UPGRADE = false;
-
- private static final int VERSION_1_0_0 = 10000099;
- private static final int VERSION_3_0_0 = 30000099;
- private static final int VERSION_3_3_0 = 30030099;
- private static final int BETA_VERSION_0 = 0;
-
- static public List get(boolean isMultiAccount) {
- List featuresList = new ArrayList<>();
- // Basic features showed on first install
- featuresList.add(new FeatureItem(R.drawable.whats_new_files,
- R.string.welcome_feature_1_title, R.string.welcome_feature_1_text,
- VERSION_1_0_0, BETA_VERSION_0, SHOW_ON_FIRST_RUN, true, false));
- if (isMultiAccount) {
- featuresList.add(new FeatureItem(R.drawable.whats_new_accounts,
- R.string.welcome_feature_2_title, R.string.welcome_feature_2_text,
- VERSION_1_0_0, BETA_VERSION_0, SHOW_ON_FIRST_RUN, true, false));
- }
- featuresList.add(new FeatureItem(R.drawable.whats_new_auto_upload,
- R.string.welcome_feature_3_title, R.string.welcome_feature_3_text,
- VERSION_1_0_0, BETA_VERSION_0, SHOW_ON_FIRST_RUN, true, false));
-
- // 3.0.0
- featuresList.add(new FeatureItem(R.drawable.whats_new_end_to_end_encryption,
- R.string.whats_new_end_to_end_encryption_title, R.string.whats_new_end_to_end_encryption_content,
- VERSION_3_0_0, BETA_VERSION_0, SHOW_ON_UPGRADE, false, false));
- featuresList.add(new FeatureItem(R.drawable.whats_new_resized_images, R.string.whats_new_resized_images_title,
- R.string.whats_new_resized_images_content, VERSION_3_0_0, BETA_VERSION_0, SHOW_ON_UPGRADE,
- false, false));
- featuresList.add(new FeatureItem(R.drawable.whats_new_ipv6, R.string.whats_new_ipv6_title,
- R.string.whats_new_ipv6_content, VERSION_3_0_0,
- BETA_VERSION_0, SHOW_ON_UPGRADE, false, false));
-
- // 3.3.0
- featuresList.add(new FeatureItem(R.drawable.whats_new_device_credentials,
- R.string.whats_new_device_credentials_title, R.string.whats_new_device_credentials_content,
- VERSION_3_3_0, BETA_VERSION_0, SHOW_ON_UPGRADE, false, false));
-
- return featuresList;
- }
-
- static public FeatureItem[] getFiltered(int lastSeenVersionCode, boolean isFirstRun, boolean isBeta,
- boolean isMultiAccount) {
- List features = new LinkedList<>();
-
- for (FeatureItem item : get(isMultiAccount)) {
- final int itemVersionCode = isBeta ? item.getBetaVersionNumber() : item.getVersionCode();
- if (isFirstRun && item.shouldShowOnFirstRun()) {
- features.add(item);
- } else if (!isFirstRun && !item.shouldShowOnFirstRun() &&
- MainApp.getVersionCode() >= itemVersionCode &&
- lastSeenVersionCode < itemVersionCode) {
- features.add(item);
- }
- }
- return features.toArray(new FeatureItem[features.size()]);
- }
-
- static public class FeatureItem implements Parcelable {
- public static final int DO_NOT_SHOW = -1;
- private int image;
- private int titleText;
- private int contentText;
- private int versionCode;
- private int betaVersion;
- private boolean showOnInitialRun;
- private boolean contentCentered;
- private boolean bulletList;
-
- public FeatureItem(int image, int titleText, int contentText, int version, int betaVersion) {
- this(image, titleText, contentText, version, betaVersion, false, true, true);
- }
-
- public FeatureItem(int image, int titleText, int contentText, int version, int betaVersion,
- boolean showOnInitialRun) {
- this(image, titleText, contentText, version, betaVersion, showOnInitialRun, true, true);
- }
-
- public FeatureItem(int image, int titleText, int contentText, int versionCode, int betaVersion,
- boolean showOnInitialRun, boolean contentCentered, boolean bulletList) {
- this.image = image;
- this.titleText = titleText;
- this.contentText = contentText;
- this.versionCode = versionCode;
- this.betaVersion = betaVersion;
- this.showOnInitialRun = showOnInitialRun;
- this.contentCentered = contentCentered;
- this.bulletList = bulletList;
- }
-
- public boolean shouldShowImage() { return image != DO_NOT_SHOW; }
- public int getImage() { return image; }
-
- public boolean shouldShowTitleText() { return titleText != DO_NOT_SHOW; }
- public int getTitleText() { return titleText; }
-
- public boolean shouldShowContentText() { return contentText != DO_NOT_SHOW; }
- public int getContentText() { return contentText; }
-
- public int getVersionCode() {
- return versionCode;
- }
- public int getBetaVersionNumber() { return betaVersion; }
- public boolean shouldShowOnFirstRun() { return showOnInitialRun; }
-
- public boolean shouldContentCentered() {
- return contentCentered;
- }
-
- public boolean shouldShowBulletPointList() {
- return bulletList;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(image);
- dest.writeInt(titleText);
- dest.writeInt(contentText);
- dest.writeInt(versionCode);
- dest.writeInt(betaVersion);
- dest.writeByte((byte) (showOnInitialRun ? 1 : 0));
- dest.writeByte((byte) (contentCentered ? 1 : 0));
- dest.writeByte((byte) (bulletList ? 1 : 0));
- }
-
- private FeatureItem(Parcel p) {
- image = p.readInt();
- titleText = p.readInt();
- contentText = p.readInt();
- versionCode = p.readInt();
- betaVersion = p.readInt();
- showOnInitialRun = p.readByte() == 1;
- contentCentered = p.readByte() == 1;
- bulletList = p.readByte() == 1;
- }
- public static final Parcelable.Creator CREATOR =
- new Parcelable.Creator() {
-
- @Override
- public Object createFromParcel(Parcel source) {
- return new FeatureItem(source);
- }
-
- @Override
- public Object[] newArray(int size) {
- return new FeatureItem[size];
- }
- };
- }
-}
diff --git a/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java b/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java
index f772d139d9..6375ca6f22 100644
--- a/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java
@@ -105,7 +105,7 @@ public abstract class BaseActivity extends AppCompatActivity {
/**
* Tries to swap the current ownCloud {@link Account} for other valid and existing.
*
- * If no valid ownCloud {@link Account} exists, the the user is requested
+ * If no valid ownCloud {@link Account} exists, then the user is requested
* to create a new ownCloud {@link Account}.
*
* POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}.
diff --git a/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
index 932f63e3af..31292a1d14 100644
--- a/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
@@ -446,7 +446,9 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
UserInfoActivity.openAccountRemovalConfirmationDialog(getAccount(), getFragmentManager(), true);
break;
case R.id.drawer_menu_account_add:
- createAccount(false);
+ Intent firstRunIntent = new Intent(getApplicationContext(), FirstRunActivity.class);
+ firstRunIntent.putExtra(FirstRunActivity.EXTRA_ALLOW_CLOSE, true);
+ startActivity(firstRunIntent);
break;
case R.id.drawer_menu_account_manage:
Intent manageAccountsIntent = new Intent(getApplicationContext(), ManageAccountsActivity.class);
diff --git a/src/main/java/com/owncloud/android/ui/activity/FirstRunActivity.java b/src/main/java/com/owncloud/android/ui/activity/FirstRunActivity.java
new file mode 100644
index 0000000000..b83a3d1480
--- /dev/null
+++ b/src/main/java/com/owncloud/android/ui/activity/FirstRunActivity.java
@@ -0,0 +1,228 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Bartosz Przybylski
+ * Copyright (C) 2015 Bartosz Przybylski
+ * Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2016 Nextcloud.
+ *
+ * This program 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 program 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 program. If not, see .
+ */
+
+package com.owncloud.android.ui.activity;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.v4.view.ViewPager;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.owncloud.android.MainApp;
+import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.authentication.AuthenticatorActivity;
+import com.owncloud.android.features.FeatureItem;
+import com.owncloud.android.ui.adapter.FeaturesViewAdapter;
+import com.owncloud.android.ui.whatsnew.ProgressIndicator;
+import com.owncloud.android.utils.DisplayUtils;
+
+/**
+ * Activity displaying general feature after a fresh install.
+ */
+public class FirstRunActivity extends BaseActivity implements ViewPager.OnPageChangeListener {
+
+ public static final String KEY_LAST_SEEN_VERSION_CODE = "lastSeenVersionCode";
+ public static final String EXTRA_ALLOW_CLOSE = "ALLOW_CLOSE";
+ public static final int FIRST_RUN_RESULT_CODE = 199;
+
+ private static final String TAG = FirstRunActivity.class.getSimpleName();
+
+ private ProgressIndicator mProgress;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.first_run_activity);
+
+ boolean isProviderOrOwnInstallationVisible = getResources().getBoolean(R.bool.show_provider_or_own_installation);
+
+ setSlideshowSize(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
+
+ Button loginButton = findViewById(R.id.login);
+ loginButton.setBackgroundColor(Color.WHITE);
+ loginButton.setTextColor(Color.BLACK);
+
+ loginButton.setOnClickListener(v -> {
+ if (getIntent().getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
+ Intent authenticatorActivityIntent = new Intent(this, AuthenticatorActivity.class);
+ authenticatorActivityIntent.putExtra(AuthenticatorActivity.EXTRA_USE_PROVIDER_AS_WEBLOGIN, false);
+ startActivityForResult(authenticatorActivityIntent, FIRST_RUN_RESULT_CODE);
+ } else {
+ finish();
+ }
+ });
+
+ Button providerButton = findViewById(R.id.signup);
+ providerButton.setBackgroundColor(getResources().getColor(R.color.primary_dark));
+ providerButton.setTextColor(getResources().getColor(R.color.login_text_color));
+ providerButton.setVisibility(isProviderOrOwnInstallationVisible ? View.VISIBLE : View.GONE);
+ providerButton.setOnClickListener(v -> {
+ Intent authenticatorActivityIntent = new Intent(this, AuthenticatorActivity.class);
+ authenticatorActivityIntent.putExtra(AuthenticatorActivity.EXTRA_USE_PROVIDER_AS_WEBLOGIN, true);
+
+ if (getIntent().getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
+ startActivityForResult(authenticatorActivityIntent, FIRST_RUN_RESULT_CODE);
+ } else {
+ authenticatorActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(authenticatorActivityIntent);
+ }
+ });
+
+ TextView hostOwnServerTextView = findViewById(R.id.host_own_server);
+ hostOwnServerTextView.setTextColor(getResources().getColor(R.color.login_text_color));
+ hostOwnServerTextView.setVisibility(isProviderOrOwnInstallationVisible ? View.VISIBLE : View.GONE);
+
+ mProgress = findViewById(R.id.progressIndicator);
+ ViewPager mPager = findViewById(R.id.contentPanel);
+
+ // Sometimes, accounts are not deleted when you uninstall the application so we'll do it now
+ if (isFirstRun(this)) {
+ AccountManager am = (AccountManager) getSystemService(ACCOUNT_SERVICE);
+ if (am != null) {
+ for (Account account : AccountUtils.getAccounts(this)) {
+ am.removeAccount(account, null, null);
+ }
+ }
+ }
+
+ FeaturesViewAdapter featuresViewAdapter = new FeaturesViewAdapter(getSupportFragmentManager(),
+ getFirstRun());
+ mProgress.setNumberOfSteps(featuresViewAdapter.getCount());
+ mPager.setAdapter(featuresViewAdapter);
+
+ mPager.addOnPageChangeListener(this);
+ }
+
+ private void setSlideshowSize(boolean isLandscape) {
+ boolean isProviderOrOwnInstallationVisible = getResources().getBoolean(R.bool.show_provider_or_own_installation);
+ LinearLayout buttonLayout = findViewById(R.id.buttonLayout);
+ LinearLayout.LayoutParams layoutParams;
+
+ buttonLayout.setOrientation(isLandscape ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
+
+ LinearLayout bottomLayout = findViewById(R.id.bottomLayout);
+ if (isProviderOrOwnInstallationVisible) {
+ layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ } else {
+ layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ DisplayUtils.convertDpToPixel(isLandscape ? 100f : 150f, this));
+ }
+
+ bottomLayout.setLayoutParams(layoutParams);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ setSlideshowSize(true);
+ } else {
+ setSlideshowSize(false);
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ onFinish();
+
+ if (getIntent().getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
+ super.onBackPressed();
+ }
+ }
+
+ private void onFinish() {
+ SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = pref.edit();
+ editor.putInt(KEY_LAST_SEEN_VERSION_CODE, MainApp.getVersionCode());
+ editor.apply();
+ }
+
+ static private boolean isFirstRun(Context context) {
+ return AccountUtils.getCurrentOwnCloudAccount(context) == null;
+ }
+
+ static public boolean runIfNeeded(Context context) {
+ if (context instanceof FirstRunActivity) {
+ return false;
+ }
+
+ if (isFirstRun(context) && context instanceof AuthenticatorActivity) {
+ context.startActivity(new Intent(context, FirstRunActivity.class));
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ // unused but to be implemented due to abstract parent
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ mProgress.animateToStep(position + 1);
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ // unused but to be implemented due to abstract parent
+ }
+
+ public void onHostYourOwnServerClick(View view) {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_server_install))));
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (FIRST_RUN_RESULT_CODE == requestCode && RESULT_OK == resultCode) {
+ setAccount(AccountUtils.getCurrentOwnCloudAccount(this));
+ onAccountSet(false);
+
+ Intent i = new Intent(this, FileDisplayActivity.class);
+ i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(i);
+ }
+ }
+
+ public static FeatureItem[] getFirstRun() {
+ return new FeatureItem[]{
+ new FeatureItem(R.drawable.logo, R.string.first_run_1_text, R.string.empty, true, false),
+ new FeatureItem(R.drawable.first_run_files, R.string.first_run_2_text, R.string.empty, true, false),
+ new FeatureItem(R.drawable.first_run_groupware, R.string.first_run_3_text, R.string.empty, true, false),
+ new FeatureItem(R.drawable.first_run_talk, R.string.first_run_4_text, R.string.empty, true, false)};
+ }
+}
diff --git a/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java b/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java
index edea936ea5..eced4a35ea 100644
--- a/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java
@@ -270,6 +270,13 @@ public class ManageAccountsActivity extends FileActivity
}
@Override
+ public void showFirstRunActivity() {
+ Intent firstRunIntent = new Intent(getApplicationContext(), FirstRunActivity.class);
+ firstRunIntent.putExtra(FirstRunActivity.EXTRA_ALLOW_CLOSE, true);
+ startActivity(firstRunIntent);
+ }
+
+ // @Override
public void createAccount() {
AccountManager am = AccountManager.get(getApplicationContext());
am.addAccount(MainApp.getAccountType(this),
@@ -286,9 +293,9 @@ public class ManageAccountsActivity extends FileActivity
String name = result.getString(AccountManager.KEY_ACCOUNT_NAME);
AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), name);
mAccountListAdapter = new AccountListAdapter(
- ManageAccountsActivity.this,
- getAccountListItems(),
- mTintedCheck
+ ManageAccountsActivity.this,
+ getAccountListItems(),
+ mTintedCheck
);
mListView.setAdapter(mAccountListAdapter);
runOnUiThread(new Runnable() {
diff --git a/src/main/java/com/owncloud/android/ui/activity/WhatsNewActivity.java b/src/main/java/com/owncloud/android/ui/activity/WhatsNewActivity.java
index 2763e03e9a..aedde89dec 100644
--- a/src/main/java/com/owncloud/android/ui/activity/WhatsNewActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/WhatsNewActivity.java
@@ -22,48 +22,30 @@
package com.owncloud.android.ui.activity;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.text.style.BulletSpan;
-import android.view.Gravity;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
-import com.owncloud.android.authentication.AccountAuthenticatorActivity;
import com.owncloud.android.authentication.AccountUtils;
-import com.owncloud.android.features.FeatureList;
-import com.owncloud.android.features.FeatureList.FeatureItem;
+import com.owncloud.android.features.FeatureItem;
+import com.owncloud.android.ui.adapter.FeaturesViewAdapter;
+import com.owncloud.android.ui.adapter.FeaturesWebViewAdapter;
import com.owncloud.android.ui.whatsnew.ProgressIndicator;
import com.owncloud.android.utils.ThemeUtils;
/**
- * Activity displaying general feature after a fresh install and new features after an update.
+ * Activity displaying new features after an update.
*/
public class WhatsNewActivity extends FragmentActivity implements ViewPager.OnPageChangeListener {
@@ -79,22 +61,12 @@ public class WhatsNewActivity extends FragmentActivity implements ViewPager.OnPa
super.onCreate(savedInstanceState);
setContentView(R.layout.whats_new_activity);
- int fontColor = ThemeUtils.fontColor(this);
+ int fontColor = getResources().getColor(R.color.login_text_color);
mProgress = findViewById(R.id.progressIndicator);
mPager = findViewById(R.id.contentPanel);
- final boolean isBeta = getResources().getBoolean(R.bool.is_beta);
- final boolean isMultiAccount = getResources().getBoolean(R.bool.multiaccount_support);
String[] urls = getResources().getStringArray(R.array.whatsnew_urls);
- // Sometimes, accounts are not deleted when you uninstall the application so we'll do it now
- if (isFirstRun()) {
- AccountManager am = (AccountManager) getSystemService(ACCOUNT_SERVICE);
- for (Account account : AccountUtils.getAccounts(this)) {
- am.removeAccount(account, null, null);
- }
- }
-
boolean showWebView = urls.length > 0;
if (showWebView) {
@@ -104,29 +76,25 @@ public class WhatsNewActivity extends FragmentActivity implements ViewPager.OnPa
mPager.setAdapter(featuresWebViewAdapter);
} else {
FeaturesViewAdapter featuresViewAdapter = new FeaturesViewAdapter(getSupportFragmentManager(),
- FeatureList.getFiltered(getLastSeenVersionCode(), isFirstRun(), isBeta, isMultiAccount));
+ getWhatsNew(this));
mProgress.setNumberOfSteps(featuresViewAdapter.getCount());
mPager.setAdapter(featuresViewAdapter);
}
mPager.addOnPageChangeListener(this);
-
mForwardFinishButton = findViewById(R.id.forward);
ThemeUtils.colorImageButton(mForwardFinishButton, fontColor);
-
- mForwardFinishButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (mProgress.hasNextStep()) {
- mPager.setCurrentItem(mPager.getCurrentItem() + 1, true);
- mProgress.animateToStep(mPager.getCurrentItem() + 1);
- } else {
- onFinish();
- finish();
- }
- updateNextButtonIfNeeded();
+
+ mForwardFinishButton.setOnClickListener(view -> {
+ if (mProgress.hasNextStep()) {
+ mPager.setCurrentItem(mPager.getCurrentItem() + 1, true);
+ mProgress.animateToStep(mPager.getCurrentItem() + 1);
+ } else {
+ onFinish();
+ finish();
}
+ updateNextButtonIfNeeded();
});
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
@@ -137,20 +105,15 @@ public class WhatsNewActivity extends FragmentActivity implements ViewPager.OnPa
mSkipButton = findViewById(R.id.skip);
mSkipButton.setTextColor(fontColor);
- mSkipButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onFinish();
- finish();
- }
+ mSkipButton.setOnClickListener(view -> {
+ onFinish();
+ finish();
});
TextView tv = findViewById(R.id.welcomeText);
if (showWebView) {
tv.setText(R.string.app_name);
- } else if (isFirstRun()) {
- tv.setText(R.string.empty);
} else {
tv.setText(String.format(getString(R.string.whats_new_title), MainApp.getVersionName()));
}
@@ -164,7 +127,6 @@ public class WhatsNewActivity extends FragmentActivity implements ViewPager.OnPa
super.onBackPressed();
}
-
private void updateNextButtonIfNeeded() {
if (!mProgress.hasNextStep()) {
mForwardFinishButton.setImageResource(R.drawable.ic_done_white);
@@ -182,15 +144,6 @@ public class WhatsNewActivity extends FragmentActivity implements ViewPager.OnPa
editor.apply();
}
- static public int getLastSeenVersionCode() {
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(MainApp.getAppContext());
- return pref.getInt(KEY_LAST_SEEN_VERSION_CODE, 0);
- }
-
- static private boolean isFirstRun() {
- return getLastSeenVersionCode() == 0 && AccountUtils.getCurrentOwnCloudAccount(MainApp.getAppContext()) == null;
- }
-
static public void runIfNeeded(Context context) {
if (!context.getResources().getBoolean(R.bool.show_whats_new)) {
return;
@@ -206,12 +159,7 @@ public class WhatsNewActivity extends FragmentActivity implements ViewPager.OnPa
}
static private boolean shouldShow(Context context) {
- final boolean isBeta = context.getResources().getBoolean(R.bool.is_beta);
- final boolean isMultiAccount = context.getResources().getBoolean(R.bool.multiaccount_support);
-
- return (isFirstRun() && context instanceof AccountAuthenticatorActivity) || (!(isFirstRun() &&
- (context instanceof FileDisplayActivity)) && !(context instanceof PassCodeActivity) &&
- (FeatureList.getFiltered(getLastSeenVersionCode(), isFirstRun(), isBeta, isMultiAccount).length > 0));
+ return !(context instanceof PassCodeActivity) && (getWhatsNew(context).length > 0);
}
@Override
@@ -230,172 +178,22 @@ public class WhatsNewActivity extends FragmentActivity implements ViewPager.OnPa
// unused but to be implemented due to abstract parent
}
- private final class FeaturesWebViewAdapter extends FragmentPagerAdapter {
- private String[] mWebUrls;
-
- public FeaturesWebViewAdapter(FragmentManager fm, String[] webUrls) {
- super(fm);
- mWebUrls = webUrls;
- }
-
- @Override
- public Fragment getItem(int position) {
- return FeatureWebFragment.newInstance(mWebUrls[position]);
- }
-
- @Override
- public int getCount() {
- return mWebUrls.length;
- }
+ static private boolean isFirstRun(Context context) {
+ return AccountUtils.getCurrentOwnCloudAccount(context) == null;
}
- public static class FeatureWebFragment extends Fragment {
- private String mWebUrl;
+ static private FeatureItem[] getWhatsNew(Context context) {
+ int itemVersionCode = 30030000;
- static public FeatureWebFragment newInstance(String webUrl) {
- FeatureWebFragment f = new FeatureWebFragment();
- Bundle args = new Bundle();
- args.putString("url", webUrl);
- f.setArguments(args);
- return f;
- }
+ int lastSeenVersionCode = MainApp.getLastSeenVersionCode(context);
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mWebUrl = getArguments() != null ? getArguments().getString("url") : null;
- }
-
- @SuppressLint("SetJavaScriptEnabled")
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.whats_new_webview_element, container, false);
-
- WebView webView = v.findViewById(R.id.whatsNewWebView);
- webView.getSettings().setJavaScriptEnabled(true);
- webView.getSettings().setDomStorageEnabled(true);
- webView.getSettings().setAllowFileAccess(false);
- webView.setWebViewClient(new WebViewClient());
- webView.loadUrl(mWebUrl);
-
- return v;
- }
- }
-
- private final class FeaturesViewAdapter extends FragmentPagerAdapter {
-
- private FeatureItem[] mFeatures;
-
- public FeaturesViewAdapter(FragmentManager fm, FeatureItem[] features) {
- super(fm);
- mFeatures = features;
- }
-
- @Override
- public Fragment getItem(int position) {
- return FeatureFragment.newInstance(mFeatures[position]);
- }
-
- @Override
- public int getCount() {
- return mFeatures.length;
- }
- }
-
- public static class FeatureFragment extends Fragment {
- private FeatureItem mItem;
-
- static public FeatureFragment newInstance(FeatureItem item) {
- FeatureFragment f = new FeatureFragment();
- Bundle args = new Bundle();
- args.putParcelable("feature", item);
- f.setArguments(args);
- return f;
- }
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mItem = getArguments() != null ? (FeatureItem) getArguments().getParcelable("feature") : null;
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.whats_new_element, container, false);
- int fontColor = ThemeUtils.fontColor(getContext());
-
- ImageView iv = v.findViewById(R.id.whatsNewImage);
- if (mItem.shouldShowImage()) {
- iv.setImageResource(mItem.getImage());
- }
-
- TextView titleTextView = v.findViewById(R.id.whatsNewTitle);
- if (mItem.shouldShowTitleText()) {
- titleTextView.setText(mItem.getTitleText());
- titleTextView.setTextColor(fontColor);
- }
-
- if (mItem.shouldShowContentText()) {
- LinearLayout linearLayout = v.findViewById(R.id.whatsNewTextLayout);
-
-
- if (mItem.shouldShowBulletPointList()) {
- String[] texts = getText(mItem.getContentText()).toString().split("\n");
-
- for (String text : texts) {
- TextView textView = generateTextView(text, getContext(),
- mItem.shouldContentCentered(), fontColor, true);
-
- linearLayout.addView(textView);
- }
- } else {
- TextView textView = generateTextView(getText(mItem.getContentText()).toString(),
- getContext(), mItem.shouldContentCentered(), fontColor, false);
-
- linearLayout.addView(textView);
- }
- }
-
- return v;
- }
- }
-
- private static TextView generateTextView(String text, Context context,
- boolean shouldContentCentered, int fontColor,
- boolean showBulletPoints) {
- int standardMargin = context.getResources().getDimensionPixelSize(R.dimen.standard_margin);
- int doubleMargin = context.getResources()
- .getDimensionPixelSize(R.dimen.standard_double_margin);
- int zeroMargin = context.getResources().getDimensionPixelSize(R.dimen.zero);
-
- TextView textView = new TextView(context);
- LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- layoutParams.setMargins(doubleMargin, standardMargin, doubleMargin, zeroMargin);
- textView.setTextAppearance(context, R.style.NextcloudTextAppearanceMedium);
- textView.setLayoutParams(layoutParams);
-
- if (showBulletPoints) {
- BulletSpan bulletSpan = new BulletSpan(standardMargin, fontColor);
- SpannableString spannableString = new SpannableString(text);
- spannableString.setSpan(bulletSpan, 0, spannableString.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- textView.setText(spannableString);
+ if (!isFirstRun(context) && MainApp.getVersionCode() >= itemVersionCode
+ && lastSeenVersionCode < itemVersionCode) {
+ return new FeatureItem[]{(new FeatureItem(R.drawable.whats_new_device_credentials,
+ R.string.whats_new_device_credentials_title, R.string.whats_new_device_credentials_content,
+ false, false))};
} else {
- textView.setText(text);
+ return new FeatureItem[0];
}
- textView.setTextColor(fontColor);
-
- if (!shouldContentCentered) {
- textView.setGravity(Gravity.START);
- } else {
- textView.setGravity(Gravity.CENTER_HORIZONTAL);
- }
-
- return textView;
}
}
diff --git a/src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java
index 3e4810fe76..adad7b6ebb 100644
--- a/src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java
+++ b/src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java
@@ -127,7 +127,7 @@ public class AccountListAdapter extends ArrayAdapter implements
((ImageView) actionView.findViewById(R.id.user_icon)).setImageResource(R.drawable.ic_account_plus);
// bind action listener
- actionView.setOnClickListener(v -> mListener.createAccount());
+ actionView.setOnClickListener(v -> mListener.showFirstRunActivity());
return actionView;
}
@@ -184,7 +184,7 @@ public class AccountListAdapter extends ArrayAdapter implements
*/
public interface AccountListAdapterListener {
- void createAccount();
+ void showFirstRunActivity();
}
/**
diff --git a/src/main/java/com/owncloud/android/ui/adapter/FeaturesViewAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/FeaturesViewAdapter.java
new file mode 100644
index 0000000000..beb392150d
--- /dev/null
+++ b/src/main/java/com/owncloud/android/ui/adapter/FeaturesViewAdapter.java
@@ -0,0 +1,28 @@
+package com.owncloud.android.ui.adapter;
+
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+
+import com.owncloud.android.features.FeatureItem;
+import com.owncloud.android.ui.fragment.FeatureFragment;
+
+public class FeaturesViewAdapter extends FragmentPagerAdapter {
+
+ private FeatureItem[] mFeatures;
+
+ public FeaturesViewAdapter(FragmentManager fm, FeatureItem[] features) {
+ super(fm);
+ mFeatures = features;
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ return FeatureFragment.newInstance(mFeatures[position]);
+ }
+
+ @Override
+ public int getCount() {
+ return mFeatures.length;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/owncloud/android/ui/adapter/FeaturesWebViewAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/FeaturesWebViewAdapter.java
new file mode 100644
index 0000000000..c7e2520be5
--- /dev/null
+++ b/src/main/java/com/owncloud/android/ui/adapter/FeaturesWebViewAdapter.java
@@ -0,0 +1,26 @@
+package com.owncloud.android.ui.adapter;
+
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+
+import com.owncloud.android.ui.fragment.FeatureWebFragment;
+
+public class FeaturesWebViewAdapter extends FragmentPagerAdapter {
+ private String[] mWebUrls;
+
+ public FeaturesWebViewAdapter(FragmentManager fm, String[] webUrls) {
+ super(fm);
+ mWebUrls = webUrls;
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ return FeatureWebFragment.newInstance(mWebUrls[position]);
+ }
+
+ @Override
+ public int getCount() {
+ return mWebUrls.length;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/owncloud/android/ui/fragment/FeatureFragment.java b/src/main/java/com/owncloud/android/ui/fragment/FeatureFragment.java
new file mode 100644
index 0000000000..e609f3085d
--- /dev/null
+++ b/src/main/java/com/owncloud/android/ui/fragment/FeatureFragment.java
@@ -0,0 +1,119 @@
+package com.owncloud.android.ui.fragment;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.BulletSpan;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.owncloud.android.R;
+import com.owncloud.android.features.FeatureItem;
+
+
+public class FeatureFragment extends Fragment {
+ private FeatureItem mItem;
+
+ static public FeatureFragment newInstance(FeatureItem item) {
+ FeatureFragment f = new FeatureFragment();
+ Bundle args = new Bundle();
+ args.putParcelable("feature", item);
+ f.setArguments(args);
+ return f;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mItem = getArguments() != null ? (FeatureItem) getArguments().getParcelable("feature") : null;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.whats_new_element, container, false);
+ int fontColor = getResources().getColor(R.color.login_text_color);
+
+ ImageView iv = v.findViewById(R.id.whatsNewImage);
+ if (mItem.shouldShowImage()) {
+ iv.setImageResource(mItem.getImage());
+ }
+
+ TextView titleTextView = v.findViewById(R.id.whatsNewTitle);
+ if (mItem.shouldShowTitleText()) {
+ titleTextView.setText(mItem.getTitleText());
+ titleTextView.setTextColor(fontColor);
+ titleTextView.setVisibility(View.VISIBLE);
+ } else {
+ titleTextView.setVisibility(View.GONE);
+ }
+
+ LinearLayout linearLayout = v.findViewById(R.id.whatsNewTextLayout);
+ if (mItem.shouldShowContentText()) {
+ if (mItem.shouldShowBulletPointList()) {
+ String[] texts = getText(mItem.getContentText()).toString().split("\n");
+
+ for (String text : texts) {
+ TextView textView = generateTextView(text, getContext(),
+ mItem.shouldContentCentered(), fontColor, true);
+
+ linearLayout.addView(textView);
+ }
+ } else {
+ TextView textView = generateTextView(getText(mItem.getContentText()).toString(),
+ getContext(), mItem.shouldContentCentered(), fontColor, false);
+
+ linearLayout.addView(textView);
+ }
+ } else {
+ linearLayout.setVisibility(View.GONE);
+ }
+
+ return v;
+ }
+
+ private TextView generateTextView(String text, Context context,
+ boolean shouldContentCentered, int fontColor,
+ boolean showBulletPoints) {
+ int standardMargin = context.getResources().getDimensionPixelSize(R.dimen.standard_margin);
+ int doubleMargin = context.getResources()
+ .getDimensionPixelSize(R.dimen.standard_double_margin);
+ int zeroMargin = context.getResources().getDimensionPixelSize(R.dimen.zero);
+
+ TextView textView = new TextView(context);
+ LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ layoutParams.setMargins(doubleMargin, standardMargin, doubleMargin, zeroMargin);
+ textView.setTextAppearance(context, R.style.NextcloudTextAppearanceMedium);
+ textView.setLayoutParams(layoutParams);
+
+ if (showBulletPoints) {
+ BulletSpan bulletSpan = new BulletSpan(standardMargin, fontColor);
+ SpannableString spannableString = new SpannableString(text);
+ spannableString.setSpan(bulletSpan, 0, spannableString.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ textView.setText(spannableString);
+ } else {
+ textView.setText(text);
+ }
+ textView.setTextColor(fontColor);
+
+ if (!shouldContentCentered) {
+ textView.setGravity(Gravity.START);
+ } else {
+ textView.setGravity(Gravity.CENTER_HORIZONTAL);
+ }
+
+ return textView;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/owncloud/android/ui/fragment/FeatureWebFragment.java b/src/main/java/com/owncloud/android/ui/fragment/FeatureWebFragment.java
new file mode 100644
index 0000000000..59feea0105
--- /dev/null
+++ b/src/main/java/com/owncloud/android/ui/fragment/FeatureWebFragment.java
@@ -0,0 +1,49 @@
+package com.owncloud.android.ui.fragment;
+
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import com.owncloud.android.R;
+
+public class FeatureWebFragment extends Fragment {
+ private String mWebUrl;
+
+ static public FeatureWebFragment newInstance(String webUrl) {
+ FeatureWebFragment f = new FeatureWebFragment();
+ Bundle args = new Bundle();
+ args.putString("url", webUrl);
+ f.setArguments(args);
+ return f;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mWebUrl = getArguments() != null ? getArguments().getString("url") : null;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater,
+ @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.whats_new_webview_element, container, false);
+
+ WebView webView = v.findViewById(R.id.whatsNewWebView);
+ webView.getSettings().setJavaScriptEnabled(true);
+ webView.getSettings().setDomStorageEnabled(true);
+ webView.getSettings().setAllowFileAccess(false);
+ webView.setWebViewClient(new WebViewClient());
+ webView.loadUrl(mWebUrl);
+
+ return v;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/owncloud/android/ui/whatsnew/ProgressIndicator.java b/src/main/java/com/owncloud/android/ui/whatsnew/ProgressIndicator.java
index edf399a2ab..c117dc5b08 100644
--- a/src/main/java/com/owncloud/android/ui/whatsnew/ProgressIndicator.java
+++ b/src/main/java/com/owncloud/android/ui/whatsnew/ProgressIndicator.java
@@ -33,7 +33,6 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import com.owncloud.android.R;
-import com.owncloud.android.utils.ThemeUtils;
/**
* Progress indicator visualizing the actual progress with dots.
@@ -65,7 +64,7 @@ public class ProgressIndicator extends FrameLayout {
}
public void setNumberOfSteps(int steps) {
- int fontColor = ThemeUtils.fontColor(getContext());
+ int fontColor = getResources().getColor(R.color.login_text_color);
mNumberOfSteps = steps;
mDotsContainer.removeAllViews();
for (int i = 0; i < steps; ++i) {
diff --git a/src/main/java/com/owncloud/android/utils/DisplayUtils.java b/src/main/java/com/owncloud/android/utils/DisplayUtils.java
index a5aeb19bde..7edce285e5 100644
--- a/src/main/java/com/owncloud/android/utils/DisplayUtils.java
+++ b/src/main/java/com/owncloud/android/utils/DisplayUtils.java
@@ -50,6 +50,7 @@ import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.StyleSpan;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -762,6 +763,13 @@ public class DisplayUtils {
}
}
+ public static int convertDpToPixel(float dp, Context context) {
+ Resources resources = context.getResources();
+ DisplayMetrics metrics = resources.getDisplayMetrics();
+
+ return (int) (dp * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
+ }
+
static public void showServerOutdatedSnackbar(Activity activity) {
Snackbar.make(activity.findViewById(android.R.id.content),
R.string.outdated_server, Snackbar.LENGTH_INDEFINITE)
diff --git a/src/main/res/drawable/first_run_files.xml b/src/main/res/drawable/first_run_files.xml
new file mode 100644
index 0000000000..3c134cf4ae
--- /dev/null
+++ b/src/main/res/drawable/first_run_files.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/src/main/res/drawable/first_run_groupware.xml b/src/main/res/drawable/first_run_groupware.xml
new file mode 100644
index 0000000000..38d32c2389
--- /dev/null
+++ b/src/main/res/drawable/first_run_groupware.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/drawable/first_run_talk.xml b/src/main/res/drawable/first_run_talk.xml
new file mode 100644
index 0000000000..9958a72452
--- /dev/null
+++ b/src/main/res/drawable/first_run_talk.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/layout-land/account_setup.xml b/src/main/res/layout-land/account_setup.xml
index 55bd979557..06df2f249a 100644
--- a/src/main/res/layout-land/account_setup.xml
+++ b/src/main/res/layout-land/account_setup.xml
@@ -288,26 +288,6 @@
android:text="@string/setup_btn_connect"
android:contentDescription="@string/setup_btn_connect"
android:visibility="gone"/>
-
-
-
-
-
-
-
diff --git a/src/main/res/layout/account_setup.xml b/src/main/res/layout/account_setup.xml
index 3963648d24..51663f2e8e 100644
--- a/src/main/res/layout/account_setup.xml
+++ b/src/main/res/layout/account_setup.xml
@@ -271,25 +271,6 @@
android:text="@string/setup_btn_connect"
android:contentDescription="@string/setup_btn_connect"
android:visibility="gone"/>
-
-
-
-
-
-
diff --git a/src/main/res/layout/first_run_activity.xml b/src/main/res/layout/first_run_activity.xml
new file mode 100644
index 0000000000..1ef6a86bb7
--- /dev/null
+++ b/src/main/res/layout/first_run_activity.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/res/layout/whats_new_activity.xml b/src/main/res/layout/whats_new_activity.xml
index a240b94563..7a512d42fe 100644
--- a/src/main/res/layout/whats_new_activity.xml
+++ b/src/main/res/layout/whats_new_activity.xml
@@ -47,8 +47,10 @@
@@ -86,8 +88,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:src="@drawable/arrow_right"
- android:contentDescription="@string/forward"/>
+ android:contentDescription="@string/forward"
+ android:src="@drawable/arrow_right"/>
diff --git a/src/main/res/layout/whats_new_element.xml b/src/main/res/layout/whats_new_element.xml
index 38bd47e66d..580280a976 100644
--- a/src/main/res/layout/whats_new_element.xml
+++ b/src/main/res/layout/whats_new_element.xml
@@ -20,48 +20,48 @@
License along with this program. If not, see .
-->
+ android:layout_weight="60"
+ android:contentDescription="@string/what_s_new_image"
+ app:srcCompat="@drawable/whats_new_files"/>
+ android:textStyle="bold"/>
diff --git a/src/main/res/values/setup.xml b/src/main/res/values/setup.xml
index 019c91fccb..e4a3dc482e 100644
--- a/src/main/res/values/setup.xml
+++ b/src/main/res/values/setup.xml
@@ -27,8 +27,8 @@
regular
- true
- "https://nextcloud.com/providers"
+ true
+ https://www.nextcloud.com/register
off
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 3627391384..0d38d32337 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -797,6 +797,13 @@
E2E mnemonic
To show mnemonic please enable device credentials.
Use Android\'s device internal protection
+ Log in
+ Sign up with provider
+ Host your own server
+ Keep your data secure and under your control
+ Secure collaboration & file exchange
+ Easy-to-use web mail, calendering & contacts
+ Screensharing, online meetings & web conferences
Use anything like a pattern, password, pin or your fingerprint to keep your data safe.
Restore deleted file
Restore file
@@ -810,7 +817,6 @@
New %1$s media folder detected.
photo
video
-
The server has reached end of life, please upgrade!
Dismiss
No app available to send mails!