Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 91155a7e authored by Mohammed Althaf T's avatar Mohammed Althaf T 😊
Browse files

Merge branch '7922-main-429' into 'main'

Handle 401 exception on app update

See merge request !150
parents b639c53d d57054ac
Loading
Loading
Loading
Loading
Loading
+10 −2
Original line number Diff line number Diff line
@@ -27,7 +27,15 @@ android {
        localProps.load(new FileInputStream(localPropsFile))
    }

    def appVersionCode = localProps.getProperty('VERSION_CODE', '403090007').toInteger() + 1
    def appVersionCode = localProps.getProperty('VERSION_CODE')
    if (appVersionCode == null) {
        // Set initial version code if not present
        appVersionCode = 403090008
    } else {
        // Increment version code for subsequent builds
        appVersionCode = appVersionCode.toInteger() + 1
    }

    localProps.setProperty('VERSION_CODE', appVersionCode.toString())
    localProps.store(new FileOutputStream(localPropsFile), null)

@@ -35,7 +43,7 @@ android {
        applicationId "foundation.e.accountmanager"

        versionCode appVersionCode
        versionName '4.3.9-7'
        versionName '4.3.9-8'

        buildConfigField "long", "buildTime", System.currentTimeMillis() + "L"

+2 −0
Original line number Diff line number Diff line
@@ -34,4 +34,6 @@ object Constants {
    const val E_SYNC_URL = "e.email"

    const val MURENA_DAV_URL = "https://murena.io/remote.php/dav"

    const val HTTP_STATUS_CODE_TOO_MANY_REQUESTS = 429
}
+2 −1
Original line number Diff line number Diff line
@@ -13,7 +13,8 @@ data class Credentials(
    val authState: AuthState? = null,
    val certificateAlias: String? = null,
    val serverUri: URI? = null,
    val clientSecret: String? = null
    val clientSecret: String? = null,
    var passwordNeedsUpdate: Boolean = false
) {

    override fun toString(): String {
+10 −1
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ class AccountSettings(
        const val KEY_AUTH_STATE = "auth_state"
        const val KEY_CLIENT_SECRET = "client_secret"
        const val KEY_CERTIFICATE_ALIAS = "certificate_alias"
        const val KEY_PASSWORD_NEEDS_UPDATE = "password_needs_update"

        const val KEY_WIFI_ONLY = "wifi_only"               // sync on WiFi only (default: false)
        const val KEY_WIFI_ONLY_SSIDS = "wifi_only_ssids"   // restrict sync to specific WiFi SSIDs
@@ -156,6 +157,9 @@ class AccountSettings(
                if (credentials.clientSecret != null) {
                    bundle.putString(KEY_CLIENT_SECRET, credentials.clientSecret)
                }

                bundle.putString(KEY_PASSWORD_NEEDS_UPDATE,
                    credentials.passwordNeedsUpdate.toString())
            }

            if (!cookies.isNullOrEmpty()) {
@@ -270,7 +274,9 @@ class AccountSettings(
                accountManager.getUserData(account, KEY_USERNAME),
                accountManager.getPassword(account),
                null,
                accountManager.getUserData(account, KEY_CERTIFICATE_ALIAS)
                accountManager.getUserData(account, KEY_CERTIFICATE_ALIAS),
                passwordNeedsUpdate = accountManager.getUserData(
                    account, KEY_PASSWORD_NEEDS_UPDATE).toBoolean()
            )
        } else {
            Credentials(
@@ -287,6 +293,9 @@ class AccountSettings(
        // Basic/Digest auth
        accountManager.setAndVerifyUserData(account, KEY_USERNAME, credentials.userName)
        accountManager.setPassword(account, credentials.password)
        accountManager.setAndVerifyUserData(account, KEY_PASSWORD_NEEDS_UPDATE,
            credentials.passwordNeedsUpdate.toString()
        )

        // client certificate
        accountManager.setAndVerifyUserData(account, KEY_CERTIFICATE_ALIAS, credentials.certificateAlias)
+54 −4
Original line number Diff line number Diff line
@@ -10,12 +10,16 @@ import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.content.SyncResult
import android.content.pm.PackageManager
import android.net.Uri
import android.os.RemoteException
import android.provider.CalendarContract
import android.provider.ContactsContract
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.edit
import androidx.core.content.pm.PackageInfoCompat
import androidx.preference.PreferenceManager
import at.bitfire.dav4jvm.*
import at.bitfire.dav4jvm.exception.*
import at.bitfire.dav4jvm.property.GetCTag
@@ -175,12 +179,20 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L

    val workDispatcher = getWorkDispatcher()

    private val PREF_LAST_VERSION_CODE = "LAST_VERSION_CODE"

    /**
     * Call performSync with default retry values
     */
    fun performSync() {
        val authState = accountSettings.credentials().authState
        val credentials = accountSettings.credentials()
        if (credentials.passwordNeedsUpdate) {
            val exception = UnauthorizedException(context.getString(R.string.sync_error_authentication_failed))
            notifyException(exception, null, null)
            return
        }

        val authState = credentials.authState
        if (authState == null || !authState.needsTokenRefresh) {
            performSync(DEFAULT_RETRY_AFTER, DEFAULT_SECOND_RETRY_AFTER, DEFAULT_MAX_RETRY_TIME)
            return
@@ -189,6 +201,34 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L
        refreshAuthTokenAndSync(authState)
    }

    private fun packageChanged(): Boolean {
        val prefs = PreferenceManager.getDefaultSharedPreferences(context)
        val currentVersion = getCurrentVersionCode()
        val savedVersion = prefs.getLong(PREF_LAST_VERSION_CODE, 0L)
        if (savedVersion != currentVersion) {
            Logger.log.warning("App has been updated!" +
                    "Previous: $savedVersion, Current: $currentVersion")
            prefs.edit {
                putLong(PREF_LAST_VERSION_CODE, currentVersion)
            }
            // Clear cookie since the app is updated.
            Logger.log.warning("Clear cookies for ${accountSettings.credentials().userName}")
            accountSettings.clearCookie()
            return true
        }

        return false
    }

    private fun getCurrentVersionCode(): Long {
        return try {
            val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
            PackageInfoCompat.getLongVersionCode(packageInfo)
        } catch (e: PackageManager.NameNotFoundException) {
            0
        }
    }

    private fun refreshAuthTokenAndSync(authState: AuthState) {
        val tokenRequest = authState.createTokenRefreshRequest()
        val clientSecretString = accountSettings.credentials().clientSecret
@@ -226,8 +266,7 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L
     * @param secondRetryAfter optional param, in seconds. Used to calculate fibonnacci sequence for rety on unhandled exception
     * @param maxRetryTime optional param, in seconds. On unhandled exception, max time the method should retry.
     */
    fun performSync(retryAfter: Int, secondRetryAfter: Int, maxRetryTime: Int) {

    private fun performSync(retryAfter: Int, secondRetryAfter: Int, maxRetryTime: Int) {
        // dismiss previous error notifications
        notificationManager.cancel(notificationTag, NotificationUtils.NOTIFY_SYNC_ERROR)

@@ -383,6 +422,14 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L

                is UnauthorizedException -> {
                    Logger.log.log(Level.WARNING, "Got 401 Unauthorized", e)
                    if (packageChanged()) {
                        return@unwrapExceptions
                    }
                    val credentials = accountSettings.credentials()
                    if (credentials.authState == null) {
                        credentials.passwordNeedsUpdate = true
                        accountSettings.credentials(credentials)
                    }
                    notifyException(e, local, remote)
                    return@unwrapExceptions
                }
@@ -411,6 +458,10 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L
        if (retryAfter > 0 && secondRetryAfter > 0 && retryAfter <= maxRetryTime) {
            try {
                Logger.log.severe("Faced unhandled exception $e, Will retry sync")
                if (e is HttpException && e.code == Constants.HTTP_STATUS_CODE_TOO_MANY_REQUESTS) {
                    Logger.log.info("HTTP 429 Too Many Requests: Retry sync cancelled")
                    return false
                }
                Logger.log.info("Retry sync after $retryAfter seconds")
                Thread.sleep(retryAfter * 1000L)
                performSync(secondRetryAfter, retryAfter + secondRetryAfter, maxRetryTime)
@@ -944,7 +995,6 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L
                .setCategory(NotificationCompat.CATEGORY_ERROR)
        viewItemAction?.let { builder.addAction(it) }


        notificationManager.notifyIfPossible(tag, NotificationUtils.NOTIFY_SYNC_ERROR, builder.build())
    }

Loading