Merge pull request #2701 from vector-im/feature/bma/fix_clear_cache

Fix clear cache issue: sometimes, after a clear cache, there is still a token, so the init sync service is not started.
This commit is contained in:
Benoit Marty 2021-01-20 17:15:12 +01:00 committed by GitHub
commit 618d1f5de6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 175 additions and 146 deletions

View File

@ -8,6 +8,7 @@ Improvements 🙌:
- -
Bugfix 🐛: Bugfix 🐛:
- Fix clear cache issue: sometimes, after a clear cache, there is still a token, so the init sync service is not started.
- Sidebar too large in horizontal orientation or tablets (#475) - Sidebar too large in horizontal orientation or tablets (#475)
Translations 🗣: Translations 🗣:

View File

@ -378,7 +378,9 @@ class CommonTestHelper(context: Context) {
fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) } fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) }
fun signOutAndClose(session: Session) { fun signOutAndClose(session: Session) {
doSync<Unit>(60_000) { session.signOut(true, it) } runBlockingTest(timeout = 60_000) {
session.signOut(true)
}
// no need signout will close // no need signout will close
// session.close() // session.close()
} }

View File

@ -66,8 +66,8 @@ class TimelineForwardPaginationTest : InstrumentedTest {
numberOfMessagesToSend) numberOfMessagesToSend)
// Alice clear the cache // Alice clear the cache
commonTestHelper.doSync<Unit> { commonTestHelper.runBlockingTest {
aliceSession.clearCache(it) aliceSession.clearCache()
} }
// And restarts the sync // And restarts the sync

View File

@ -16,8 +16,6 @@
package org.matrix.android.sdk.api.session.cache package org.matrix.android.sdk.api.session.cache
import org.matrix.android.sdk.api.MatrixCallback
/** /**
* This interface defines a method to clear the cache. It's implemented at the session level. * This interface defines a method to clear the cache. It's implemented at the session level.
*/ */
@ -26,5 +24,5 @@ interface CacheService {
/** /**
* Clear the whole cached data, except credentials. Once done, the sync has to be restarted by the sdk user. * Clear the whole cached data, except credentials. Once done, the sync has to be restarted by the sdk user.
*/ */
fun clearCache(callback: MatrixCallback<Unit>) suspend fun clearCache()
} }

View File

@ -16,9 +16,7 @@
package org.matrix.android.sdk.api.session.signout package org.matrix.android.sdk.api.session.signout
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.util.Cancelable
/** /**
* This interface defines a method to sign out, or to renew the token. It's implemented at the session level. * This interface defines a method to sign out, or to renew the token. It's implemented at the session level.
@ -29,19 +27,16 @@ interface SignOutService {
* Ask the homeserver for a new access token. * Ask the homeserver for a new access token.
* The same deviceId will be used * The same deviceId will be used
*/ */
fun signInAgain(password: String, suspend fun signInAgain(password: String)
callback: MatrixCallback<Unit>): Cancelable
/** /**
* Update the session with credentials received after SSO * Update the session with credentials received after SSO
*/ */
fun updateCredentials(credentials: Credentials, suspend fun updateCredentials(credentials: Credentials)
callback: MatrixCallback<Unit>): Cancelable
/** /**
* Sign out, and release the session, clear all the session data, including crypto data * Sign out, and release the session, clear all the session data, including crypto data
* @param signOutFromHomeserver true if the sign out request has to be done * @param signOutFromHomeserver true if the sign out request has to be done
*/ */
fun signOut(signOutFromHomeserver: Boolean, suspend fun signOut(signOutFromHomeserver: Boolean)
callback: MatrixCallback<Unit>): Cancelable
} }

View File

@ -20,7 +20,6 @@ import androidx.annotation.MainThread
import dagger.Lazy import dagger.Lazy
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.failure.GlobalError
import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.pushrules.PushRuleService
@ -217,13 +216,13 @@ internal class DefaultSession @Inject constructor(
} }
} }
override fun clearCache(callback: MatrixCallback<Unit>) { override suspend fun clearCache() {
stopSync() stopSync()
stopAnyBackgroundSync() stopAnyBackgroundSync()
uiHandler.post { uiHandler.post {
lifecycleObservers.forEach { it.onClearCache() } lifecycleObservers.forEach { it.onClearCache() }
} }
cacheService.get().clearCache(callback) cacheService.get().clearCache()
workManagerProvider.cancelAllWorks() workManagerProvider.cancelAllWorks()
} }

View File

@ -16,23 +16,18 @@
package org.matrix.android.sdk.internal.session.cache package org.matrix.android.sdk.internal.session.cache
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.cache.CacheService import org.matrix.android.sdk.api.session.cache.CacheService
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import javax.inject.Inject import javax.inject.Inject
internal class DefaultCacheService @Inject constructor(@SessionDatabase internal class DefaultCacheService @Inject constructor(@SessionDatabase
private val clearCacheTask: ClearCacheTask, private val clearCacheTask: ClearCacheTask,
private val taskExecutor: TaskExecutor) : CacheService { private val taskExecutor: TaskExecutor
) : CacheService {
override fun clearCache(callback: MatrixCallback<Unit>) { override suspend fun clearCache() {
taskExecutor.cancelAll() taskExecutor.cancelAll()
clearCacheTask clearCacheTask.execute(Unit)
.configureWith {
this.callback = callback
}
.executeBy(taskExecutor)
} }
} }

View File

@ -16,45 +16,25 @@
package org.matrix.android.sdk.internal.session.signout package org.matrix.android.sdk.internal.session.signout
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.session.signout.SignOutService import org.matrix.android.sdk.api.session.signout.SignOutService
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.auth.SessionParamsStore import org.matrix.android.sdk.internal.auth.SessionParamsStore
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import javax.inject.Inject import javax.inject.Inject
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask, internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
private val signInAgainTask: SignInAgainTask, private val signInAgainTask: SignInAgainTask,
private val sessionParamsStore: SessionParamsStore, private val sessionParamsStore: SessionParamsStore
private val coroutineDispatchers: MatrixCoroutineDispatchers, ) : SignOutService {
private val taskExecutor: TaskExecutor) : SignOutService {
override fun signInAgain(password: String, override suspend fun signInAgain(password: String) {
callback: MatrixCallback<Unit>): Cancelable { signInAgainTask.execute(SignInAgainTask.Params(password))
return signInAgainTask
.configureWith(SignInAgainTask.Params(password)) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
override fun updateCredentials(credentials: Credentials, override suspend fun updateCredentials(credentials: Credentials) {
callback: MatrixCallback<Unit>): Cancelable { sessionParamsStore.updateCredentials(credentials)
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
sessionParamsStore.updateCredentials(credentials)
}
} }
override fun signOut(signOutFromHomeserver: Boolean, override suspend fun signOut(signOutFromHomeserver: Boolean) {
callback: MatrixCallback<Unit>): Cancelable { return signOutTask.execute(SignOutTask.Params(signOutFromHomeserver))
return signOutTask
.configureWith(SignOutTask.Params(signOutFromHomeserver)) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
} }

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.platform
import com.airbnb.mvrx.MvRxState
data class EmptyState(
val dummy: Int = 0
) : MvRxState

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.platform
/**
* Mainly used to get a viewModelScope
*/
class EmptyViewModel(initialState: EmptyState) : VectorViewModel<EmptyState, EmptyAction, EmptyViewEvents>(initialState) {
override fun handle(action: EmptyAction) {
// N/A
}
}

View File

@ -22,12 +22,15 @@ import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.viewModel
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenComponent import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.startSyncing import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.platform.EmptyViewModel
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.deleteAllFiles import im.vector.app.core.utils.deleteAllFiles
import im.vector.app.databinding.FragmentLoadingBinding import im.vector.app.databinding.FragmentLoadingBinding
@ -45,10 +48,8 @@ import im.vector.app.features.signout.soft.SoftLogoutActivity
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.failure.GlobalError
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -82,6 +83,8 @@ class MainActivity : VectorBaseActivity<FragmentLoadingBinding>(), UnlockedActiv
} }
} }
private val emptyViewModel: EmptyViewModel by viewModel()
override fun getBinding() = FragmentLoadingBinding.inflate(layoutInflater) override fun getBinding() = FragmentLoadingBinding.inflate(layoutInflater)
private lateinit var args: MainActivityArgs private lateinit var args: MainActivityArgs
@ -147,38 +150,39 @@ class MainActivity : VectorBaseActivity<FragmentLoadingBinding>(), UnlockedActiv
} }
when { when {
args.isAccountDeactivated -> { args.isAccountDeactivated -> {
// Just do the local cleanup emptyViewModel.viewModelScope.launch {
Timber.w("Account deactivated, start app") // Just do the local cleanup
sessionHolder.clearActiveSession() Timber.w("Account deactivated, start app")
doLocalCleanup(clearPreferences = true) sessionHolder.clearActiveSession()
startNextActivityAndFinish() doLocalCleanup(clearPreferences = true)
startNextActivityAndFinish()
}
}
args.clearCredentials -> {
emptyViewModel.viewModelScope.launch {
try {
session.signOut(!args.isUserLoggedOut)
Timber.w("SIGN_OUT: success, start app")
sessionHolder.clearActiveSession()
doLocalCleanup(clearPreferences = true)
startNextActivityAndFinish()
} catch (failure: Throwable) {
displayError(failure)
}
}
}
args.clearCache -> {
emptyViewModel.viewModelScope.launch {
try {
session.clearCache()
doLocalCleanup(clearPreferences = false)
session.startSyncing(applicationContext)
startNextActivityAndFinish()
} catch (failure: Throwable) {
displayError(failure)
}
}
} }
args.clearCredentials -> session.signOut(
!args.isUserLoggedOut,
object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.w("SIGN_OUT: success, start app")
sessionHolder.clearActiveSession()
doLocalCleanup(clearPreferences = true)
startNextActivityAndFinish()
}
override fun onFailure(failure: Throwable) {
displayError(failure)
}
})
args.clearCache -> session.clearCache(
object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
doLocalCleanup(clearPreferences = false)
session.startSyncing(applicationContext)
startNextActivityAndFinish()
}
override fun onFailure(failure: Throwable) {
displayError(failure)
}
})
} }
} }
@ -187,24 +191,22 @@ class MainActivity : VectorBaseActivity<FragmentLoadingBinding>(), UnlockedActiv
Timber.w("Ignoring invalid token global error") Timber.w("Ignoring invalid token global error")
} }
private fun doLocalCleanup(clearPreferences: Boolean) { private suspend fun doLocalCleanup(clearPreferences: Boolean) {
GlobalScope.launch(Dispatchers.Main) { // On UI Thread
// On UI Thread Glide.get(this@MainActivity).clearMemory()
Glide.get(this@MainActivity).clearMemory()
if (clearPreferences) { if (clearPreferences) {
vectorPreferences.clearPreferences() vectorPreferences.clearPreferences()
uiStateRepository.reset() uiStateRepository.reset()
pinLocker.unlock() pinLocker.unlock()
pinCodeStore.deleteEncodedPin() pinCodeStore.deleteEncodedPin()
} }
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
// On BG thread // On BG thread
Glide.get(this@MainActivity).clearDiskCache() Glide.get(this@MainActivity).clearDiskCache()
// Also clear cache (Logs, etc...) // Also clear cache (Logs, etc...)
deleteAllFiles(this@MainActivity.cacheDir) deleteAllFiles(this@MainActivity.cacheDir)
}
} }
} }

View File

@ -19,10 +19,13 @@ package im.vector.app.features.link
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.viewModel
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenComponent import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.platform.EmptyViewModel
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivityProgressBinding import im.vector.app.databinding.ActivityProgressBinding
@ -30,7 +33,7 @@ import im.vector.app.features.login.LoginActivity
import im.vector.app.features.login.LoginConfig import im.vector.app.features.login.LoginConfig
import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.permalink.PermalinkHandler
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import org.matrix.android.sdk.api.MatrixCallback import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -45,6 +48,8 @@ class LinkHandlerActivity : VectorBaseActivity<ActivityProgressBinding>() {
@Inject lateinit var errorFormatter: ErrorFormatter @Inject lateinit var errorFormatter: ErrorFormatter
@Inject lateinit var permalinkHandler: PermalinkHandler @Inject lateinit var permalinkHandler: PermalinkHandler
private val emptyViewModel: EmptyViewModel by viewModel()
override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
injector.inject(this) injector.inject(this)
} }
@ -139,23 +144,30 @@ class LinkHandlerActivity : VectorBaseActivity<ActivityProgressBinding>() {
.setTitle(R.string.dialog_title_warning) .setTitle(R.string.dialog_title_warning)
.setMessage(R.string.error_user_already_logged_in) .setMessage(R.string.error_user_already_logged_in)
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.logout) { _, _ -> .setPositiveButton(R.string.logout) { _, _ -> safeSignout(uri) }
sessionHolder.getSafeActiveSession()?.signOut(true, object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
displayError(failure)
}
override fun onSuccess(data: Unit) {
Timber.d("## displayAlreadyLoginPopup(): logout succeeded")
sessionHolder.clearActiveSession()
startLoginActivity(uri)
}
}) ?: finish()
}
.setNegativeButton(R.string.cancel) { _, _ -> finish() } .setNegativeButton(R.string.cancel) { _, _ -> finish() }
.show() .show()
} }
private fun safeSignout(uri: Uri) {
val session = sessionHolder.getSafeActiveSession()
if (session == null) {
// Should not happen
startLoginActivity(uri)
} else {
emptyViewModel.viewModelScope.launch {
try {
session.signOut(true)
Timber.d("## displayAlreadyLoginPopup(): logout succeeded")
sessionHolder.clearActiveSession()
startLoginActivity(uri)
} catch (failure: Throwable) {
displayError(failure)
}
}
}
}
private fun displayError(failure: Throwable) { private fun displayError(failure: Throwable) {
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle(R.string.dialog_title_error) .setTitle(R.string.dialog_title_error)

View File

@ -16,6 +16,7 @@
package im.vector.app.features.signout.soft package im.vector.app.features.signout.soft
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
@ -30,6 +31,7 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.hasUnsavedKeys import im.vector.app.core.extensions.hasUnsavedKeys
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.login.LoginMode import im.vector.app.features.login.LoginMode
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.LoginFlowResult import org.matrix.android.sdk.api.auth.data.LoginFlowResult
@ -174,22 +176,19 @@ class SoftLogoutViewModel @AssistedInject constructor(
asyncLoginAction = Loading() asyncLoginAction = Loading()
) )
} }
currentTask = session.updateCredentials(action.credentials, viewModelScope.launch {
object : MatrixCallback<Unit> { try {
override fun onFailure(failure: Throwable) { session.updateCredentials(action.credentials)
_viewEvents.post(SoftLogoutViewEvents.Failure(failure)) onSessionRestored()
setState { } catch (failure: Throwable) {
copy( _viewEvents.post(SoftLogoutViewEvents.Failure(failure))
asyncLoginAction = Uninitialized setState {
) copy(
} asyncLoginAction = Uninitialized
} )
override fun onSuccess(data: Unit) {
onSessionRestored()
}
} }
) }
}
} }
} }
} }
@ -202,21 +201,18 @@ class SoftLogoutViewModel @AssistedInject constructor(
passwordShown = false passwordShown = false
) )
} }
currentTask = session.signInAgain(action.password, viewModelScope.launch {
object : MatrixCallback<Unit> { try {
override fun onFailure(failure: Throwable) { session.signInAgain(action.password)
setState { onSessionRestored()
copy( } catch (failure: Throwable) {
asyncLoginAction = Fail(failure) setState {
) copy(
} asyncLoginAction = Fail(failure)
} )
override fun onSuccess(data: Unit) {
onSessionRestored()
}
} }
) }
}
} }
private fun onSessionRestored() { private fun onSessionRestored() {