From b6c31cf6cee805acad17373dede4e61c025c208b Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Wed, 24 May 2023 18:01:31 +0600 Subject: [PATCH 01/11] Absract out openId implementation Current openId login flow is tightly coupled with google auth flow. To add other openId support, we need to first decouple it. This commit do just that + it re-factor how openId credentials are shared, now via gitlab CI variable, which will be pushed in local.properties file on the build stage. --- .gitlab-ci.yml | 3 + app/build.gradle | 16 +- app/src/main/AndroidManifest.xml | 4 +- .../authorization/IdentityProvider.java | 215 ++------- .../davdroid/ui/setup/GoogleAuthFragment.kt | 79 ++++ .../ui/setup/GoogleAuthenticatorFragment.kt | 443 ------------------ .../ui/setup/GoogleAuthenticatorModel.kt | 48 -- .../davdroid/ui/setup/LoginActivity.kt | 23 +- .../setup/OpenIdAuthenticationBaseFragment.kt | 211 +++++++++ .../ui/setup/OpenIdAuthenticationViewModel.kt | 127 +++++ .../layout/fragment_google_authenticator.xml | 24 - .../main/res/layout/frament_openid_auth.xml | 23 + .../values/account_providers_auth_config.xml | 18 - app/src/main/res/values/strings.xml | 3 + 14 files changed, 519 insertions(+), 718 deletions(-) create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthFragment.kt delete mode 100644 app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt delete mode 100644 app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorModel.kt create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationViewModel.kt delete mode 100644 app/src/main/res/layout/fragment_google_authenticator.xml create mode 100644 app/src/main/res/layout/frament_openid_auth.xml delete mode 100644 app/src/main/res/values/account_providers_auth_config.xml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 00cb60995..8fc973b0f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,6 +6,9 @@ stages: before_script: - echo email.key=$PEPPER >> local.properties + - echo GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID >> local.properties + - echo GOOGLE_REDIRECT_URI=$GOOGLE_REDIRECT_URI >> local.properties + - echo YAHOO_CLIENT_ID=$YAHOO_CLIENT_ID >> local.properties - export GRADLE_USER_HOME=$(pwd)/.gradle - chmod +x ./gradlew diff --git a/app/build.gradle b/app/build.gradle index 72b5a40fe..1e697ceeb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -86,7 +86,7 @@ android { shrinkResources true - buildConfigField "String", "EMAIL_KEY", "\"${retrieveEmailKey()}\"" + buildConfigField "String", "EMAIL_KEY", "\"${retrieveKey("email.key")}\"" } } @@ -101,8 +101,14 @@ android { } defaultConfig { + buildConfigField "String", "GOOGLE_CLIENT_ID", "\"${retrieveKey("GOOGLE_CLIENT_ID")}\"" + buildConfigField "String", "GOOGLE_REDIRECT_URI", "\"${retrieveKey("GOOGLE_REDIRECT_URI")}\"" + + buildConfigField "String", "YAHOO_CLIENT_ID", "\"${retrieveKey("YAHOO_CLIENT_ID")}\"" + manifestPlaceholders = [ - 'appAuthRedirectScheme': 'net.openid.appauthdemo' + 'appAuthRedirectScheme': applicationId, + "googleAuthRedirectScheme": retrieveKey("GOOGLE_REDIRECT_URI") ] } } @@ -195,13 +201,13 @@ dependencies { testImplementation "com.squareup.okhttp3:mockwebserver:${versions.okhttp}" } -def retrieveEmailKey() { +def retrieveKey(String keyName) { Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) - String value = properties.getProperty("email.key") + String value = properties.getProperty(keyName) if (value == null) { - throw new GradleException("email.key property not found in local.properties file") + throw new GradleException(keyName + " property not found in local.properties file") } return value diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d3f5abf34..1fb492915 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -567,8 +567,8 @@ - - + + diff --git a/app/src/main/java/at/bitfire/davdroid/authorization/IdentityProvider.java b/app/src/main/java/at/bitfire/davdroid/authorization/IdentityProvider.java index 7277d3bdf..4dc78579f 100644 --- a/app/src/main/java/at/bitfire/davdroid/authorization/IdentityProvider.java +++ b/app/src/main/java/at/bitfire/davdroid/authorization/IdentityProvider.java @@ -1,5 +1,5 @@ /* - * Copyright ECORP SAS 2022 + * Copyright MURENA SAS 2022, 2023 * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -16,200 +16,97 @@ package at.bitfire.davdroid.authorization; -import android.content.Context; -import android.content.res.Resources; import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.StringRes; import net.openid.appauth.AuthorizationServiceConfiguration; import net.openid.appauth.AuthorizationServiceConfiguration.RetrieveConfigurationCallback; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.Objects; -import at.bitfire.davdroid.R; +import at.bitfire.davdroid.BuildConfig; /** * An abstraction of identity providers, containing all necessary info for the demo app. */ public class IdentityProvider { - /** - * Value used to indicate that a configured property is not specified or required. - */ - public static final int NOT_SPECIFIED = -1; - public static final IdentityProvider GOOGLE = new IdentityProvider( - "Google", - R.string.google_discovery_uri, - NOT_SPECIFIED, // auth endpoint is discovered - NOT_SPECIFIED, // token endpoint is discovered - R.string.google_client_id, - NOT_SPECIFIED, // client secret is not required for Google - R.string.google_auth_redirect_uri, - R.string.google_scope_string, - R.string.google_name); - - public static final List PROVIDERS = Arrays.asList( - GOOGLE); - - public static List getEnabledProviders(Context context) { - ArrayList providers = new ArrayList<>(); - for (IdentityProvider provider : PROVIDERS) { - provider.readConfiguration(context); - providers.add(provider); - } - return providers; - } - + "https://accounts.google.com/.well-known/openid-configuration", + null, + null, + BuildConfig.GOOGLE_CLIENT_ID, + null, + BuildConfig.GOOGLE_REDIRECT_URI + ":/oauth2redirect", + "openid profile email https://www.googleapis.com/auth/carddav https://www.googleapis.com/auth/calendar https://mail.google.com/", + null + ); + + private final Uri mDiscoveryEndpoint; + private final Uri mAuthEndpoint; + private final Uri mTokenEndpoint; @NonNull - public final String name; - - @StringRes - public final int buttonContentDescriptionRes; - - @StringRes - private final int mDiscoveryEndpointRes; - - @StringRes - private final int mAuthEndpointRes; - - @StringRes - private final int mTokenEndpointRes; - - @StringRes - private final int mClientIdRes; - - @StringRes - private final int mClientSecretRes; - - @StringRes - private final int mRedirectUriRes; - - @StringRes - private final int mScopeRes; - - private boolean mConfigurationRead = false; - private Uri mDiscoveryEndpoint; - private Uri mAuthEndpoint; - private Uri mTokenEndpoint; - private String mClientId; - private String mClientSecret; - private Uri mRedirectUri; - private String mScope; + private final String mClientId; + private final String mClientSecret; + @NonNull + private final Uri mRedirectUri; + private final String mScope; + private final String mUserInfoEndpoint; IdentityProvider( - @NonNull String name, - @StringRes int discoveryEndpointRes, - @StringRes int authEndpointRes, - @StringRes int tokenEndpointRes, - @StringRes int clientIdRes, - @StringRes int clientSecretRes, - @StringRes int redirectUriRes, - @StringRes int scopeRes, - @StringRes int buttonContentDescriptionRes) { - if (!isSpecified(discoveryEndpointRes) - && !isSpecified(authEndpointRes) - && !isSpecified(tokenEndpointRes)) { + @Nullable String discoveryEndpoint, + @Nullable String authEndpoint, + @Nullable String tokenEndpoint, + @NonNull String clientId, + @Nullable String clientSecret, + @NonNull String redirectUri, + @Nullable String scope, + @Nullable String userInfoEndpoint) { + if (discoveryEndpoint == null && + (authEndpoint == null || tokenEndpoint == null)) { throw new IllegalArgumentException( "the discovery endpoint or the auth and token endpoints must be specified"); } - this.name = name; - this.mDiscoveryEndpointRes = discoveryEndpointRes; - this.mAuthEndpointRes = authEndpointRes; - this.mTokenEndpointRes = tokenEndpointRes; - this.mClientIdRes = checkSpecified(clientIdRes, "clientIdRes"); - this.mClientSecretRes = clientSecretRes; - this.mRedirectUriRes = checkSpecified(redirectUriRes, "redirectUriRes"); - this.mScopeRes = checkSpecified(scopeRes, "scopeRes"); - this.buttonContentDescriptionRes = - checkSpecified(buttonContentDescriptionRes, "buttonContentDescriptionRes"); - } - - /** - * This must be called before any of the getters will function. - */ - public void readConfiguration(Context context) { - if (mConfigurationRead) { - return; - } - - Resources res = context.getResources(); - - mDiscoveryEndpoint = isSpecified(mDiscoveryEndpointRes) - ? getUriResource(res, mDiscoveryEndpointRes, "discoveryEndpointRes") - : null; - mAuthEndpoint = isSpecified(mAuthEndpointRes) - ? getUriResource(res, mAuthEndpointRes, "authEndpointRes") - : null; - mTokenEndpoint = isSpecified(mTokenEndpointRes) - ? getUriResource(res, mTokenEndpointRes, "tokenEndpointRes") - : null; - mClientId = res.getString(mClientIdRes); - mClientSecret = isSpecified(mClientSecretRes) ? res.getString(mClientSecretRes) : null; - mRedirectUri = getUriResource(res, mRedirectUriRes, "mRedirectUriRes"); - mScope = res.getString(mScopeRes); - - mConfigurationRead = true; - } - - private void checkConfigurationRead() { - if (!mConfigurationRead) { - throw new IllegalStateException("Configuration not read"); - } - } - - @Nullable - public Uri getDiscoveryEndpoint() { - checkConfigurationRead(); - return mDiscoveryEndpoint; - } - - @Nullable - public Uri getAuthEndpoint() { - checkConfigurationRead(); - return mAuthEndpoint; - } - - @Nullable - public Uri getTokenEndpoint() { - checkConfigurationRead(); - return mTokenEndpoint; + this.mDiscoveryEndpoint = retrieveUri(discoveryEndpoint); + this.mAuthEndpoint = retrieveUri(authEndpoint); + this.mTokenEndpoint = retrieveUri(tokenEndpoint); + this.mClientId = clientId; + this.mClientSecret = clientSecret; + this.mRedirectUri = Objects.requireNonNull(retrieveUri(redirectUri)); + this.mScope = scope; + this.mUserInfoEndpoint = userInfoEndpoint; } @NonNull public String getClientId() { - checkConfigurationRead(); return mClientId; } @Nullable public String getClientSecret() { - checkConfigurationRead(); return mClientSecret; } @NonNull public Uri getRedirectUri() { - checkConfigurationRead(); return mRedirectUri; } @NonNull public String getScope() { - checkConfigurationRead(); return mScope; } - public void retrieveConfig(Context context, - RetrieveConfigurationCallback callback) { - readConfiguration(context); - if (getDiscoveryEndpoint() != null) { + @Nullable + public String getUserInfoEndpoint() { + return mUserInfoEndpoint; + } + + public void retrieveConfig(RetrieveConfigurationCallback callback) { + if (mDiscoveryEndpoint != null) { AuthorizationServiceConfiguration.fetchFromUrl(mDiscoveryEndpoint, callback); } else { AuthorizationServiceConfiguration config = @@ -218,19 +115,11 @@ public class IdentityProvider { } } - private static boolean isSpecified(int value) { - return value != NOT_SPECIFIED; - } - - private static int checkSpecified(int value, String valueName) { - if (value == NOT_SPECIFIED) { - throw new IllegalArgumentException(valueName + " must be specified"); + @Nullable + private Uri retrieveUri(@Nullable String value) { + if (value == null) { + return null; } - return value; - } - - private static Uri getUriResource(Resources res, @StringRes int resId, String resName) { - return Uri.parse(res.getString(resId)); + return Uri.parse(value); } } - diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthFragment.kt new file mode 100644 index 000000000..ef16ce841 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthFragment.kt @@ -0,0 +1,79 @@ +/* + * Copyright MURENA SAS 2023 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 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 . + */ + +package at.bitfire.davdroid.ui.setup + +import android.os.Bundle +import android.text.Layout +import android.text.SpannableString +import android.text.style.AlignmentSpan +import android.view.View +import at.bitfire.davdroid.R +import at.bitfire.davdroid.authorization.IdentityProvider +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.json.JSONObject + +class GoogleAuthFragment : OpenIdAuthenticationBaseFragment(IdentityProvider.GOOGLE) { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + handleConfirmationDialog() + } + + override fun onAuthenticationComplete(userData: JSONObject) { + val emailKey = "email" + + if (!userData.has(emailKey)) { + handleLoginFailedToast() + return + } + + val email = userData.getString(emailKey) + if (email.isBlank()) { + handleLoginFailedToast() + return + } + + val baseUrl = "https://apidata.googleusercontent.com/caldav/v2/$email/user" + proceedNext(email, baseUrl) + } + + private fun handleConfirmationDialog() { + if (isAuthFlowComplete()) { + return + } + + showConfirmationDialog() + } + + private fun showConfirmationDialog() { + val title = SpannableString(getString(R.string.google_alert_title)) + title.setSpan( + AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), + 0, + title.length, + 0 + ) + + MaterialAlertDialogBuilder(requireContext(), R.style.CustomAlertDialogStyle) + .setTitle(title) + .setMessage(R.string.google_alert_message) + .setCancelable(false) + .setPositiveButton(R.string.ok) { _, _ -> + startAuthFLow() + }.show() + } +} diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt deleted file mode 100644 index 01d1d82a5..000000000 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt +++ /dev/null @@ -1,443 +0,0 @@ -/* - * Copyright ECORP SAS 2022 - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 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 . - */ - -package at.bitfire.davdroid.ui.setup - -import android.app.Activity -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.net.ConnectivityManager -import android.os.AsyncTask -import android.os.Build -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.text.Layout -import android.text.SpannableString -import android.text.style.AlignmentSpan -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.fragment.app.viewModels -import at.bitfire.davdroid.R -import at.bitfire.davdroid.authorization.IdentityProvider -import at.bitfire.davdroid.databinding.FragmentGoogleAuthenticatorBinding -import at.bitfire.davdroid.db.Credentials -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import net.openid.appauth.* -import org.json.JSONException -import org.json.JSONObject -import java.io.BufferedReader -import java.io.IOException -import java.io.InputStream -import java.io.InputStreamReader -import java.net.HttpURLConnection -import java.net.MalformedURLException -import java.net.URI -import java.net.URL - -class GoogleAuthenticatorFragment : Fragment(), AuthorizationService.TokenResponseCallback { - - private val model by viewModels() - private val loginModel by activityViewModels() - - private val extraAuthServiceDiscovery = "authServiceDiscovery" - private val extraClientSecret = "clientSecret" - - private var authState: AuthState? = null - private var authorizationService: AuthorizationService? = null - - private val bufferSize = 1024 - private var userInfoJson: JSONObject? = null - - private fun isNetworkAvailable(): Boolean { - val connectivityManager = requireActivity().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val activeNetworkInfo = connectivityManager.activeNetworkInfo - return activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { - // Initialise the authorization service - authorizationService = AuthorizationService(requireContext()) - - val v = FragmentGoogleAuthenticatorBinding.inflate(inflater, container, false) - v.lifecycleOwner = this - v.model = model - - activity?.intent?.let { - model.initialize(it) - val builder = MaterialAlertDialogBuilder(requireContext(), R.style.CustomAlertDialogStyle) - - if (!with(it) { getBooleanExtra(LoginActivity.ACCOUNT_PROVIDER_GOOGLE_AUTH_COMPLETE, false) }) { - val title = SpannableString(getString(R.string.google_alert_title)) - // alert dialog title align center - title.setSpan( - AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), - 0, - title.length, - 0 - ) - - builder.setTitle(title) - builder.setMessage(getString(R.string.google_alert_message)) - builder.setPositiveButton(android.R.string.yes) { dialog, which -> - // Get all the account providers - val providers = IdentityProvider.getEnabledProviders(context) - - // Iterate over the account providers - for (idp in providers) { - val retrieveCallback = AuthorizationServiceConfiguration.RetrieveConfigurationCallback { serviceConfiguration, ex -> - if (ex == null && serviceConfiguration != null) { - makeAuthRequest(serviceConfiguration, idp) - } else if (!isNetworkAvailable()) { - Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show() - requireActivity().finish() - } else { - Toast.makeText(context, "Login failed, please try again later", Toast.LENGTH_LONG).show() - requireActivity().finish() - } - } - - if (idp.name == getString(R.string.google_name)) { - // Get configurations for the Google account provider - idp.retrieveConfig(context, retrieveCallback) - } - } - } - builder.setCancelable(false) - - val dialog = builder.create() - dialog.show() - } - else { - if (authState == null) { - val response = AuthorizationResponse.fromIntent(requireActivity().intent) - val ex = AuthorizationException.fromIntent(requireActivity().intent) - authState = AuthState(response, ex) - - if (response != null) { - exchangeAuthorizationCode(response) - } else if (!isNetworkAvailable()) { - Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show() - requireActivity().finish() - } else { - Toast.makeText(context, "Login failed, please try again later", Toast.LENGTH_LONG).show() - requireActivity().finish() - } - } - } - } - - return v.root - } - - private fun makeAuthRequest( - serviceConfig: AuthorizationServiceConfiguration, - idp: IdentityProvider) { - - if (!isNetworkAvailable()) { - Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show() - requireActivity().finish() - } - - val authRequest = AuthorizationRequest.Builder( - serviceConfig, - idp.clientId, - ResponseTypeValues.CODE, - idp.redirectUri) - .setScope(idp.scope) - .build() - - authorizationService?.performAuthorizationRequest( - authRequest, - createPostAuthorizationIntent( - requireContext(), - authRequest, - serviceConfig.discoveryDoc, - idp.clientSecret), - authorizationService?.createCustomTabsIntentBuilder()!! - .build()) - - requireActivity().setResult(Activity.RESULT_OK) - requireActivity().finish() - } - - private fun createPostAuthorizationIntent( - context: Context, - request: AuthorizationRequest, - discoveryDoc: AuthorizationServiceDiscovery?, - clientSecret: String?): PendingIntent { - val intent = Intent(context, LoginActivity::class.java) - - if (discoveryDoc != null) { - intent.putExtra(extraAuthServiceDiscovery, discoveryDoc.docJson.toString()) - } - - if (clientSecret != null) { - intent.putExtra(extraClientSecret, clientSecret) - } - - intent.putExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE, LoginActivity.ACCOUNT_PROVIDER_GOOGLE) - intent.putExtra(LoginActivity.ACCOUNT_PROVIDER_GOOGLE_AUTH_COMPLETE, true) - intent.putExtra(LoginActivity.OPEN_APP_PACKAGE_AFTER_AUTH, requireActivity().intent.getStringExtra(LoginActivity.OPEN_APP_PACKAGE_AFTER_AUTH)) - intent.putExtra(LoginActivity.OPEN_APP_ACTIVITY_AFTER_AUTH, requireActivity().intent.getStringExtra(LoginActivity.OPEN_APP_ACTIVITY_AFTER_AUTH)) - - var flag = 0 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - flag = flag or PendingIntent.FLAG_MUTABLE - } - - return PendingIntent.getActivity(context, request.hashCode(), intent, flag) - } - - private fun exchangeAuthorizationCode(authorizationResponse: AuthorizationResponse) { - if (!isNetworkAvailable()) { - Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show() - requireActivity().finish() - } - - val additionalParams = HashMap() - if (getClientSecretFromIntent(requireActivity().intent) != null) { - additionalParams["client_secret"] = getClientSecretFromIntent(requireActivity().intent) - } - performTokenRequest(authorizationResponse.createTokenExchangeRequest(additionalParams)) - } - - private fun getClientSecretFromIntent(intent: Intent): String? { - return if (!intent.hasExtra(extraClientSecret)) { - null - } - else intent.getStringExtra(extraClientSecret) - } - - - private fun performTokenRequest(request: TokenRequest) { - if (!isNetworkAvailable()) { - Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show() - requireActivity().finish() - } - - authorizationService?.performTokenRequest( - request, this) - } - - override fun onTokenRequestCompleted(response: TokenResponse?, ex: AuthorizationException?) { - authState?.update(response, ex) - - getAccountInfo() - } - - private fun getAccountInfo() { - if (!isNetworkAvailable()) { - Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show() - requireActivity().finish() - } - - val discoveryDoc = getDiscoveryDocFromIntent(requireActivity().intent) - - if (!authState!!.isAuthorized - || discoveryDoc == null - || discoveryDoc.userinfoEndpoint == null) { - Toast.makeText(context, "Login failed, please try again later", Toast.LENGTH_LONG).show() - requireActivity().finish() - } - else { - object : AsyncTask() { - override fun doInBackground(vararg params: Void): Void? { - if (fetchUserInfo()) { - Toast.makeText(context, "Login failed, please try again later", Toast.LENGTH_LONG).show() - requireActivity().finish() - } - - return null - } - }.execute() - } - } - - private fun getDiscoveryDocFromIntent(intent: Intent): AuthorizationServiceDiscovery? { - if (!intent.hasExtra(extraAuthServiceDiscovery)) { - return null - } - val discoveryJson = intent.getStringExtra(extraAuthServiceDiscovery) - try { - return AuthorizationServiceDiscovery(JSONObject(discoveryJson)) - } - catch (ex: JSONException) { - throw IllegalStateException("Malformed JSON in discovery doc") - } - catch (ex: AuthorizationServiceDiscovery.MissingArgumentException) { - throw IllegalStateException("Malformed JSON in discovery doc") - } - - } - - private fun fetchUserInfo(): Boolean { - var error = false - - if (authState!!.authorizationServiceConfiguration == null) { - return true - } - - authState!!.performActionWithFreshTokens(authorizationService!!, AuthState.AuthStateAction { accessToken, _, ex -> - if (ex != null) { - error = true - return@AuthStateAction - } - - val discoveryDoc = getDiscoveryDocFromIntent(requireActivity().intent) - ?: throw IllegalStateException("no available discovery doc") - - val userInfoEndpoint: URL - try { - userInfoEndpoint = URL(discoveryDoc.userinfoEndpoint!!.toString()) - } - catch (urlEx: MalformedURLException) { - error = true - return@AuthStateAction - } - - var userInfoResponse: InputStream? = null - try { - val conn = userInfoEndpoint.openConnection() as HttpURLConnection - conn.setRequestProperty("Authorization", "Bearer " + accessToken!!) - conn.instanceFollowRedirects = false - userInfoResponse = conn.inputStream - val response = readStream(userInfoResponse) - updateUserInfo(JSONObject(response)) - } - catch (ioEx: IOException) { - error = true - } - catch (jsonEx: JSONException) { - error = true - } - finally { - if (userInfoResponse != null) { - try { - userInfoResponse.close() - } - catch (ioEx: IOException) { - error = true - } - - } - } - }) - - return error - } - - @Throws(IOException::class) - private fun readStream(stream: InputStream?): String { - val br = BufferedReader(InputStreamReader(stream!!)) - val buffer = CharArray(bufferSize) - val sb = StringBuilder() - var readCount = br.read(buffer) - while (readCount != -1) { - sb.append(buffer, 0, readCount) - readCount = br.read(buffer) - } - return sb.toString() - } - - private fun updateUserInfo(jsonObject: JSONObject) { - Handler(Looper.getMainLooper()).post { - userInfoJson = jsonObject - onAccountInfoGotten() - } - } - - private fun onAccountInfoGotten() { - if (!isNetworkAvailable()) { - Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show() - requireActivity().finish() - } - - if (userInfoJson != null) { - try { - - var emailAddress = "" - if (userInfoJson!!.has("email")) { - emailAddress = userInfoJson!!.getString("email") - } - - if (validate(emailAddress, authState!!)) - requireFragmentManager().beginTransaction() - .replace(android.R.id.content, DetectConfigurationFragment(), null) - .addToBackStack(null) - .commit() - - } - catch (ex: JSONException) { - Toast.makeText(context, "Login failed, please try again later", Toast.LENGTH_LONG).show() - requireActivity().finish() - } - - } - else { - Toast.makeText(context, "Login failed, please try again later", Toast.LENGTH_LONG).show() - requireActivity().finish() - } - - } - - private fun validate(emailAddress: String, authState: AuthState): Boolean { - var valid = false - - fun validateUrl() { - model.baseUrlError.value = null - try { - val uri = URI("https://apidata.googleusercontent.com/caldav/v2/$emailAddress/user") - if (uri.scheme.equals("http", true) || uri.scheme.equals("https", true)) { - valid = true - loginModel.baseURI = uri - } else - model.baseUrlError.value = getString(R.string.login_url_must_be_http_or_https) - } catch (e: Exception) { - model.baseUrlError.value = e.localizedMessage - } - } - - when { - - model.loginWithUrlAndTokens.value == true -> { - validateUrl() - - model.usernameError.value = null - - if (loginModel.baseURI != null) { - valid = true - loginModel.credentials = Credentials(emailAddress, null, authState, null) - } - } - - } - - return valid - } - - override fun onDestroy() { - super.onDestroy() - authorizationService?.dispose() - } -} diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorModel.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorModel.kt deleted file mode 100644 index d77217537..000000000 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorModel.kt +++ /dev/null @@ -1,48 +0,0 @@ -package at.bitfire.davdroid.ui.setup - -import android.content.Intent -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel - -class GoogleAuthenticatorModel: ViewModel() { - - private var initialized = false - - val loginWithUrlAndTokens = MutableLiveData() - - val baseUrl = MutableLiveData() - val baseUrlError = MutableLiveData() - - val emailAddress = MutableLiveData() - val emailAddressError = MutableLiveData() - - val username = MutableLiveData() - val usernameError = MutableLiveData() - - val password = MutableLiveData() - val passwordError = MutableLiveData() - - val certificateAlias = MutableLiveData() - val certificateAliasError = MutableLiveData() - - init { - loginWithUrlAndTokens.value = true - } - - fun initialize(intent: Intent) { - if (initialized) - return - - // we've got initial login data - val givenUrl = intent.getStringExtra(LoginActivity.EXTRA_URL) - val givenUsername = intent.getStringExtra(LoginActivity.EXTRA_USERNAME) - val givenPassword = intent.getStringExtra(LoginActivity.EXTRA_PASSWORD) - - baseUrl.value = givenUrl - - password.value = givenPassword - - initialized = true - } - -} diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt index cfdcbceb7..819f72468 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt @@ -5,15 +5,9 @@ package at.bitfire.davdroid.ui.setup import android.os.Bundle -import android.text.Layout -import android.text.SpannableString -import android.text.style.AlignmentSpan -import android.util.Log import android.view.MenuItem -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -47,20 +41,19 @@ class LoginActivity: AppCompatActivity() { const val SETUP_ACCOUNT_PROVIDER_TYPE = "setup_account_provider_type" const val ACCOUNT_PROVIDER_EELO = "eelo" const val ACCOUNT_PROVIDER_GOOGLE = "google" - const val ACCOUNT_PROVIDER_GOOGLE_AUTH_COMPLETE = "google_auth_complete" + const val OPENID_AUTH_FLOW_COMPLETE = "openId_authFlow_complete" const val OPEN_APP_PACKAGE_AFTER_AUTH = "open_app_package_after_auth" const val OPEN_APP_ACTIVITY_AFTER_AUTH = "open_app_activity_after_auth" const val IGNORE_ACCOUNT_SETUP = "ignore_account_setup" - + const val RETRY_ON_401 = "retry_on_401" } @Inject lateinit var loginFragmentFactories: Map - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -79,19 +72,19 @@ class LoginActivity: AppCompatActivity() { when (intent.getStringExtra(SETUP_ACCOUNT_PROVIDER_TYPE)) { ACCOUNT_PROVIDER_EELO -> { supportFragmentManager.beginTransaction() - .replace(android.R.id.content, EeloAuthenticatorFragment()) - .commit() + .replace(android.R.id.content, EeloAuthenticatorFragment()) + .commit() } ACCOUNT_PROVIDER_GOOGLE -> { supportFragmentManager.beginTransaction() - .replace(android.R.id.content, GoogleAuthenticatorFragment()) - .commit() + .replace(android.R.id.content, GoogleAuthFragment()) + .commit() } else -> // first call, add first login fragment supportFragmentManager.beginTransaction() - .replace(android.R.id.content, fragment) - .commit() + .replace(android.R.id.content, fragment) + .commit() } } else Logger.log.severe("Couldn't create LoginFragment") diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt new file mode 100644 index 000000000..78f785ca1 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt @@ -0,0 +1,211 @@ +/* + * Copyright MURENA SAS 2023 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 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 . + */ + +package at.bitfire.davdroid.ui.setup + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import at.bitfire.davdroid.R +import at.bitfire.davdroid.authorization.IdentityProvider +import at.bitfire.davdroid.db.Credentials +import at.bitfire.davdroid.log.Logger +import at.bitfire.davdroid.ui.NetworkUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import net.openid.appauth.AuthState.AuthStateAction +import net.openid.appauth.AuthorizationException +import net.openid.appauth.AuthorizationResponse +import net.openid.appauth.AuthorizationService.TokenResponseCallback +import net.openid.appauth.AuthorizationServiceConfiguration +import org.json.JSONObject +import java.net.URI +import java.util.logging.Level + +abstract class OpenIdAuthenticationBaseFragment(private val identityProvider: IdentityProvider) : + Fragment() { + + private val viewModel by viewModels() + private val loginModel by activityViewModels() + + private val authReqActivityResultLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + val intent = result.data + intent?.let { + val response = AuthorizationResponse.fromIntent(it) + val exception = AuthorizationException.fromIntent(it) + + if (response == null || exception != null) { + Logger.log.log(Level.SEVERE, "Failed to retrieve auth response", exception) + handleLoginFailedToast() + return@let + } + + performTokenRequest(response, exception) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + viewModel.identityProvider = identityProvider + + return inflater.inflate(R.layout.frament_openid_auth, container, false) + } + + protected fun isAuthFlowComplete(): Boolean { + return activity?.intent?.getBooleanExtra( + LoginActivity.OPENID_AUTH_FLOW_COMPLETE, + false + ) == true + } + + protected fun startAuthFLow() { + if (isAuthFlowComplete()) { + return + } + + if (handleNoNetworkToast()) { + return + } + + viewModel.identityProvider?.retrieveConfig { serviceConfiguration, exception -> + if (exception != null || serviceConfiguration == null) { + Logger.log.log(Level.SEVERE, "failed to fetch configuration", exception) + handleLoginFailedToast() + return@retrieveConfig + } + + obtainAuthCode(serviceConfiguration) + } + } + + private fun obtainAuthCode(serviceConfiguration: AuthorizationServiceConfiguration) { + if (handleNoNetworkToast()) { + return + } + + val authIntent = viewModel.getAuthIntent(serviceConfiguration) + authReqActivityResultLauncher.launch(authIntent) + } + + private fun performTokenRequest( + authorizationResponse: AuthorizationResponse, + authorizationException: AuthorizationException? + ) { + if (handleNoNetworkToast()) { + return + } + + viewModel.performTokenRequest( + authorizationResponse, + authorizationException, + getTokenResponseCallback() + ) + } + + private fun getTokenResponseCallback(): TokenResponseCallback { + return TokenResponseCallback { response, exception -> + if (response == null || exception != null) { + Logger.log.log(Level.SEVERE, "failed to retrieve token", exception) + handleLoginFailedToast() + return@TokenResponseCallback + } + + viewModel.retrieveAccountInfo(response, exception, getAuthStateActionCallback()) + } + } + + private fun getAuthStateActionCallback(): AuthStateAction { + return AuthStateAction { accessToken, _, exception -> + if (exception != null || accessToken == null) { + Logger.log.log(Level.SEVERE, "failed to retrieve user info", exception) + handleLoginFailedToast() + return@AuthStateAction + } + + val infoEndpoint = viewModel.getUserInfoEndpoint() + if (infoEndpoint == null) { + handleLoginFailedToast() + return@AuthStateAction + } + + lifecycleScope.launch(Dispatchers.IO) { + val infoJson = viewModel.retrieveUserInfoFromServer(infoEndpoint, accessToken) + if (infoJson == null) { + lifecycleScope.launch(Dispatchers.Main) { + handleLoginFailedToast() + } + return@launch + } + + lifecycleScope.launch(Dispatchers.Main) { + onAuthenticationComplete(infoJson) + } + } + } + } + + private fun finishActivity() { + this@OpenIdAuthenticationBaseFragment.requireActivity().finish() + } + + private fun handleNoNetworkToast(): Boolean { + if (NetworkUtils.isConnectedToNetwork(requireContext())) { + return false + } + + Toast.makeText(requireContext(), R.string.no_internet_toast, Toast.LENGTH_LONG).show() + finishActivity() + return true + } + + protected fun handleLoginFailedToast() { + Toast.makeText(requireContext(), R.string.login_failed, Toast.LENGTH_LONG).show() + finishActivity() + } + + protected fun proceedNext(userName: String, baseUrl: String) { + activity?.intent?.putExtra(LoginActivity.OPENID_AUTH_FLOW_COMPLETE, true) + + val baseUri = URI(baseUrl) + loginModel.baseURI = baseUri + loginModel.credentials = Credentials( + userName, + null, + viewModel.getAuthState(), + null, + baseUri + ) //TODO: add clientSecret param in credential in future + + parentFragmentManager.beginTransaction() + .replace(android.R.id.content, DetectConfigurationFragment(), null) + .addToBackStack(null) + .commit() + } + + protected abstract fun onAuthenticationComplete(userData: JSONObject) +} diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationViewModel.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationViewModel.kt new file mode 100644 index 000000000..f29cec936 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationViewModel.kt @@ -0,0 +1,127 @@ +/* + * Copyright MURENA SAS 2023 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 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 . + */ + +package at.bitfire.davdroid.ui.setup + +import android.app.Application +import android.content.Intent +import androidx.annotation.WorkerThread +import androidx.lifecycle.AndroidViewModel +import at.bitfire.davdroid.authorization.IdentityProvider +import at.bitfire.davdroid.log.Logger +import net.openid.appauth.* +import net.openid.appauth.AuthState.AuthStateAction +import net.openid.appauth.AuthorizationService.TokenResponseCallback +import okio.buffer +import okio.source +import org.json.JSONObject +import java.net.HttpURLConnection +import java.net.URL +import java.nio.charset.StandardCharsets +import java.util.logging.Level + +class OpenIdAuthenticationViewModel(application: Application) : AndroidViewModel(application) { + + private val authorizationService: AuthorizationService = AuthorizationService(getApplication()) + private var authState: AuthState? = null + var identityProvider: IdentityProvider? = null + + override fun onCleared() { + authorizationService.dispose() + super.onCleared() + } + + fun getAuthState(): AuthState { + return authState!! + } + + fun getAuthIntent(serviceConfiguration: AuthorizationServiceConfiguration): Intent { + authState = AuthState(serviceConfiguration) + + val authRequestBuilder = AuthorizationRequest.Builder( + serviceConfiguration, + identityProvider!!.clientId, + ResponseTypeValues.CODE, + identityProvider!!.redirectUri + ) + + authRequestBuilder.setScopes(identityProvider!!.scope) + + return authorizationService.getAuthorizationRequestIntent(authRequestBuilder.build()) + } + + fun performTokenRequest( + authorizationResponse: AuthorizationResponse, + authorizationException: AuthorizationException?, + callback: TokenResponseCallback + ) { + authState?.update(authorizationResponse, authorizationException) + val request = authorizationResponse.createTokenExchangeRequest() + + if (identityProvider?.clientSecret != null) { + val clientAuth = ClientSecretBasic(identityProvider!!.clientSecret!!) + authorizationService.performTokenRequest(request, clientAuth, callback) + return + } + + authorizationService.performTokenRequest(request, callback) + } + + @WorkerThread + fun retrieveUserInfoFromServer(infoEndpoint: String, accessToken: String): JSONObject? { + try { + val userInfoEndpoint = URL(infoEndpoint) + val conn = userInfoEndpoint.openConnection() as HttpURLConnection + conn.setRequestProperty("Authorization", "Bearer $accessToken") + conn.instanceFollowRedirects = false + val response = conn.inputStream.source().buffer() + .readString(StandardCharsets.UTF_8) + conn.inputStream.close() + return JSONObject(response) + } catch (ex: Exception) { + Logger.log.log(Level.SEVERE, "failed to retrieve userInfo", ex) + } + + return null + } + + fun getUserInfoEndpoint(): String? { + var infoEndpoint = identityProvider?.userInfoEndpoint + + if (infoEndpoint == null) { + val discovery = authState?.authorizationServiceConfiguration?.discoveryDoc ?: return null + infoEndpoint = discovery.userinfoEndpoint.toString() + } + + return infoEndpoint + } + + fun retrieveAccountInfo( + response: TokenResponse, + exception: AuthorizationException?, + callback: AuthStateAction + ) { + authState?.update(response, exception) + + if (identityProvider?.clientSecret != null) { + val clientAuth = ClientSecretBasic(identityProvider!!.clientSecret!!) + authState?.performActionWithFreshTokens(authorizationService, clientAuth, callback) + return + } + + authState?.performActionWithFreshTokens(authorizationService, callback) + } +} diff --git a/app/src/main/res/layout/fragment_google_authenticator.xml b/app/src/main/res/layout/fragment_google_authenticator.xml deleted file mode 100644 index 1b3ed112c..000000000 --- a/app/src/main/res/layout/fragment_google_authenticator.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/frament_openid_auth.xml b/app/src/main/res/layout/frament_openid_auth.xml new file mode 100644 index 000000000..b4bd4feb5 --- /dev/null +++ b/app/src/main/res/layout/frament_openid_auth.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/app/src/main/res/values/account_providers_auth_config.xml b/app/src/main/res/values/account_providers_auth_config.xml deleted file mode 100644 index b3b10fdba..000000000 --- a/app/src/main/res/values/account_providers_auth_config.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Google - - 100496780587-pbiu5eudcjm6cge2phduc6mt8mgbsmsr.apps.googleusercontent.com - - https://accounts.google.com/.well-known/openid-configuration - - openid profile email https://www.googleapis.com/auth/carddav https://www.googleapis.com/auth/calendar https://mail.google.com/ - - - com.googleusercontent.apps.100496780587-pbiu5eudcjm6cge2phduc6mt8mgbsmsr:/oauth2redirect - - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 19d2a57c2..3217c8adf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -568,4 +568,7 @@ Service detection failed Failed to refresh collection list + + Login failed, please try again later + OK -- GitLab From ce7ab5726ab7bd89c9d01c266d041130ecdd398b Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Wed, 24 May 2023 19:54:10 +0600 Subject: [PATCH 02/11] Refactor syncAdapterServices various syncAdapter services have similar implementation. To make the implementation simple, we need to inherit these syncAdapter instead copy-pasting same code. --- .../CalendarsSyncAdapterService.kt | 62 +++++-- .../syncadapter/DummySyncAdapterService.kt | 47 +++++ .../EeloAppDataSyncAdapterService.kt | 29 +-- .../EeloCalendarsSyncAdapterService.kt | 175 +----------------- .../EeloEmailSyncAdapterService.kt | 30 +-- .../EeloMediaSyncAdapterService.kt | 29 +-- .../EeloMeteredEdriveSyncAdapterService.kt | 30 +-- .../EeloNotesSyncAdapterService.kt | 28 +-- .../EeloTasksSyncAdapterService.kt | 157 +--------------- .../syncadapter/EmailSyncAdapterService.kt | 47 +++++ .../GoogleAccountAuthenticatorService.kt | 11 +- .../GoogleCalendarsSyncAdapterService.kt | 173 +---------------- .../GoogleEmailSyncAdapterService.kt | 29 +-- .../GoogleTasksSyncAdapterService.kt | 156 +--------------- .../syncadapter/TasksSyncAdapterService.kt | 6 +- 15 files changed, 159 insertions(+), 850 deletions(-) create mode 100644 app/src/main/java/at/bitfire/davdroid/syncadapter/DummySyncAdapterService.kt create mode 100644 app/src/main/java/at/bitfire/davdroid/syncadapter/EmailSyncAdapterService.kt diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt index 503a7b4b1..4770f5473 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt @@ -28,17 +28,23 @@ import kotlin.collections.component1 import kotlin.collections.component2 import kotlin.collections.set -class CalendarsSyncAdapterService: SyncAdapterService() { +open class CalendarsSyncAdapterService : SyncAdapterService() { override fun syncAdapter() = CalendarsSyncAdapter(this, appDatabase) - - class CalendarsSyncAdapter( + class CalendarsSyncAdapter( context: Context, appDatabase: AppDatabase ) : SyncAdapter(context, appDatabase) { - override fun sync(account: Account, extras: Bundle, authority: String, httpClient: Lazy, provider: ContentProviderClient, syncResult: SyncResult) { + override fun sync( + account: Account, + extras: Bundle, + authority: String, + httpClient: Lazy, + provider: ContentProviderClient, + syncResult: SyncResult + ) { try { val accountSettings = AccountSettings(context, account) @@ -46,7 +52,10 @@ class CalendarsSyncAdapterService: SyncAdapterService() { - sync conditions (e.g. "sync only in WiFi") are not met AND - this is is an automatic sync (i.e. manual syncs are run regardless of sync conditions) */ - if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(accountSettings)) + if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions( + accountSettings + ) + ) return if (accountSettings.getEventColors()) @@ -58,11 +67,26 @@ class CalendarsSyncAdapterService: SyncAdapterService() { val priorityCalendars = priorityCollections(extras) val calendars = AndroidCalendar - .find(account, provider, LocalCalendar.Factory, "${CalendarContract.Calendars.SYNC_EVENTS}!=0", null) - .sortedByDescending { priorityCalendars.contains(it.id) } + .find( + account, + provider, + LocalCalendar.Factory, + "${CalendarContract.Calendars.SYNC_EVENTS}!=0", + null + ) + .sortedByDescending { priorityCalendars.contains(it.id) } for (calendar in calendars) { Logger.log.info("Synchronizing calendar #${calendar.id}, URL: ${calendar.name}") - CalendarSyncManager(context, account, accountSettings, extras, httpClient.value, authority, syncResult, calendar).let { + CalendarSyncManager( + context, + account, + accountSettings, + extras, + httpClient.value, + authority, + syncResult, + calendar + ).let { val authState = accountSettings.credentials().authState if (authState != null) { if (authState.needsTokenRefresh) { @@ -88,26 +112,30 @@ class CalendarsSyncAdapterService: SyncAdapterService() { ) object : AsyncTask() { override fun doInBackground(vararg params: Void): Void? { - it.performSync() + it.performSyncWithRetry() return null } }.execute() } } else { - it.performSync() + it.performSyncWithRetry() } } else { - it.performSync() + it.performSyncWithRetry() } } } - } catch(e: Exception) { + } catch (e: Exception) { Logger.log.log(Level.SEVERE, "Couldn't sync calendars", e) } Logger.log.info("Calendar sync complete") } - private fun updateLocalCalendars(provider: ContentProviderClient, account: Account, settings: AccountSettings) { + private fun updateLocalCalendars( + provider: ContentProviderClient, + account: Account, + settings: AccountSettings + ) { val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) val remoteCalendars = mutableMapOf() @@ -118,7 +146,13 @@ class CalendarsSyncAdapterService: SyncAdapterService() { // delete/update local calendars val updateColors = settings.getManageCalendarColors() - for (calendar in AndroidCalendar.find(account, provider, LocalCalendar.Factory, null, null)) + for (calendar in AndroidCalendar.find( + account, + provider, + LocalCalendar.Factory, + null, + null + )) calendar.name?.let { val url = it.toHttpUrl() val info = remoteCalendars[url] diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/DummySyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/DummySyncAdapterService.kt new file mode 100644 index 000000000..7a11d81a4 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/DummySyncAdapterService.kt @@ -0,0 +1,47 @@ +/* + * Copyright MURENA SAS 2023 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 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 . + */ + +package at.bitfire.davdroid.syncadapter + +import android.accounts.Account +import android.content.ContentProviderClient +import android.content.Context +import android.content.SyncResult +import android.os.Bundle +import at.bitfire.davdroid.HttpClient +import at.bitfire.davdroid.db.AppDatabase + +abstract class DummySyncAdapterService : SyncAdapterService() { + + override fun syncAdapter() = DummySyncAdapter(this, appDatabase) + + + class DummySyncAdapter( + context: Context, + db: AppDatabase + ) : SyncAdapter(context, db) { + override fun sync( + account: Account, + extras: Bundle, + authority: String, + httpClient: Lazy, + provider: ContentProviderClient, + syncResult: SyncResult + ) { + // Unused + } + } +} diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAppDataSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAppDataSyncAdapterService.kt index 0136139b6..b5b1db95a 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAppDataSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAppDataSyncAdapterService.kt @@ -16,31 +16,4 @@ package at.bitfire.davdroid.syncadapter -import android.accounts.Account -import android.content.* -import android.os.Bundle -import at.bitfire.davdroid.HttpClient -import at.bitfire.davdroid.db.AppDatabase - -class EeloAppDataSyncAdapterService : SyncAdapterService() { - - override fun syncAdapter() = EeloAppDataSyncAdapter(this, appDatabase) - - class EeloAppDataSyncAdapter( - context: Context, - db: AppDatabase - ): SyncAdapter(context, db) { - - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - // Unused - } - } -} - +class EeloAppDataSyncAdapterService : DummySyncAdapterService() \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloCalendarsSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloCalendarsSyncAdapterService.kt index 60ba1088c..a9367e363 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloCalendarsSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloCalendarsSyncAdapterService.kt @@ -16,177 +16,4 @@ package at.bitfire.davdroid.syncadapter -import android.accounts.Account -import android.content.ContentProviderClient -import android.content.ContentResolver -import android.content.Context -import android.content.SyncResult -import android.os.AsyncTask -import android.os.Bundle -import android.provider.CalendarContract -import at.bitfire.davdroid.HttpClient -import at.bitfire.davdroid.db.AppDatabase -import at.bitfire.davdroid.db.Collection -import at.bitfire.davdroid.db.Credentials -import at.bitfire.davdroid.db.Service -import at.bitfire.davdroid.log.Logger -import at.bitfire.davdroid.resource.LocalCalendar -import at.bitfire.davdroid.settings.AccountSettings -import at.bitfire.ical4android.AndroidCalendar -import net.openid.appauth.AuthorizationService -import okhttp3.HttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import java.util.logging.Level - -class EeloCalendarsSyncAdapterService : SyncAdapterService() { - - override fun syncAdapter() = CalendarsSyncAdapter(this, appDatabase) - - class CalendarsSyncAdapter( - context: Context, - appDatabase: AppDatabase - ) : SyncAdapter(context, appDatabase) { - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - try { - val accountSettings = AccountSettings(context, account) - - /* don't run sync if - - sync conditions (e.g. "sync only in WiFi") are not met AND - - this is is an automatic sync (i.e. manual syncs are run regardless of sync conditions) - */ - if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions( - accountSettings - ) - ) - return - - if (accountSettings.getEventColors()) - AndroidCalendar.insertColors(provider, account) - else - AndroidCalendar.removeColors(provider, account) - - updateLocalCalendars(provider, account, accountSettings) - - val priorityCalendars = priorityCollections(extras) - val calendars = AndroidCalendar - .find( - account, - provider, - LocalCalendar.Factory, - "${CalendarContract.Calendars.SYNC_EVENTS}!=0", - null - ) - .sortedByDescending { priorityCalendars.contains(it.id) } - for (calendar in calendars) { - Logger.log.info("Synchronizing calendar #${calendar.id}, URL: ${calendar.name}") - CalendarSyncManager( - context, - account, - accountSettings, - extras, - httpClient.value, - authority, - syncResult, - calendar - ).let { - val authState = accountSettings.credentials().authState - if (authState != null) { - if (authState.needsTokenRefresh) { - val tokenRequest = authState.createTokenRefreshRequest() - - AuthorizationService(context).performTokenRequest(tokenRequest) { tokenResponse, ex -> - authState.update(tokenResponse, ex) - accountSettings.credentials( - Credentials( - account.name, - null, - authState, - null - ) - ) - it.accountSettings.credentials( - Credentials( - it.account.name, - null, - authState, - null - ) - ) - object : AsyncTask() { - override fun doInBackground(vararg params: Void): Void? { - it.performSyncWithRetry() - return null - } - }.execute() - } - } else { - it.performSyncWithRetry() - } - } else { - it.performSyncWithRetry() - } - } - } - } catch (e: Exception) { - Logger.log.log(Level.SEVERE, "Couldn't sync calendars", e) - } - Logger.log.info("Calendar sync complete") - } - - private fun updateLocalCalendars( - provider: ContentProviderClient, - account: Account, - settings: AccountSettings - ) { - val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) - - val remoteCalendars = mutableMapOf() - - if (service != null) { - for (collection in db.collectionDao().getSyncCalendars(service.id)) { - remoteCalendars[collection.url] = collection - } - } - - // delete/update local calendars - val updateColors = settings.getManageCalendarColors() - for (calendar in AndroidCalendar.find( - account, - provider, - LocalCalendar.Factory, - null, - null - )) - calendar.name?.let { - val url = it.toHttpUrlOrNull()!! - val info = remoteCalendars[url] - - if (info == null) { - Logger.log.log(Level.INFO, "Deleting obsolete local calendar", url) - calendar.delete() - } else { - // remote CollectionInfo found for this local collection, update data - Logger.log.log(Level.FINE, "Updating local calendar $url", info) - calendar.update(info, updateColors) - // we already have a local calendar for this remote collection, don't take into consideration anymore - remoteCalendars -= url - } - } - - // create new local calendars - for ((_, info) in remoteCalendars) { - Logger.log.log(Level.INFO, "Adding local calendar", info) - LocalCalendar.create(account, provider, info) - } - } - - } -} - +class EeloCalendarsSyncAdapterService : CalendarsSyncAdapterService() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloEmailSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloEmailSyncAdapterService.kt index c9d955939..3575ab895 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloEmailSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloEmailSyncAdapterService.kt @@ -16,32 +16,4 @@ package at.bitfire.davdroid.syncadapter -import android.accounts.Account -import android.content.* -import android.os.Bundle -import at.bitfire.davdroid.HttpClient -import at.bitfire.davdroid.MailAccountSyncHelper -import at.bitfire.davdroid.db.AppDatabase - -class EeloEmailSyncAdapterService : SyncAdapterService() { - - override fun syncAdapter() = EeloEmailSyncAdapter(this, appDatabase) - - - class EeloEmailSyncAdapter( - context: Context, - db: AppDatabase - ): SyncAdapter(context, db) { - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - MailAccountSyncHelper.syncMailAccounts(context.applicationContext) - } - } -} - +class EeloEmailSyncAdapterService : EmailSyncAdapterService() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMediaSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMediaSyncAdapterService.kt index e7af69c3e..75d11b05a 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMediaSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMediaSyncAdapterService.kt @@ -16,31 +16,4 @@ package at.bitfire.davdroid.syncadapter -import android.accounts.Account -import android.content.* -import android.os.Bundle -import at.bitfire.davdroid.HttpClient -import at.bitfire.davdroid.db.AppDatabase - -class EeloMediaSyncAdapterService : SyncAdapterService() { - - override fun syncAdapter() = EeloMediaSyncAdapter(this, appDatabase) - - - class EeloMediaSyncAdapter( - context: Context, - db: AppDatabase - ): SyncAdapter(context, db) { - - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - // Unused - } - } -} +class EeloMediaSyncAdapterService : DummySyncAdapterService() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMeteredEdriveSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMeteredEdriveSyncAdapterService.kt index 2702a7a4c..8277877bb 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMeteredEdriveSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMeteredEdriveSyncAdapterService.kt @@ -16,32 +16,4 @@ package at.bitfire.davdroid.syncadapter -import android.accounts.Account -import android.content.ContentProviderClient -import android.content.Context -import android.content.SyncResult -import android.os.Bundle -import at.bitfire.davdroid.HttpClient -import at.bitfire.davdroid.db.AppDatabase - -class EeloMeteredEdriveSyncAdapterService : SyncAdapterService() { - - override fun syncAdapter() = EeloMeteredEdriveSyncAdapter(this, appDatabase) - - class EeloMeteredEdriveSyncAdapter( - context: Context, - db: AppDatabase - ): SyncAdapter(context, db) { - - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - // Unused - } - } -} \ No newline at end of file +class EeloMeteredEdriveSyncAdapterService : DummySyncAdapterService() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloNotesSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloNotesSyncAdapterService.kt index 86a1daa89..c06204177 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloNotesSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloNotesSyncAdapterService.kt @@ -16,30 +16,4 @@ package at.bitfire.davdroid.syncadapter -import android.accounts.Account -import android.content.* -import android.os.Bundle -import at.bitfire.davdroid.HttpClient -import at.bitfire.davdroid.db.AppDatabase - -class EeloNotesSyncAdapterService : SyncAdapterService() { - - override fun syncAdapter() = EeloNotesSyncAdapter(this, appDatabase) - - class EeloNotesSyncAdapter( - context: Context, - db: AppDatabase - ): SyncAdapter(context, db) { - - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - // Unused - } - } -} +class EeloNotesSyncAdapterService : DummySyncAdapterService() \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloTasksSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloTasksSyncAdapterService.kt index 7afed9507..acd197cd0 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloTasksSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloTasksSyncAdapterService.kt @@ -45,159 +45,4 @@ import java.util.logging.Level /** * Synchronization manager for CalDAV collections; handles tasks ({@code VTODO}). */ -class EeloTasksSyncAdapterService : SyncAdapterService() { - - override fun syncAdapter() = TasksSyncAdapter(this, appDatabase) - - - class TasksSyncAdapter( - context: Context, - appDatabase: AppDatabase - ) : SyncAdapter(context, appDatabase) { - - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - try { - val providerName = TaskProvider.ProviderName.fromAuthority(authority) - val taskProvider = TaskProvider.fromProviderClient(context, providerName, provider) - - // make sure account can be seen by OpenTasks - if (Build.VERSION.SDK_INT >= 26) - AccountManager.get(context).setAccountVisibility( - account, - taskProvider.name.packageName, - AccountManager.VISIBILITY_VISIBLE - ) - - val accountSettings = AccountSettings(context, account) - /* don't run sync if - - sync conditions (e.g. "sync only in WiFi") are not met AND - - this is is an automatic sync (i.e. manual syncs are run regardless of sync conditions) - */ - if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions( - accountSettings - ) - ) - return - - updateLocalTaskLists(taskProvider, account, accountSettings) - - val priorityTaskLists = priorityCollections(extras) - val taskLists = AndroidTaskList - .find( - account, - taskProvider, - LocalTaskList.Factory, - "${TaskContract.TaskLists.SYNC_ENABLED}!=0", - null - ) - .sortedByDescending { priorityTaskLists.contains(it.id) } - for (taskList in taskLists) { - Logger.log.info("Synchronizing task list #${taskList.id} [${taskList.syncId}]") - TasksSyncManager( - context, - account, - accountSettings, - httpClient.value, - extras, - authority, - syncResult, - taskList - ).let { - val authState = accountSettings.credentials().authState - if (authState != null) { - if (authState.needsTokenRefresh) { - val tokenRequest = authState.createTokenRefreshRequest() - - AuthorizationService(context).performTokenRequest(tokenRequest, - AuthorizationService.TokenResponseCallback { tokenResponse, ex -> - authState.update(tokenResponse, ex) - accountSettings.credentials( - Credentials( - account.name, - null, - authState, - null - ) - ) - it.accountSettings.credentials( - Credentials( - it.account.name, - null, - authState, - null - ) - ) - object : AsyncTask() { - override fun doInBackground(vararg params: Void): Void? { - it.performSyncWithRetry() - return null - } - }.execute() - }) - } else { - it.performSyncWithRetry() - } - } else { - it.performSyncWithRetry() - } - } - } - } catch (e: TaskProvider.ProviderTooOldException) { - SyncUtils.notifyProviderTooOld(context, e) - syncResult.databaseError = true - } catch (e: Exception) { - Logger.log.log(Level.SEVERE, "Couldn't sync task lists", e) - syncResult.databaseError = true - } - - Logger.log.info("Task sync complete") - } - - private fun updateLocalTaskLists( - provider: TaskProvider, - account: Account, - settings: AccountSettings - ) { - val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) - - val remoteTaskLists = mutableMapOf() - if (service != null) - for (collection in db.collectionDao().getSyncTaskLists(service.id)) { - remoteTaskLists[collection.url] = collection - } - - // delete/update local task lists - val updateColors = settings.getManageCalendarColors() - - for (list in AndroidTaskList.find(account, provider, LocalTaskList.Factory, null, null)) - list.syncId?.let { - val url = it.toHttpUrlOrNull()!! - val info = remoteTaskLists[url] - if (info == null) { - Logger.log.fine("Deleting obsolete local task list $url") - list.delete() - } else { - // remote CollectionInfo found for this local collection, update data - Logger.log.log(Level.FINE, "Updating local task list $url", info) - list.update(info, updateColors) - // we already have a local task list for this remote collection, don't take into consideration anymore - remoteTaskLists -= url - } - } - - // create new local task lists - for ((_, info) in remoteTaskLists) { - Logger.log.log(Level.INFO, "Adding local task list", info) - LocalTaskList.create(account, provider, info) - } - } - - } -} +class EeloTasksSyncAdapterService : TasksSyncAdapterService() \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EmailSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EmailSyncAdapterService.kt new file mode 100644 index 000000000..68ea74fb5 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EmailSyncAdapterService.kt @@ -0,0 +1,47 @@ +/* + * Copyright MURENA SAS 2023 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 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 . + */ + +package at.bitfire.davdroid.syncadapter + +import android.accounts.Account +import android.content.ContentProviderClient +import android.content.Context +import android.content.SyncResult +import android.os.Bundle +import at.bitfire.davdroid.HttpClient +import at.bitfire.davdroid.MailAccountSyncHelper +import at.bitfire.davdroid.db.AppDatabase + +abstract class EmailSyncAdapterService : SyncAdapterService() { + + override fun syncAdapter() = EmailSyncAdapter(this, appDatabase) + + class EmailSyncAdapter( + context: Context, + db: AppDatabase + ) : SyncAdapter(context, db) { + override fun sync( + account: Account, + extras: Bundle, + authority: String, + httpClient: Lazy, + provider: ContentProviderClient, + syncResult: SyncResult + ) { + MailAccountSyncHelper.syncMailAccounts(context.applicationContext) + } + } +} diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt index c5752de8f..44b219a79 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt @@ -173,14 +173,11 @@ class GoogleAccountAuthenticatorService : Service(), OnAccountsUpdateListener { account: Account?, authTokenType: String?, options: Bundle? - ): Bundle { + ): Bundle? { val accountManager = AccountManager.get(context) - val authState = AuthState.jsonDeserialize( - accountManager.getUserData( - account, - AccountSettings.KEY_AUTH_STATE - ) - ) + val authStateString = accountManager.getUserData(account, AccountSettings.KEY_AUTH_STATE) ?: return null + + val authState = AuthState.jsonDeserialize(authStateString) if (authState != null) { if (authState.needsTokenRefresh) { diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleCalendarsSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleCalendarsSyncAdapterService.kt index 95c8d5fcd..4c5745a64 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleCalendarsSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleCalendarsSyncAdapterService.kt @@ -16,175 +16,4 @@ package at.bitfire.davdroid.syncadapter -import android.accounts.Account -import android.content.ContentProviderClient -import android.content.ContentResolver -import android.content.Context -import android.content.SyncResult -import android.os.AsyncTask -import android.os.Bundle -import android.provider.CalendarContract -import at.bitfire.davdroid.HttpClient -import at.bitfire.davdroid.db.AppDatabase -import at.bitfire.davdroid.db.Collection -import at.bitfire.davdroid.db.Credentials -import at.bitfire.davdroid.db.Service -import at.bitfire.davdroid.log.Logger -import at.bitfire.davdroid.resource.LocalCalendar -import at.bitfire.davdroid.settings.AccountSettings -import at.bitfire.ical4android.AndroidCalendar -import net.openid.appauth.AuthorizationService -import okhttp3.HttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import java.util.logging.Level - -class GoogleCalendarsSyncAdapterService : SyncAdapterService() { - - override fun syncAdapter() = CalendarsSyncAdapter(this, appDatabase) - - - class CalendarsSyncAdapter( - context: Context, - db: AppDatabase - ) : SyncAdapter(context, db) { - - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - try { - val accountSettings = AccountSettings(context, account) - - /* don't run sync if - - sync conditions (e.g. "sync only in WiFi") are not met AND - - this is is an automatic sync (i.e. manual syncs are run regardless of sync conditions) - */ - if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions( - accountSettings - ) - ) - return - - if (accountSettings.getEventColors()) - AndroidCalendar.insertColors(provider, account) - else - AndroidCalendar.removeColors(provider, account) - - updateLocalCalendars(provider, account, accountSettings) - - val priorityCalendars = priorityCollections(extras) - val calendars = AndroidCalendar - .find( - account, - provider, - LocalCalendar.Factory, - "${CalendarContract.Calendars.SYNC_EVENTS}!=0", - null - ) - .sortedByDescending { priorityCalendars.contains(it.id) } - for (calendar in calendars) { - Logger.log.info("Synchronizing calendar #${calendar.id}, URL: ${calendar.name}") - CalendarSyncManager( - context, - account, - accountSettings, - extras, - httpClient.value, - authority, - syncResult, - calendar - ).let { - val authState = accountSettings.credentials().authState - if (authState != null) { - if (authState.needsTokenRefresh) { - val tokenRequest = authState.createTokenRefreshRequest() - - AuthorizationService(context).performTokenRequest(tokenRequest, - AuthorizationService.TokenResponseCallback { tokenResponse, ex -> - authState.update(tokenResponse, ex) - accountSettings.credentials( - Credentials( - account.name, - null, - authState, - null - ) - ) - it.accountSettings.credentials( - Credentials( - it.account.name, - null, - authState, - null - ) - ) - object : AsyncTask() { - override fun doInBackground(vararg params: Void): Void? { - it.performSyncWithRetry() - return null - } - }.execute() - }) - } else { - it.performSyncWithRetry() - } - } else { - it.performSyncWithRetry() - } - } - } - } catch (e: Exception) { - Logger.log.log(Level.SEVERE, "Couldn't sync calendars", e) - } - Logger.log.info("Calendar sync complete") - } - - private fun updateLocalCalendars( - provider: ContentProviderClient, - account: Account, - settings: AccountSettings - ) { - val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) - - val remoteCalendars = mutableMapOf() - if (service != null) - for (collection in db.collectionDao().getSyncCalendars(service.id)) { - remoteCalendars[collection.url] = collection - } - - // delete/update local calendars - val updateColors = settings.getManageCalendarColors() - for (calendar in AndroidCalendar.find( - account, - provider, - LocalCalendar.Factory, - null, - null - )) - calendar.name?.let { - val url = it.toHttpUrlOrNull()!! - val info = remoteCalendars[url] - if (info == null) { - Logger.log.log(Level.INFO, "Deleting obsolete local calendar", url) - calendar.delete() - } else { - // remote CollectionInfo found for this local collection, update data - Logger.log.log(Level.FINE, "Updating local calendar $url", info) - calendar.update(info, updateColors) - // we already have a local calendar for this remote collection, don't take into consideration anymore - remoteCalendars -= url - } - } - - // create new local calendars - for ((_, info) in remoteCalendars) { - Logger.log.log(Level.INFO, "Adding local calendar", info) - LocalCalendar.create(account, provider, info) - } - } - } -} +class GoogleCalendarsSyncAdapterService : CalendarsSyncAdapterService() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleEmailSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleEmailSyncAdapterService.kt index 03d838817..fae1bcdcd 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleEmailSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleEmailSyncAdapterService.kt @@ -16,31 +16,4 @@ package at.bitfire.davdroid.syncadapter -import android.accounts.Account -import android.content.* -import android.os.Bundle -import at.bitfire.davdroid.HttpClient -import at.bitfire.davdroid.MailAccountSyncHelper -import at.bitfire.davdroid.db.AppDatabase - -class GoogleEmailSyncAdapterService : SyncAdapterService() { - - override fun syncAdapter() = GoogleEmailSyncAdapter(this, appDatabase) - - class GoogleEmailSyncAdapter( - context: Context, - db: AppDatabase - ): SyncAdapter(context, db) { - - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - MailAccountSyncHelper.syncMailAccounts(context.applicationContext) - } - } -} \ No newline at end of file +class GoogleEmailSyncAdapterService : EmailSyncAdapterService() \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleTasksSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleTasksSyncAdapterService.kt index 66e180fb8..082d3fd1d 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleTasksSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleTasksSyncAdapterService.kt @@ -44,158 +44,4 @@ import java.util.logging.Level /** * Synchronization manager for CalDAV collections; handles tasks ({@code VTODO}). */ -class GoogleTasksSyncAdapterService : SyncAdapterService() { - - override fun syncAdapter() = TasksSyncAdapter(this, appDatabase) - - - class TasksSyncAdapter( - context: Context, - db: AppDatabase - ) : SyncAdapter(context, db) { - - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - try { - val providerName = TaskProvider.ProviderName.fromAuthority(authority) - val taskProvider = TaskProvider.fromProviderClient(context, providerName, provider) - - // make sure account can be seen by OpenTasks - if (Build.VERSION.SDK_INT >= 26) - AccountManager.get(context).setAccountVisibility( - account, - taskProvider.name.packageName, - AccountManager.VISIBILITY_VISIBLE - ) - - val accountSettings = AccountSettings(context, account) - /* don't run sync if - - sync conditions (e.g. "sync only in WiFi") are not met AND - - this is is an automatic sync (i.e. manual syncs are run regardless of sync conditions) - */ - if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions( - accountSettings - ) - ) - return - - updateLocalTaskLists(taskProvider, account, accountSettings) - - val priorityTaskLists = priorityCollections(extras) - val taskLists = AndroidTaskList - .find( - account, - taskProvider, - LocalTaskList.Factory, - "${TaskContract.TaskLists.SYNC_ENABLED}!=0", - null - ) - .sortedByDescending { priorityTaskLists.contains(it.id) } - for (taskList in taskLists) { - Logger.log.info("Synchronizing task list #${taskList.id} [${taskList.syncId}]") - TasksSyncManager( - context, - account, - accountSettings, - httpClient.value, - extras, - authority, - syncResult, - taskList - ).let { - val authState = accountSettings.credentials().authState - if (authState != null) { - if (authState.needsTokenRefresh) { - val tokenRequest = authState.createTokenRefreshRequest() - - AuthorizationService(context).performTokenRequest(tokenRequest, - AuthorizationService.TokenResponseCallback { tokenResponse, ex -> - authState.update(tokenResponse, ex) - accountSettings.credentials( - Credentials( - account.name, - null, - authState, - null - ) - ) - it.accountSettings.credentials( - Credentials( - it.account.name, - null, - authState, - null - ) - ) - object : AsyncTask() { - override fun doInBackground(vararg params: Void): Void? { - it.performSyncWithRetry() - return null - } - }.execute() - }) - } else { - it.performSyncWithRetry() - } - } else { - it.performSyncWithRetry() - } - } - } - } catch (e: TaskProvider.ProviderTooOldException) { - SyncUtils.notifyProviderTooOld(context, e) - syncResult.databaseError = true - } catch (e: Exception) { - Logger.log.log(Level.SEVERE, "Couldn't sync task lists", e) - syncResult.databaseError = true - } - - Logger.log.info("Task sync complete") - } - - private fun updateLocalTaskLists( - provider: TaskProvider, - account: Account, - settings: AccountSettings - ) { - val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) - - val remoteTaskLists = mutableMapOf() - if (service != null) - for (collection in db.collectionDao().getSyncTaskLists(service.id)) { - remoteTaskLists[collection.url] = collection - } - - // delete/update local task lists - val updateColors = settings.getManageCalendarColors() - - for (list in AndroidTaskList.find(account, provider, LocalTaskList.Factory, null, null)) - list.syncId?.let { - val url = it.toHttpUrlOrNull()!! - val info = remoteTaskLists[url] - if (info == null) { - Logger.log.fine("Deleting obsolete local task list $url") - list.delete() - } else { - // remote CollectionInfo found for this local collection, update data - Logger.log.log(Level.FINE, "Updating local task list $url", info) - list.update(info, updateColors) - // we already have a local task list for this remote collection, don't take into consideration anymore - remoteTaskLists -= url - } - } - - // create new local task lists - for ((_, info) in remoteTaskLists) { - Logger.log.log(Level.INFO, "Adding local task list", info) - LocalTaskList.create(account, provider, info) - } - } - } -} +class GoogleTasksSyncAdapterService : TasksSyncAdapterService() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.kt index 3c5f262e6..b3b7b0b65 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.kt @@ -99,16 +99,16 @@ open class TasksSyncAdapterService: SyncAdapterService() { ) object : AsyncTask() { override fun doInBackground(vararg params: Void): Void? { - it.performSync() + it.performSyncWithRetry() return null } }.execute() } } else { - it.performSync() + it.performSyncWithRetry() } } else { - it.performSync() + it.performSyncWithRetry() } } } -- GitLab From 28a437c24847d141306341b14dbe2987f155bc3b Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Wed, 24 May 2023 21:05:15 +0600 Subject: [PATCH 03/11] Add clientSecret support for openId accounts --- .../java/at/bitfire/davdroid/OpenIdUtils.kt | 32 +++++++++++++++++++ .../at/bitfire/davdroid/db/Credentials.kt | 13 ++++---- .../davdroid/settings/AccountSettings.kt | 9 +++++- .../CalendarsSyncAdapterService.kt | 11 +++++-- .../syncadapter/ContactsSyncAdapterService.kt | 11 +++++-- .../GoogleAccountAuthenticatorService.kt | 5 ++- .../syncadapter/TasksSyncAdapterService.kt | 11 +++++-- .../setup/OpenIdAuthenticationBaseFragment.kt | 5 +-- 8 files changed, 78 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/at/bitfire/davdroid/OpenIdUtils.kt diff --git a/app/src/main/java/at/bitfire/davdroid/OpenIdUtils.kt b/app/src/main/java/at/bitfire/davdroid/OpenIdUtils.kt new file mode 100644 index 000000000..f2f9e0de9 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/OpenIdUtils.kt @@ -0,0 +1,32 @@ +/* + * Copyright MURENA SAS 2023 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 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 . + */ + +package at.bitfire.davdroid + +import net.openid.appauth.ClientAuthentication +import net.openid.appauth.ClientSecretBasic +import net.openid.appauth.NoClientAuthentication + +object OpenIdUtils { + + fun getClientAuthentication(secret: String?): ClientAuthentication { + if (secret == null) { + return NoClientAuthentication.INSTANCE + } + + return ClientSecretBasic(secret) + } +} diff --git a/app/src/main/java/at/bitfire/davdroid/db/Credentials.kt b/app/src/main/java/at/bitfire/davdroid/db/Credentials.kt index 45af12750..6c9b34ae8 100644 --- a/app/src/main/java/at/bitfire/davdroid/db/Credentials.kt +++ b/app/src/main/java/at/bitfire/davdroid/db/Credentials.kt @@ -8,11 +8,12 @@ import net.openid.appauth.AuthState import java.net.URI data class Credentials( - val userName: String? = null, - val password: String? = null, - val authState: AuthState? = null, - val certificateAlias: String? = null, - val serverUri: URI? = null + val userName: String? = null, + val password: String? = null, + val authState: AuthState? = null, + val certificateAlias: String? = null, + val serverUri: URI? = null, + val clientSecret: String? = null ) { override fun toString(): String { @@ -20,4 +21,4 @@ data class Credentials( return "Credentials(userName=$userName, password=$maskedPassword, certificateAlias=$certificateAlias)" } -} \ No newline at end of file +} diff --git a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt index eb618d92b..c9af2758c 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt @@ -93,6 +93,7 @@ class AccountSettings( const val KEY_USERNAME = "user_name" const val KEY_EMAIL_ADDRESS = "email_address" const val KEY_AUTH_STATE = "auth_state" + const val KEY_CLIENT_SECRET = "client_secret" const val KEY_CERTIFICATE_ALIAS = "certificate_alias" const val KEY_WIFI_ONLY = "wifi_only" // sync on WiFi only (default: false) @@ -155,6 +156,10 @@ class AccountSettings( if (credentials.authState != null) { bundle.putString(KEY_AUTH_STATE, credentials.authState.jsonSerializeString()) } + + if (credentials.clientSecret != null) { + bundle.putString(KEY_CLIENT_SECRET, credentials.clientSecret) + } } if (!baseURL.isNullOrEmpty()) { @@ -258,7 +263,8 @@ class AccountSettings( accountManager.getUserData(account, KEY_USERNAME), accountManager.getPassword(account), AuthState.jsonDeserialize(accountManager.getUserData(account, KEY_AUTH_STATE)), - accountManager.getUserData(account, KEY_CERTIFICATE_ALIAS) + accountManager.getUserData(account, KEY_CERTIFICATE_ALIAS), + clientSecret = accountManager.getUserData(account, KEY_CLIENT_SECRET) ) } } @@ -274,6 +280,7 @@ class AccountSettings( accountManager.setPassword(account, credentials.password) accountManager.setUserData(account, KEY_AUTH_STATE, credentials.authState.jsonSerializeString()) accountManager.setUserData(account, KEY_CERTIFICATE_ALIAS, credentials.certificateAlias) + accountManager.setUserData(account, KEY_CLIENT_SECRET, credentials.clientSecret) } } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt index 4770f5473..3278cc5e1 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt @@ -12,6 +12,7 @@ import android.os.AsyncTask import android.os.Bundle import android.provider.CalendarContract import at.bitfire.davdroid.HttpClient +import at.bitfire.davdroid.OpenIdUtils import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.Credentials @@ -91,15 +92,18 @@ open class CalendarsSyncAdapterService : SyncAdapterService() { if (authState != null) { if (authState.needsTokenRefresh) { val tokenRequest = authState.createTokenRefreshRequest() + val clientSecretString = accountSettings.credentials().clientSecret + val clientSecret = OpenIdUtils.getClientAuthentication(clientSecretString) - AuthorizationService(context).performTokenRequest(tokenRequest) { tokenResponse, ex -> + AuthorizationService(context).performTokenRequest(tokenRequest, clientSecret) { tokenResponse, ex -> authState.update(tokenResponse, ex) accountSettings.credentials( Credentials( account.name, null, authState, - null + null, + clientSecret = clientSecretString ) ) it.accountSettings.credentials( @@ -107,7 +111,8 @@ open class CalendarsSyncAdapterService : SyncAdapterService() { it.account.name, null, authState, - null + null, + clientSecret = clientSecretString ) ) object : AsyncTask() { diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.kt index 5de00c3d1..bb89b01a8 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.kt @@ -13,6 +13,7 @@ import android.os.AsyncTask import android.os.Bundle import android.provider.ContactsContract import at.bitfire.davdroid.HttpClient +import at.bitfire.davdroid.OpenIdUtils import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.log.Logger @@ -71,15 +72,18 @@ class ContactsSyncAdapterService: SyncAdapterService() { if (authState != null) { if (authState.needsTokenRefresh) { val tokenRequest = authState.createTokenRefreshRequest() + val clientSecretString = accountSettings.credentials().clientSecret + val clientSecret = OpenIdUtils.getClientAuthentication(clientSecretString) - AuthorizationService(context).performTokenRequest(tokenRequest) { tokenResponse, ex -> + AuthorizationService(context).performTokenRequest(tokenRequest, clientSecret) { tokenResponse, ex -> authState.update(tokenResponse, ex) accountSettings.credentials( Credentials( account.name, null, authState, - null + null, + clientSecret = clientSecretString ) ) it.accountSettings.credentials( @@ -87,7 +91,8 @@ class ContactsSyncAdapterService: SyncAdapterService() { it.account.name, null, authState, - null + null, + clientSecret = clientSecretString ) ) object : AsyncTask() { diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt index 44b219a79..4296d903c 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt @@ -22,6 +22,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.util.Log +import at.bitfire.davdroid.OpenIdUtils import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.log.Logger @@ -182,8 +183,10 @@ class GoogleAccountAuthenticatorService : Service(), OnAccountsUpdateListener { if (authState != null) { if (authState.needsTokenRefresh) { val tokenRequest = authState.createTokenRefreshRequest() + val clientSecretString = accountManager.getUserData(account, AccountSettings.KEY_CLIENT_SECRET) + val clientSecret = OpenIdUtils.getClientAuthentication(clientSecretString) - AuthorizationService(context).performTokenRequest(tokenRequest) { tokenResponse, ex -> + AuthorizationService(context).performTokenRequest(tokenRequest, clientSecret) { tokenResponse, ex -> authState.update(tokenResponse, ex) accountManager.setUserData( account, diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.kt index b3b7b0b65..7aa0c8712 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.kt @@ -21,6 +21,7 @@ import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.ical4android.AndroidTaskList import at.bitfire.ical4android.TaskProvider import android.os.AsyncTask +import at.bitfire.davdroid.OpenIdUtils import at.bitfire.davdroid.db.Credentials import net.openid.appauth.AuthorizationService import okhttp3.HttpUrl @@ -78,15 +79,18 @@ open class TasksSyncAdapterService: SyncAdapterService() { if (authState != null) { if (authState.needsTokenRefresh) { val tokenRequest = authState.createTokenRefreshRequest() + val clientSecretString = accountSettings.credentials().clientSecret + val clientSecret = OpenIdUtils.getClientAuthentication(clientSecretString) - AuthorizationService(context).performTokenRequest(tokenRequest) { tokenResponse, ex -> + AuthorizationService(context).performTokenRequest(tokenRequest, clientSecret) { tokenResponse, ex -> authState.update(tokenResponse, ex) accountSettings.credentials( Credentials( account.name, null, authState, - null + null, + clientSecret = clientSecretString ) ) it.accountSettings.credentials( @@ -94,7 +98,8 @@ open class TasksSyncAdapterService: SyncAdapterService() { it.account.name, null, authState, - null + null, + clientSecret = clientSecretString ) ) object : AsyncTask() { diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt index 78f785ca1..a14450369 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt @@ -198,8 +198,9 @@ abstract class OpenIdAuthenticationBaseFragment(private val identityProvider: Id null, viewModel.getAuthState(), null, - baseUri - ) //TODO: add clientSecret param in credential in future + baseUri, + identityProvider.clientSecret + ) parentFragmentManager.beginTransaction() .replace(android.R.id.content, DetectConfigurationFragment(), null) -- GitLab From 663dbe9cd14bf1e1ce23a201398ffcc4e8992baa Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Thu, 25 May 2023 12:41:39 +0600 Subject: [PATCH 04/11] Fix 401 on openId PUT sync requests on SyncManager for openId accounts, accessToken was not passed for the PUT requests, so CALDAV & CARDAV create/update requests for those accounts were not pushed to the cloud. --- .../main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt index 0ce18b78e..18807295e 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt @@ -437,7 +437,7 @@ abstract class SyncManager, out CollectionType: L newFileName = local.prepareForUpload() val uploadUrl = collectionURL.newBuilder().addPathSegment(newFileName).build() - remoteExceptionContext(DavResource(httpClient.okHttpClient, uploadUrl)) { remote -> + remoteExceptionContext(DavResource(httpClient.okHttpClient, uploadUrl, accountSettings.credentials().authState?.accessToken)) { remote -> Logger.log.info("Uploading new record ${local.id} -> $newFileName") remote.put(generateUpload(local), ifNoneMatch = true, callback = readTagsFromResponse) } @@ -446,7 +446,7 @@ abstract class SyncManager, out CollectionType: L local.prepareForUpload() val uploadUrl = collectionURL.newBuilder().addPathSegment(existingFileName).build() - remoteExceptionContext(DavResource(httpClient.okHttpClient, uploadUrl)) { remote -> + remoteExceptionContext(DavResource(httpClient.okHttpClient, uploadUrl, accountSettings.credentials().authState?.accessToken)) { remote -> val lastScheduleTag = local.scheduleTag val lastETag = if (lastScheduleTag == null) local.eTag else null Logger.log.info("Uploading modified record ${local.id} -> $existingFileName (ETag=$lastETag, Schedule-Tag=$lastScheduleTag)") -- GitLab From 37d630697cf4f9b88afea9886ff9ab6bd6310f8d Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Thu, 25 May 2023 12:55:32 +0600 Subject: [PATCH 05/11] add nullify annotations in IdentityProvider --- .../davdroid/authorization/IdentityProvider.java | 11 +++++++++++ .../syncadapter/CalendarsSyncAdapterService.kt | 15 +++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/authorization/IdentityProvider.java b/app/src/main/java/at/bitfire/davdroid/authorization/IdentityProvider.java index 4dc78579f..b936ecdf9 100644 --- a/app/src/main/java/at/bitfire/davdroid/authorization/IdentityProvider.java +++ b/app/src/main/java/at/bitfire/davdroid/authorization/IdentityProvider.java @@ -44,15 +44,26 @@ public class IdentityProvider { null ); + @Nullable private final Uri mDiscoveryEndpoint; + + @Nullable private final Uri mAuthEndpoint; + + @Nullable private final Uri mTokenEndpoint; @NonNull private final String mClientId; + + @Nullable private final String mClientSecret; @NonNull private final Uri mRedirectUri; + + @Nullable private final String mScope; + + @Nullable private final String mUserInfoEndpoint; IdentityProvider( diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt index 3278cc5e1..ae4bc54cc 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt @@ -53,11 +53,10 @@ open class CalendarsSyncAdapterService : SyncAdapterService() { - sync conditions (e.g. "sync only in WiFi") are not met AND - this is is an automatic sync (i.e. manual syncs are run regardless of sync conditions) */ - if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions( - accountSettings - ) - ) + if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && + !checkSyncConditions(accountSettings)) { return + } if (accountSettings.getEventColors()) AndroidCalendar.insertColors(provider, account) @@ -93,9 +92,13 @@ open class CalendarsSyncAdapterService : SyncAdapterService() { if (authState.needsTokenRefresh) { val tokenRequest = authState.createTokenRefreshRequest() val clientSecretString = accountSettings.credentials().clientSecret - val clientSecret = OpenIdUtils.getClientAuthentication(clientSecretString) + val clientSecret = + OpenIdUtils.getClientAuthentication(clientSecretString) - AuthorizationService(context).performTokenRequest(tokenRequest, clientSecret) { tokenResponse, ex -> + AuthorizationService(context).performTokenRequest( + tokenRequest, + clientSecret + ) { tokenResponse, ex -> authState.update(tokenResponse, ex) accountSettings.credentials( Credentials( -- GitLab From 808c30325ebb2606df13f8e5dc40e5189794ecd7 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Thu, 25 May 2023 13:24:50 +0600 Subject: [PATCH 06/11] refactor according to review --- .../syncadapter/DummySyncAdapterService.kt | 47 ------------------- .../EeloAppDataSyncAdapterService.kt | 23 ++++++++- .../EeloMediaSyncAdapterService.kt | 23 ++++++++- .../EeloMeteredEdriveSyncAdapterService.kt | 23 ++++++++- .../EeloNotesSyncAdapterService.kt | 23 ++++++++- 5 files changed, 88 insertions(+), 51 deletions(-) delete mode 100644 app/src/main/java/at/bitfire/davdroid/syncadapter/DummySyncAdapterService.kt diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/DummySyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/DummySyncAdapterService.kt deleted file mode 100644 index 7a11d81a4..000000000 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/DummySyncAdapterService.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright MURENA SAS 2023 - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 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 . - */ - -package at.bitfire.davdroid.syncadapter - -import android.accounts.Account -import android.content.ContentProviderClient -import android.content.Context -import android.content.SyncResult -import android.os.Bundle -import at.bitfire.davdroid.HttpClient -import at.bitfire.davdroid.db.AppDatabase - -abstract class DummySyncAdapterService : SyncAdapterService() { - - override fun syncAdapter() = DummySyncAdapter(this, appDatabase) - - - class DummySyncAdapter( - context: Context, - db: AppDatabase - ) : SyncAdapter(context, db) { - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - // Unused - } - } -} diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAppDataSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAppDataSyncAdapterService.kt index b5b1db95a..82d94924b 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAppDataSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAppDataSyncAdapterService.kt @@ -16,4 +16,25 @@ package at.bitfire.davdroid.syncadapter -class EeloAppDataSyncAdapterService : DummySyncAdapterService() \ No newline at end of file +import android.accounts.Account +import android.content.ContentProviderClient +import android.content.SyncResult +import android.os.Bundle +import at.bitfire.davdroid.HttpClient + +class EeloAppDataSyncAdapterService : SyncAdapterService() { + + override fun syncAdapter() = + object : SyncAdapter(this@EeloAppDataSyncAdapterService, appDatabase) { + override fun sync( + account: Account, + extras: Bundle, + authority: String, + httpClient: Lazy, + provider: ContentProviderClient, + syncResult: SyncResult + ) { + // ignore + } + } +} \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMediaSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMediaSyncAdapterService.kt index 75d11b05a..268cd842c 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMediaSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMediaSyncAdapterService.kt @@ -16,4 +16,25 @@ package at.bitfire.davdroid.syncadapter -class EeloMediaSyncAdapterService : DummySyncAdapterService() +import android.accounts.Account +import android.content.ContentProviderClient +import android.content.SyncResult +import android.os.Bundle +import at.bitfire.davdroid.HttpClient + +class EeloMediaSyncAdapterService : SyncAdapterService() { + + override fun syncAdapter() = + object : SyncAdapter(this@EeloMediaSyncAdapterService, appDatabase) { + override fun sync( + account: Account, + extras: Bundle, + authority: String, + httpClient: Lazy, + provider: ContentProviderClient, + syncResult: SyncResult + ) { + // ignore + } + } +} diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMeteredEdriveSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMeteredEdriveSyncAdapterService.kt index 8277877bb..96178f4c6 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMeteredEdriveSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMeteredEdriveSyncAdapterService.kt @@ -16,4 +16,25 @@ package at.bitfire.davdroid.syncadapter -class EeloMeteredEdriveSyncAdapterService : DummySyncAdapterService() +import android.accounts.Account +import android.content.ContentProviderClient +import android.content.SyncResult +import android.os.Bundle +import at.bitfire.davdroid.HttpClient + +class EeloMeteredEdriveSyncAdapterService : SyncAdapterService() { + + override fun syncAdapter() = + object : SyncAdapter(this@EeloMeteredEdriveSyncAdapterService, appDatabase) { + override fun sync( + account: Account, + extras: Bundle, + authority: String, + httpClient: Lazy, + provider: ContentProviderClient, + syncResult: SyncResult + ) { + // ignore + } + } +} diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloNotesSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloNotesSyncAdapterService.kt index c06204177..dbc2ee23e 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloNotesSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloNotesSyncAdapterService.kt @@ -16,4 +16,25 @@ package at.bitfire.davdroid.syncadapter -class EeloNotesSyncAdapterService : DummySyncAdapterService() \ No newline at end of file +import android.accounts.Account +import android.content.ContentProviderClient +import android.content.SyncResult +import android.os.Bundle +import at.bitfire.davdroid.HttpClient + +class EeloNotesSyncAdapterService : SyncAdapterService() { + + override fun syncAdapter() = + object : SyncAdapter(this@EeloNotesSyncAdapterService, appDatabase) { + override fun sync( + account: Account, + extras: Bundle, + authority: String, + httpClient: Lazy, + provider: ContentProviderClient, + syncResult: SyncResult + ) { + // ignore + } + } +} \ No newline at end of file -- GitLab From 3c4ecfd1f0ff40b6c9d719ccc8d38678f926a091 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Thu, 25 May 2023 18:43:26 +0600 Subject: [PATCH 07/11] Centralize supported accountType destribution Currently app support multiple accountTypes (webdav, murena & google). But there is no center util method which could provide all supported accountTypes & all supported active accounts. So to add new account, we need to change multiple files. This commit optimize this by introducing these util methods in AccountsUtils object class. --- .../at/bitfire/davdroid/db/AppDatabase.kt | 3 +- .../davdroid/resource/LocalAddressBook.kt | 10 +--- .../davdroid/settings/AccountSettings.kt | 22 ++++---- .../davdroid/syncadapter/AccountUtils.kt | 53 ++++++++++++++++++- .../syncadapter/AccountsUpdatedListener.kt | 13 +---- .../EeloAccountAuthenticatorService.kt | 25 +++------ .../GoogleAccountAuthenticatorService.kt | 26 +++------ .../davdroid/syncadapter/SyncManager.kt | 2 +- .../bitfire/davdroid/syncadapter/SyncUtils.kt | 18 +------ .../davdroid/ui/AccountListFragment.kt | 19 ++++--- .../bitfire/davdroid/ui/DebugInfoActivity.kt | 37 ++++--------- .../ui/account/RenameAccountFragment.kt | 20 ++----- .../davdroid/ui/account/SettingsActivity.kt | 9 ++-- .../ui/setup/AccountDetailsFragment.kt | 3 +- .../ui/setup/InviteSuccessfulFragment.kt | 3 +- .../davdroid/ui/setup/SendInviteFragment.kt | 3 +- 16 files changed, 123 insertions(+), 143 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt b/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt index 237651c17..51478c40a 100644 --- a/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt +++ b/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt @@ -18,6 +18,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase import at.bitfire.davdroid.R import at.bitfire.davdroid.TextTable import at.bitfire.davdroid.log.Logger +import at.bitfire.davdroid.syncadapter.AccountUtils import at.bitfire.davdroid.ui.AccountsActivity import at.bitfire.davdroid.ui.NotificationUtils import dagger.Module @@ -67,7 +68,7 @@ abstract class AppDatabase: RoomDatabase() { // remove all accounts because they're unfortunately useless without database val am = AccountManager.get(context) - for (account in am.getAccountsByType(context.getString(R.string.account_type))) + for (account in AccountUtils.getMainAccounts(context)) am.removeAccount(account, null, null) } }) diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt index dbacfccab..abada92d0 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt @@ -86,11 +86,7 @@ open class LocalAddressBook( * @return list of [mainAccount]'s address books */ fun findAll(context: Context, provider: ContentProviderClient?, mainAccount: Account?): List { - val accountManager = AccountManager.get(context) - val accounts = ArrayList() - accountManager.getAccountsByType(context.getString(R.string.account_type_eelo_address_book)).forEach { accounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type_google_address_book)).forEach { accounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)).forEach { accounts.add(it) } + val accounts = AccountUtils.getAddressBookAccounts(context) return accounts.toTypedArray().map { LocalAddressBook(context, it, provider) } .filter { mainAccount == null || it.mainAccount == mainAccount } @@ -128,9 +124,7 @@ open class LocalAddressBook( * @throws IllegalArgumentException if the given account is not a address book account or does not have a main account */ fun mainAccount(context: Context, account: Account): Account = - if (account.type == context.getString(R.string.account_type_address_book) || - account.type == context.getString(R.string.account_type_eelo_address_book) || - account.type == context.getString(R.string.account_type_google_address_book)) { + if (account.type in AccountUtils.getAddressBookAccountTypes(context)) { val manager = AccountManager.get(context) val accountName = manager.getUserData(account, USER_DATA_MAIN_ACCOUNT_NAME) diff --git a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt index c9af2758c..2221fea3e 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt @@ -33,6 +33,7 @@ import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalAddressBook import at.bitfire.davdroid.resource.LocalTask import at.bitfire.davdroid.resource.TaskUtils +import at.bitfire.davdroid.syncadapter.AccountUtils import at.bitfire.davdroid.syncadapter.SyncUtils import at.bitfire.ical4android.AndroidCalendar import at.bitfire.ical4android.AndroidEvent @@ -173,8 +174,7 @@ class AccountSettings( val addressBooksAuthority = context.getString(R.string.address_books_authority) val taskAuthority = TaskUtils.currentProvider(context)?.authority - val am = AccountManager.get(context) - for (account in am.getAccountsByType(context.getString(R.string.account_type))) + for (account in AccountUtils.getMainAccounts(context)) try { val settings = AccountSettings(context, account) @@ -220,16 +220,20 @@ class AccountSettings( val account: Account init { - when (argAccount.type) { - context.getString(R.string.account_type_address_book), context.getString(R.string.account_type_eelo_address_book), context.getString(R.string.account_type_google_address_book) -> { + account = when (argAccount.type) { + in AccountUtils.getAddressBookAccountTypes(context) -> { /* argAccount is an address book account, which is not a main account. However settings are - stored in the main account, so resolve and use the main account instead. */ - account = LocalAddressBook.mainAccount(context, argAccount) + stored in the main account, so resolve and use the main account instead. */ + LocalAddressBook.mainAccount(context, argAccount) } - context.getString(R.string.account_type), context.getString(R.string.google_account_type), context.getString(R.string.eelo_account_type) -> - account = argAccount - else -> + + in AccountUtils.getMainAccountTypes(context) -> { + argAccount + } + + else -> { throw IllegalArgumentException("Account type not supported. AccountType: ${argAccount.type}") + } } // synchronize because account migration must only be run one time diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountUtils.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountUtils.kt index add388be2..840e24b21 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountUtils.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountUtils.kt @@ -8,6 +8,7 @@ import android.accounts.Account import android.accounts.AccountManager import android.content.Context import android.os.Bundle +import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger object AccountUtils { @@ -24,7 +25,12 @@ object AccountUtils { * @throws IllegalArgumentException when user data contains non-String values * @throws IllegalStateException if user data can't be set */ - fun createAccount(context: Context, account: Account, userData: Bundle, password: String? = null): Boolean { + fun createAccount( + context: Context, + account: Account, + userData: Bundle, + password: String? = null + ): Boolean { // validate user data for (key in userData.keySet()) { userData.get(key)?.let { entry -> @@ -63,5 +69,50 @@ object AccountUtils { return true } + fun getMainAccountTypes(context: Context) = + listOf( + context.getString(R.string.account_type), + context.getString(R.string.eelo_account_type), + context.getString(R.string.google_account_type) + ) + fun getMainAccounts(context: Context): List { + val accountManager = AccountManager.get(context) + val accounts = mutableListOf() + + getMainAccountTypes(context) + .forEach { + accounts.addAll(accountManager.getAccountsByType(it)) + } + + return accounts + } + + fun getAddressBookAccountTypes(context: Context) = + listOf( + context.getString(R.string.account_type_address_book), + context.getString(R.string.account_type_eelo_address_book), + context.getString(R.string.account_type_google_address_book) + ) + + fun getAddressBookAccounts(context: Context): List { + val accountManager = AccountManager.get(context) + val accounts = mutableListOf() + + getAddressBookAccountTypes(context) + .forEach { + accounts.addAll(accountManager.getAccountsByType(it)) + } + + return accounts + } + + fun getAddressBookType(context: Context, mainType: String): String? { + return when(mainType) { + context.getString(R.string.account_type) -> context.getString(R.string.account_type_address_book) + context.getString(R.string.eelo_account_type) -> context.getString(R.string.account_type_eelo_address_book) + context.getString(R.string.google_account_type) -> context.getString(R.string.account_type_google_address_book) + else -> null + } + } } \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountsUpdatedListener.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountsUpdatedListener.kt index 6aa7f006a..778ee9bb3 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountsUpdatedListener.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountsUpdatedListener.kt @@ -9,7 +9,6 @@ import android.accounts.AccountManager import android.accounts.OnAccountsUpdateListener import android.content.Context import androidx.annotation.AnyThread -import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalAddressBook @@ -86,23 +85,15 @@ class AccountsUpdatedListener private constructor( private fun cleanupAccounts(context: Context, accounts: Array) { Logger.log.log(Level.INFO, "Cleaning up accounts. Current accounts") - val accountManager = AccountManager.get(context) val accountNames = HashSet() - val accountFromManager = ArrayList() - - accountManager.getAccountsByType(context.getString(R.string.eelo_account_type)).forEach { accountFromManager.add(it) } - accountManager.getAccountsByType(context.getString(R.string.google_account_type)).forEach { accountFromManager.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type)).forEach { accountFromManager.add(it) } + val accountFromManager = AccountUtils.getMainAccounts(context) for (account in accountFromManager.toTypedArray()) { accountNames += account.name } // delete orphaned address book accounts - val addressBookAccounts = ArrayList() - accountManager.getAccountsByType(context.getString(R.string.account_type_eelo_address_book)).forEach { addressBookAccounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type_google_address_book)).forEach { addressBookAccounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)).forEach { addressBookAccounts.add(it) } + val addressBookAccounts = AccountUtils.getAddressBookAccounts(context) addressBookAccounts.map { LocalAddressBook(context, it, null) } .forEach { try { diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt index af37b24b5..73fbde6bd 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt @@ -16,12 +16,15 @@ package at.bitfire.davdroid.syncadapter -import android.accounts.* +import android.accounts.AbstractAccountAuthenticator +import android.accounts.Account +import android.accounts.AccountAuthenticatorResponse +import android.accounts.AccountManager +import android.accounts.OnAccountsUpdateListener import android.app.Service import android.content.Context import android.content.Intent import android.os.Bundle -import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalAddressBook @@ -46,30 +49,16 @@ class EeloAccountAuthenticatorService : Service(), OnAccountsUpdateListener { fun cleanupAccounts(context: Context, db: AppDatabase) { Logger.log.info("Cleaning up orphaned accounts") - val accountManager = AccountManager.get(context) - val accountNames = HashSet() - val accounts = ArrayList() - accountManager.getAccountsByType(context.getString(R.string.eelo_account_type)) - .forEach { accounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.google_account_type)) - .forEach { accounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type)) - .forEach { accounts.add(it) } + val accounts = AccountUtils.getMainAccounts(context) for (account in accounts.toTypedArray()) { accountNames += account.name } // delete orphaned address book accounts - val addressBookAccounts = ArrayList() - accountManager.getAccountsByType(context.getString(R.string.account_type_eelo_address_book)) - .forEach { addressBookAccounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type_google_address_book)) - .forEach { addressBookAccounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)) - .forEach { addressBookAccounts.add(it) } + val addressBookAccounts = AccountUtils.getAddressBookAccounts(context) addressBookAccounts.map { LocalAddressBook(context, it, null) } .forEach { try { diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt index 4296d903c..8a66a06be 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt @@ -16,14 +16,16 @@ package at.bitfire.davdroid.syncadapter -import android.accounts.* +import android.accounts.AbstractAccountAuthenticator +import android.accounts.Account +import android.accounts.AccountAuthenticatorResponse +import android.accounts.AccountManager +import android.accounts.OnAccountsUpdateListener import android.app.Service import android.content.Context import android.content.Intent import android.os.Bundle -import android.util.Log import at.bitfire.davdroid.OpenIdUtils -import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalAddressBook @@ -50,29 +52,15 @@ class GoogleAccountAuthenticatorService : Service(), OnAccountsUpdateListener { fun cleanupAccounts(context: Context, db: AppDatabase) { Logger.log.info("Cleaning up orphaned accounts") - val accountManager = AccountManager.get(context) - val accountNames = HashSet() - val accounts = ArrayList() - accountManager.getAccountsByType(context.getString(R.string.eelo_account_type)) - .forEach { accounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.google_account_type)) - .forEach { accounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type)) - .forEach { accounts.add(it) } + val accounts = AccountUtils.getMainAccounts(context) for (account in accounts.toTypedArray()) { accountNames += account.name } // delete orphaned address book accounts - val addressBookAccounts = ArrayList() - accountManager.getAccountsByType(context.getString(R.string.account_type_eelo_address_book)) - .forEach { addressBookAccounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type_google_address_book)) - .forEach { addressBookAccounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)) - .forEach { addressBookAccounts.add(it) } + val addressBookAccounts = AccountUtils.getAddressBookAccounts(context) addressBookAccounts.map { LocalAddressBook(context, it, null) } .forEach { try { diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt index 18807295e..4acce13cc 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt @@ -797,7 +797,7 @@ abstract class SyncManager, out CollectionType: L val contentIntent: Intent var viewItemAction: NotificationCompat.Action? = null - if ((account.type == context.getString(R.string.account_type) || account.type == context.getString(R.string.eelo_account_type) || account.type == context.getString(R.string.google_account_type)) + if (account.type in AccountUtils.getMainAccountTypes(context) && (e is UnauthorizedException || e is NotFoundException)) { contentIntent = Intent(context, SettingsActivity::class.java) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncUtils.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncUtils.kt index b2d663b69..d797ef509 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncUtils.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncUtils.kt @@ -160,24 +160,8 @@ object SyncUtils { } } - private fun allAccounts(context: Context): List { - val accountManager = AccountManager.get(context) - val accounts = mutableListOf() - - accounts.addAll(getAccountsByType(context, accountManager, R.string.account_type)) - accounts.addAll(getAccountsByType(context, accountManager, R.string.eelo_account_type)) - accounts.addAll(getAccountsByType(context, accountManager, R.string.google_account_type)) - - return accounts - } - - private fun getAccountsByType(context: Context, accountManager: AccountManager, @StringRes type: Int): Array { - val accountType = context.getString(type) - return accountManager.getAccountsByType(accountType) - } - fun syncAllAccounts(context: Context): Boolean { - val accounts = allAccounts(context) + val accounts = AccountUtils.getMainAccounts(context) if (accounts.isEmpty()) { return false } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AccountListFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/AccountListFragment.kt index ad65c31d9..58d26a2f3 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AccountListFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/AccountListFragment.kt @@ -10,13 +10,19 @@ import android.accounts.AccountManager import android.accounts.OnAccountsUpdateListener import android.app.Activity import android.app.Application -import android.content.* +import android.content.ContentResolver +import android.content.Intent +import android.content.SyncStatusObserver import android.content.pm.PackageManager -import android.net.* +import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.Settings -import android.view.* +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.View +import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -31,6 +37,7 @@ import at.bitfire.davdroid.DavUtils.SyncStatus import at.bitfire.davdroid.R import at.bitfire.davdroid.databinding.AccountListBinding import at.bitfire.davdroid.databinding.AccountListItemBinding +import at.bitfire.davdroid.syncadapter.AccountUtils import at.bitfire.davdroid.ui.account.AccountActivity import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint @@ -249,11 +256,7 @@ class AccountListFragment: Fragment() { val context = getApplication() val collator = Collator.getInstance() - val accountsFromManager = ArrayList() - val accountManager = AccountManager.get(context) - accountManager.getAccountsByType(context.getString(R.string.eelo_account_type)).forEach { accountsFromManager.add(it) } - accountManager.getAccountsByType(context.getString(R.string.google_account_type)).forEach { accountsFromManager.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type)).forEach { accountsFromManager.add(it) } + val accountsFromManager = AccountUtils.getMainAccounts(context) val sortedAccounts = accountsFromManager .sortedWith { a, b -> diff --git a/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt index a8c63ad87..bdeb3d480 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt @@ -43,6 +43,7 @@ import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalAddressBook import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.SettingsManager +import at.bitfire.davdroid.syncadapter.AccountUtils import at.bitfire.ical4android.TaskProvider.ProviderName import at.bitfire.ical4android.util.MiscUtils.ContentProviderClientHelper.closeCompat import at.techbee.jtx.JtxContract @@ -452,8 +453,10 @@ class DebugInfoActivity: AppCompatActivity() { writer.append("\nACCOUNTS\n\n") val accountManager = AccountManager.get(context) - val mainAccounts = getMainAccounts(accountManager) - val addressBookAccounts = getAddressBookAccounts(accountManager) + val mainAccounts = AccountUtils.getMainAccounts(context) + val addressBookAccounts = AccountUtils.getAddressBookAccounts(context) + + val orphanAddressBook = mutableListOf() for (account in mainAccounts) { dumpMainAccount(account, writer) @@ -467,13 +470,15 @@ class DebugInfoActivity: AppCompatActivity() { ) if (mainAccount == account) { dumpAddressBookAccount(addressBookAccount, accountManager, writer) - iter.remove() + continue } + + orphanAddressBook.add(addressBookAccount) } } - if (addressBookAccounts.isNotEmpty()) { + if (orphanAddressBook.isNotEmpty()) { writer.append("Address book accounts without main account:\n") - for (account in addressBookAccounts) + for (account in orphanAddressBook) dumpAddressBookAccount(account, accountManager, writer) } @@ -491,28 +496,6 @@ class DebugInfoActivity: AppCompatActivity() { debugInfo.postValue(debugInfoFile) } - private fun getMainAccounts(accountManager: AccountManager): ArrayList { - val mainAccounts = ArrayList() - accountManager.getAccountsByType(context.getString(R.string.eelo_account_type)) - .forEach { mainAccounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.google_account_type)) - .forEach { mainAccounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type)) - .forEach { mainAccounts.add(it) } - return mainAccounts - } - - private fun getAddressBookAccounts(accountManager: AccountManager): ArrayList { - val addressBookAccounts = ArrayList() - accountManager.getAccountsByType(context.getString(R.string.account_type_eelo_address_book)) - .forEach { addressBookAccounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type_google_address_book)) - .forEach { addressBookAccounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)) - .forEach { addressBookAccounts.add(it) } - return addressBookAccounts - } - fun generateZip(onSuccess: (File) -> Unit) { viewModelScope.launch(Dispatchers.IO) { try { diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/RenameAccountFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/RenameAccountFragment.kt index 9a68ecd80..ead1050ab 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/RenameAccountFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/RenameAccountFragment.kt @@ -36,6 +36,7 @@ import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalAddressBook import at.bitfire.davdroid.resource.LocalTaskList import at.bitfire.davdroid.settings.AccountSettings +import at.bitfire.davdroid.syncadapter.AccountUtils import at.bitfire.davdroid.syncadapter.AccountsUpdatedListener import at.bitfire.ical4android.TaskProvider import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -143,7 +144,7 @@ class RenameAccountFragment: DialogFragment() { accountManager.renameAccount(oldAccount, newName, { if (it.result?.name == newName /* success */) viewModelScope.launch(Dispatchers.Default + NonCancellable) { - onAccountRenamed(accountManager, oldAccount, newName, syncIntervals) + onAccountRenamed(oldAccount, newName, syncIntervals) // release AccountsUpdatedListener mutex at the end of this async coroutine accountsUpdatedListener.mutex.release() @@ -159,27 +160,16 @@ class RenameAccountFragment: DialogFragment() { } } - private fun getAddressBookAccounts(accountManager: AccountManager): ArrayList { - val addressBookAccounts = ArrayList() - accountManager.getAccountsByType(context.getString(R.string.account_type_eelo_address_book)) - .forEach { addressBookAccounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type_google_address_book)) - .forEach { addressBookAccounts.add(it) } - accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)) - .forEach { addressBookAccounts.add(it) } - return addressBookAccounts - } - @SuppressLint("Recycle") @WorkerThread - fun onAccountRenamed(accountManager: AccountManager, oldAccount: Account, newName: String, syncIntervals: List>) { + fun onAccountRenamed(oldAccount: Account, newName: String, syncIntervals: List>) { // account has now been renamed Logger.log.info("Updating account name references") // cancel maybe running synchronization ContentResolver.cancelSync(oldAccount, null) - for (addrBookAccount in getAddressBookAccounts(accountManager)) + for (addrBookAccount in AccountUtils.getAddressBookAccounts(context)) ContentResolver.cancelSync(addrBookAccount, null) // update account name references in database @@ -196,7 +186,7 @@ class RenameAccountFragment: DialogFragment() { try { context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)?.let { provider -> try { - for (addrBookAccount in getAddressBookAccounts(accountManager)) { + for (addrBookAccount in AccountUtils.getAddressBookAccounts(context)) { val addressBook = LocalAddressBook(context, addrBookAccount, provider) if (oldAccount == addressBook.mainAccount) addressBook.mainAccount = Account(newName, oldAccount.type) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt index e440a0850..56f097a9f 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt @@ -121,7 +121,7 @@ class SettingsActivity: AppCompatActivity() { } private fun launchSetup(): Boolean { - AccountManager.get(context).addAccount(getString(R.string.google_account_type), + AccountManager.get(context).addAccount(account.type, null, null, null, activity, null, null) return true @@ -235,7 +235,7 @@ class SettingsActivity: AppCompatActivity() { prefUserName.summary = credentials.userName prefUserName.text = credentials.userName prefUserName.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newUserName -> - model.updateCredentials(Credentials(newUserName as String, credentials.password, credentials.authState, credentials.certificateAlias)) + model.updateCredentials(Credentials(newUserName as String, credentials.password, credentials.authState, credentials.certificateAlias, clientSecret = credentials.clientSecret)) false } @@ -254,7 +254,8 @@ class SettingsActivity: AppCompatActivity() { credentials.userName, newPassword as String, credentials.authState, - credentials.certificateAlias + credentials.certificateAlias, + clientSecret = credentials.clientSecret ) ) false @@ -268,7 +269,7 @@ class SettingsActivity: AppCompatActivity() { prefCertAlias.summary = credentials.certificateAlias ?: getString(R.string.settings_certificate_alias_empty) prefCertAlias.setOnPreferenceClickListener { KeyChain.choosePrivateKeyAlias(requireActivity(), { newAlias -> - model.updateCredentials(Credentials(credentials.userName, credentials.password, credentials.authState, newAlias)) + model.updateCredentials(Credentials(credentials.userName, credentials.password, credentials.authState, newAlias, clientSecret = credentials.clientSecret)) }, null, null, null, -1, credentials.certificateAlias) true } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt index 235eb48eb..9f39dba06 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt @@ -99,8 +99,7 @@ class AccountDetailsFragment : Fragment() { model.nameError.value = getString(R.string.login_account_name_required) else { // check whether account name already exists - val am = AccountManager.get(requireActivity()) - if (am.getAccountsByType(getString(R.string.account_type)).any { it.name == name }) { + if (AccountUtils.getMainAccounts(requireContext()).any { it.name == name }) { model.nameError.value = getString(R.string.login_account_name_already_taken) return@setOnClickListener } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/InviteSuccessfulFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/InviteSuccessfulFragment.kt index 4ee8cc6fb..9af625498 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/InviteSuccessfulFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/InviteSuccessfulFragment.kt @@ -41,8 +41,9 @@ class InviteSuccessfulFragment : Fragment() { try { activity?.let { val accountManager = AccountManager.get(it) + val accountType = it.getString(R.string.eelo_account_type) accountManager.addAccount( - "e.foundation.webdav.eelo", + accountType, null, null, null, diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/SendInviteFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/SendInviteFragment.kt index 6dc1dfb89..89b7b340b 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/SendInviteFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/SendInviteFragment.kt @@ -154,8 +154,9 @@ class SendInviteFragment : Fragment() { .setNegativeButton(R.string.login_button) { _,_ -> try { val accountManager = AccountManager.get(it) + val accountType = it.getString(R.string.eelo_account_type) accountManager.addAccount( - "e.foundation.webdav.eelo", + accountType, null, null, null, -- GitLab From 608f00a4c78e0d85db5077c71e081efb0834c637 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Thu, 25 May 2023 18:54:13 +0600 Subject: [PATCH 08/11] revert dummySyncAdapterService refactor according to review --- .../EeloAppDataSyncAdapterService.kt | 34 ++++++++++++------- .../EeloMediaSyncAdapterService.kt | 32 ++++++++++------- .../EeloMeteredEdriveSyncAdapterService.kt | 31 ++++++++++------- .../EeloNotesSyncAdapterService.kt | 33 +++++++++++------- 4 files changed, 80 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAppDataSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAppDataSyncAdapterService.kt index 82d94924b..703cb25bd 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAppDataSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAppDataSyncAdapterService.kt @@ -18,23 +18,31 @@ package at.bitfire.davdroid.syncadapter import android.accounts.Account import android.content.ContentProviderClient +import android.content.Context import android.content.SyncResult import android.os.Bundle import at.bitfire.davdroid.HttpClient +import at.bitfire.davdroid.db.AppDatabase class EeloAppDataSyncAdapterService : SyncAdapterService() { - override fun syncAdapter() = - object : SyncAdapter(this@EeloAppDataSyncAdapterService, appDatabase) { - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - // ignore - } + override fun syncAdapter() = EeloAppDataSyncAdapter(this, appDatabase) + + class EeloAppDataSyncAdapter( + context: Context, + db: AppDatabase + ) : SyncAdapter(context, db) { + + override fun sync( + account: Account, + extras: Bundle, + authority: String, + httpClient: Lazy, + provider: ContentProviderClient, + syncResult: SyncResult + ) { + // Unused } -} \ No newline at end of file + } +} + diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMediaSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMediaSyncAdapterService.kt index 268cd842c..88f0c7724 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMediaSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMediaSyncAdapterService.kt @@ -18,23 +18,31 @@ package at.bitfire.davdroid.syncadapter import android.accounts.Account import android.content.ContentProviderClient +import android.content.Context import android.content.SyncResult import android.os.Bundle import at.bitfire.davdroid.HttpClient +import at.bitfire.davdroid.db.AppDatabase class EeloMediaSyncAdapterService : SyncAdapterService() { - override fun syncAdapter() = - object : SyncAdapter(this@EeloMediaSyncAdapterService, appDatabase) { - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - // ignore - } + override fun syncAdapter() = EeloMediaSyncAdapter(this, appDatabase) + + + class EeloMediaSyncAdapter( + context: Context, + db: AppDatabase + ) : SyncAdapter(context, db) { + + override fun sync( + account: Account, + extras: Bundle, + authority: String, + httpClient: Lazy, + provider: ContentProviderClient, + syncResult: SyncResult + ) { + // Unused } + } } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMeteredEdriveSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMeteredEdriveSyncAdapterService.kt index 96178f4c6..cb49d1acc 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMeteredEdriveSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloMeteredEdriveSyncAdapterService.kt @@ -18,23 +18,30 @@ package at.bitfire.davdroid.syncadapter import android.accounts.Account import android.content.ContentProviderClient +import android.content.Context import android.content.SyncResult import android.os.Bundle import at.bitfire.davdroid.HttpClient +import at.bitfire.davdroid.db.AppDatabase class EeloMeteredEdriveSyncAdapterService : SyncAdapterService() { - override fun syncAdapter() = - object : SyncAdapter(this@EeloMeteredEdriveSyncAdapterService, appDatabase) { - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - // ignore - } + override fun syncAdapter() = EeloMeteredEdriveSyncAdapter(this, appDatabase) + + class EeloMeteredEdriveSyncAdapter( + context: Context, + db: AppDatabase + ) : SyncAdapter(context, db) { + + override fun sync( + account: Account, + extras: Bundle, + authority: String, + httpClient: Lazy, + provider: ContentProviderClient, + syncResult: SyncResult + ) { + // Unused } + } } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloNotesSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloNotesSyncAdapterService.kt index dbc2ee23e..91fed22c8 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloNotesSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloNotesSyncAdapterService.kt @@ -18,23 +18,30 @@ package at.bitfire.davdroid.syncadapter import android.accounts.Account import android.content.ContentProviderClient +import android.content.Context import android.content.SyncResult import android.os.Bundle import at.bitfire.davdroid.HttpClient +import at.bitfire.davdroid.db.AppDatabase class EeloNotesSyncAdapterService : SyncAdapterService() { - override fun syncAdapter() = - object : SyncAdapter(this@EeloNotesSyncAdapterService, appDatabase) { - override fun sync( - account: Account, - extras: Bundle, - authority: String, - httpClient: Lazy, - provider: ContentProviderClient, - syncResult: SyncResult - ) { - // ignore - } + override fun syncAdapter() = EeloNotesSyncAdapter(this, appDatabase) + + class EeloNotesSyncAdapter( + context: Context, + db: AppDatabase + ) : SyncAdapter(context, db) { + + override fun sync( + account: Account, + extras: Bundle, + authority: String, + httpClient: Lazy, + provider: ContentProviderClient, + syncResult: SyncResult + ) { + // Unused } -} \ No newline at end of file + } +} -- GitLab From d143be116a187e7e742021bac226fa98d38613aa Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Thu, 25 May 2023 19:34:52 +0600 Subject: [PATCH 09/11] remove non required fields from loginActivity --- .../syncadapter/EeloAccountAuthenticatorService.kt | 3 ++- .../syncadapter/GoogleAccountAuthenticatorService.kt | 3 ++- .../davdroid/ui/setup/AccountDetailsFragment.kt | 10 +++++----- .../davdroid/ui/setup/DetectConfigurationFragment.kt | 2 +- .../java/at/bitfire/davdroid/ui/setup/LoginActivity.kt | 7 +++---- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt index 73fbde6bd..911ba9e60 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt @@ -25,6 +25,7 @@ import android.app.Service import android.content.Context import android.content.Intent import android.os.Bundle +import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalAddressBook @@ -125,7 +126,7 @@ class EeloAccountAuthenticatorService : Service(), OnAccountsUpdateListener { intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response) intent.putExtra( LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE, - LoginActivity.ACCOUNT_PROVIDER_EELO + context.getString(R.string.eelo_account_type) ) val bundle = Bundle(1) bundle.putParcelable(AccountManager.KEY_INTENT, intent) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt index 8a66a06be..ed407171f 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt @@ -26,6 +26,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import at.bitfire.davdroid.OpenIdUtils +import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalAddressBook @@ -127,7 +128,7 @@ class GoogleAccountAuthenticatorService : Service(), OnAccountsUpdateListener { intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response) intent.putExtra( LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE, - LoginActivity.ACCOUNT_PROVIDER_GOOGLE + context.getString(R.string.google_account_type) ) options?.let { diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt index 9f39dba06..16e5d9622 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt @@ -142,8 +142,8 @@ class AccountDetailsFragment : Fragment() { } else v.contactGroupMethod.isEnabled = true - if (requireActivity().intent.getStringExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE) == LoginActivity.ACCOUNT_PROVIDER_EELO || - requireActivity().intent.getStringExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE) == LoginActivity.ACCOUNT_PROVIDER_GOOGLE) { + val providedAccountType = requireActivity().intent.getStringExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE) + if ((providedAccountType != getString(R.string.account_type)) && (providedAccountType in AccountUtils.getMainAccountTypes(requireContext()))) { v.mainDetailLayout.visibility = View.GONE v.mainLoadingLayout.visibility = View.VISIBLE @@ -176,7 +176,7 @@ class AccountDetailsFragment : Fragment() { .KEY_ACCOUNT_AUTHENTICATOR_RESPONSE)?.onResult(null) } - if (requireActivity().intent.getStringExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE) == LoginActivity.ACCOUNT_PROVIDER_EELO) { + if (requireActivity().intent.getStringExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE) == getString(R.string.eelo_account_type)) { val intent = Intent("drive.services.InitializerService") intent.setPackage(getString(R.string.e_drive_package_name)) intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, name) @@ -242,12 +242,12 @@ class AccountDetailsFragment : Fragment() { } when (activity.intent.getStringExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE)) { - LoginActivity.ACCOUNT_PROVIDER_EELO -> { + context.getString(R.string.eelo_account_type) -> { accountType = context.getString(R.string.eelo_account_type) addressBookAccountType = context.getString(R.string.account_type_eelo_address_book) baseURL = credentials?.serverUri.toString() } - LoginActivity.ACCOUNT_PROVIDER_GOOGLE -> { + context.getString(R.string.google_account_type) -> { accountType = context.getString(R.string.google_account_type) addressBookAccountType = context.getString(R.string.account_type_google_address_book) baseURL = null diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt index 937e1925a..0b5c152df 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt @@ -42,7 +42,7 @@ class DetectConfigurationFragment: Fragment() { } val accountType = requireActivity().intent.getStringExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE) - val isMurenaAccountType = (accountType == LoginActivity.ACCOUNT_PROVIDER_EELO) + val isMurenaAccountType = (accountType == getString(R.string.eelo_account_type)) model.detectConfiguration(loginModel, isMurenaAccountType).observe(this, { result -> // save result for next step diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt index 819f72468..3aa0c648a 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt @@ -8,6 +8,7 @@ import android.os.Bundle import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment +import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -39,8 +40,6 @@ class LoginActivity: AppCompatActivity() { const val EXTRA_PASSWORD = "password" const val SETUP_ACCOUNT_PROVIDER_TYPE = "setup_account_provider_type" - const val ACCOUNT_PROVIDER_EELO = "eelo" - const val ACCOUNT_PROVIDER_GOOGLE = "google" const val OPENID_AUTH_FLOW_COMPLETE = "openId_authFlow_complete" const val OPEN_APP_PACKAGE_AFTER_AUTH = "open_app_package_after_auth" @@ -70,12 +69,12 @@ class LoginActivity: AppCompatActivity() { if (fragment != null) { when (intent.getStringExtra(SETUP_ACCOUNT_PROVIDER_TYPE)) { - ACCOUNT_PROVIDER_EELO -> { + getString(R.string.eelo_account_type) -> { supportFragmentManager.beginTransaction() .replace(android.R.id.content, EeloAuthenticatorFragment()) .commit() } - ACCOUNT_PROVIDER_GOOGLE -> { + getString(R.string.google_account_type) -> { supportFragmentManager.beginTransaction() .replace(android.R.id.content, GoogleAuthFragment()) .commit() -- GitLab From 99ef9e8293cca96257e23dbca744d821747532ca Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Thu, 25 May 2023 20:12:44 +0600 Subject: [PATCH 10/11] refactor LoginActivity to support hilt injection --- .../EeloAccountAuthenticatorService.kt | 2 +- .../GoogleAccountAuthenticatorService.kt | 2 +- .../ui/setup/AccountDetailsFragment.kt | 6 +-- .../ui/setup/DetectConfigurationFragment.kt | 2 +- .../davdroid/ui/setup/LoginActivity.kt | 27 +++------- .../ui/setup/MurenaLoginFragmentFactory.kt | 52 +++++++++++++++++++ 6 files changed, 65 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/setup/MurenaLoginFragmentFactory.kt diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt index 911ba9e60..93ef72e1f 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt @@ -125,7 +125,7 @@ class EeloAccountAuthenticatorService : Service(), OnAccountsUpdateListener { val intent = Intent(context, LoginActivity::class.java) intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response) intent.putExtra( - LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE, + LoginActivity.ACCOUNT_TYPE, context.getString(R.string.eelo_account_type) ) val bundle = Bundle(1) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt index ed407171f..37fdb1429 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt @@ -127,7 +127,7 @@ class GoogleAccountAuthenticatorService : Service(), OnAccountsUpdateListener { val intent = Intent(context, LoginActivity::class.java) intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response) intent.putExtra( - LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE, + LoginActivity.ACCOUNT_TYPE, context.getString(R.string.google_account_type) ) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt index 16e5d9622..dff662c43 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt @@ -142,7 +142,7 @@ class AccountDetailsFragment : Fragment() { } else v.contactGroupMethod.isEnabled = true - val providedAccountType = requireActivity().intent.getStringExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE) + val providedAccountType = requireActivity().intent.getStringExtra(LoginActivity.ACCOUNT_TYPE) if ((providedAccountType != getString(R.string.account_type)) && (providedAccountType in AccountUtils.getMainAccountTypes(requireContext()))) { v.mainDetailLayout.visibility = View.GONE @@ -176,7 +176,7 @@ class AccountDetailsFragment : Fragment() { .KEY_ACCOUNT_AUTHENTICATOR_RESPONSE)?.onResult(null) } - if (requireActivity().intent.getStringExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE) == getString(R.string.eelo_account_type)) { + if (requireActivity().intent.getStringExtra(LoginActivity.ACCOUNT_TYPE) == getString(R.string.eelo_account_type)) { val intent = Intent("drive.services.InitializerService") intent.setPackage(getString(R.string.e_drive_package_name)) intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, name) @@ -241,7 +241,7 @@ class AccountDetailsFragment : Fragment() { baseURL = config.calDAV.principal.toString() } - when (activity.intent.getStringExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE)) { + when (activity.intent.getStringExtra(LoginActivity.ACCOUNT_TYPE)) { context.getString(R.string.eelo_account_type) -> { accountType = context.getString(R.string.eelo_account_type) addressBookAccountType = context.getString(R.string.account_type_eelo_address_book) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt index 0b5c152df..b945da0a8 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt @@ -41,7 +41,7 @@ class DetectConfigurationFragment: Fragment() { return } - val accountType = requireActivity().intent.getStringExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE) + val accountType = requireActivity().intent.getStringExtra(LoginActivity.ACCOUNT_TYPE) val isMurenaAccountType = (accountType == getString(R.string.eelo_account_type)) model.detectConfiguration(loginModel, isMurenaAccountType).observe(this, { result -> diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt index 3aa0c648a..9d1a8e3df 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt @@ -8,7 +8,6 @@ import android.os.Bundle import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -39,7 +38,7 @@ class LoginActivity: AppCompatActivity() { */ const val EXTRA_PASSWORD = "password" - const val SETUP_ACCOUNT_PROVIDER_TYPE = "setup_account_provider_type" + const val ACCOUNT_TYPE = "account_type" const val OPENID_AUTH_FLOW_COMPLETE = "openId_authFlow_complete" const val OPEN_APP_PACKAGE_AFTER_AUTH = "open_app_package_after_auth" @@ -68,25 +67,13 @@ class LoginActivity: AppCompatActivity() { } if (fragment != null) { - when (intent.getStringExtra(SETUP_ACCOUNT_PROVIDER_TYPE)) { - getString(R.string.eelo_account_type) -> { - supportFragmentManager.beginTransaction() - .replace(android.R.id.content, EeloAuthenticatorFragment()) - .commit() - } - getString(R.string.google_account_type) -> { - supportFragmentManager.beginTransaction() - .replace(android.R.id.content, GoogleAuthFragment()) - .commit() - } - else -> - // first call, add first login fragment - supportFragmentManager.beginTransaction() - .replace(android.R.id.content, fragment) - .commit() - } - } else + // first call, add first login fragment + supportFragmentManager.beginTransaction() + .replace(android.R.id.content, fragment) + .commit() + } else { Logger.log.severe("Couldn't create LoginFragment") + } } } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/MurenaLoginFragmentFactory.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/MurenaLoginFragmentFactory.kt new file mode 100644 index 000000000..b87f74479 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/MurenaLoginFragmentFactory.kt @@ -0,0 +1,52 @@ +/* + * Copyright MURENA SAS 2023 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 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 . + */ + +package at.bitfire.davdroid.ui.setup + +import android.content.Context +import android.content.Intent +import androidx.fragment.app.Fragment +import at.bitfire.davdroid.R +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.IntKey +import dagger.multibindings.IntoMap +import javax.inject.Inject + +class MurenaLoginFragmentFactory @Inject constructor(@ApplicationContext val context: Context) : LoginCredentialsFragmentFactory { + + override fun getFragment(intent: Intent): Fragment? { + val accountType = intent.getStringExtra(LoginActivity.ACCOUNT_TYPE) ?: return null + + return when (accountType) { + context.getString(R.string.eelo_account_type) -> EeloAuthenticatorFragment() + context.getString(R.string.google_account_type) -> GoogleAuthFragment() + else -> null + } + } +} + +@Module +@InstallIn(SingletonComponent::class) +abstract class MurenaLoginFlowFragmentModule { + @Binds + @IntoMap + @IntKey(/* priority */ 30) + abstract fun factory(impl: MurenaLoginFragmentFactory): LoginCredentialsFragmentFactory +} \ No newline at end of file -- GitLab From b465f2cdbe61aeaf324090327c4de288f59d1157 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Thu, 25 May 2023 20:40:59 +0600 Subject: [PATCH 11/11] add support for separate URI for cardDav --- .../bitfire/davdroid/servicedetection/DavResourceFinder.kt | 5 ++++- .../main/java/at/bitfire/davdroid/ui/setup/LoginModel.kt | 1 + .../davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt | 6 +++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/servicedetection/DavResourceFinder.kt b/app/src/main/java/at/bitfire/davdroid/servicedetection/DavResourceFinder.kt index 86633b453..5c597b314 100644 --- a/app/src/main/java/at/bitfire/davdroid/servicedetection/DavResourceFinder.kt +++ b/app/src/main/java/at/bitfire/davdroid/servicedetection/DavResourceFinder.kt @@ -106,7 +106,10 @@ class DavResourceFinder( private fun findInitialConfiguration(service: Service): Configuration.ServiceInfo? { // user-given base URI (either mailto: URI or http(s):// URL) - val baseURI = loginModel.baseURI!! + var baseURI = loginModel.baseURI!! + if (loginModel.cardDavURI != null && service == Service.CARDDAV) { + baseURI = loginModel.cardDavURI!! + } // domain for service discovery var discoveryFQDN: String? = null diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginModel.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginModel.kt index a5fc3cbc6..c3410489e 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginModel.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginModel.kt @@ -12,6 +12,7 @@ import java.net.URI class LoginModel: ViewModel() { var baseURI: URI? = null + var cardDavURI: URI? = null var credentials: Credentials? = null var configuration: DavResourceFinder.Configuration? = null diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt index a14450369..e4808648f 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationBaseFragment.kt @@ -188,9 +188,13 @@ abstract class OpenIdAuthenticationBaseFragment(private val identityProvider: Id finishActivity() } - protected fun proceedNext(userName: String, baseUrl: String) { + protected fun proceedNext(userName: String, baseUrl: String, cardDavUrl: String? = null) { activity?.intent?.putExtra(LoginActivity.OPENID_AUTH_FLOW_COMPLETE, true) + if (cardDavUrl != null) { + loginModel.cardDavURI = URI(cardDavUrl) + } + val baseUri = URI(baseUrl) loginModel.baseURI = baseUri loginModel.credentials = Credentials( -- GitLab