Skip to content
Snippets Groups Projects
Verified Commit 3ddd0abc authored by Sebastian's avatar Sebastian
Browse files

Improve behavior when restarting app via app lauchner

parent a02493fd
No related branches found
No related tags found
1 merge request!116Improve behavior when restarting app via app launcher
......@@ -26,7 +26,6 @@ import androidx.annotation.StringRes
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
......@@ -74,7 +73,7 @@ class StatusManager @Inject constructor(
object Stopped : MailboxAppState()
@Suppress("OPT_IN_USAGE")
val appState: Flow<MailboxAppState> = combine(
val appState: StateFlow<MailboxAppState> = combine(
lifecycleState, torPluginState, setupComplete
) { ls, ts, sc ->
when {
......
......@@ -33,6 +33,7 @@ import org.briarproject.android.dontkillmelib.DozeHelper
import org.briarproject.mailbox.android.MailboxPreferences
import org.briarproject.mailbox.android.MailboxService
import org.briarproject.mailbox.android.StatusManager
import org.briarproject.mailbox.android.StatusManager.MailboxAppState
import org.briarproject.mailbox.core.lifecycle.LifecycleManager
import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState
import org.briarproject.mailbox.core.settings.MetadataManager
......@@ -75,7 +76,7 @@ class MailboxViewModel @Inject constructor(
setupManager.hasDb
}
val appState = statusManager.appState
val appState: StateFlow<MailboxAppState> = statusManager.appState
val lastAccess: LiveData<Long> = metadataManager.ownerConnectionTime.asLiveData()
......
......@@ -57,6 +57,7 @@ import org.briarproject.mailbox.android.StatusManager.Wiping
import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.NOT_STARTED
import org.briarproject.mailbox.core.util.LogUtils.info
import org.slf4j.LoggerFactory.getLogger
import java.util.concurrent.atomic.AtomicBoolean
@AndroidEntryPoint
class MainActivity : AppCompatActivity(), ActivityResultCallback<ActivityResult> {
......@@ -64,7 +65,8 @@ class MainActivity : AppCompatActivity(), ActivityResultCallback<ActivityResult>
companion object {
private val LOG = getLogger(MainActivity::class.java)
const val BUNDLE_LIFECYCLE_BEYOND_NOT_STARTED = "LIFECYCLE_BEYOND_NOT_STARTED"
const val BUNDLE_LIFECYCLE_HAS_STARTED = "LIFECYCLE_HAS_STARTED"
const val BUNDLE_ONBOARDING_DONE = "ONBOARDING_DONE"
}
private val viewModel: MailboxViewModel by viewModels()
......@@ -76,6 +78,10 @@ class MainActivity : AppCompatActivity(), ActivityResultCallback<ActivityResult>
private val startForResult = registerForActivityResult(StartActivityForResult(), this)
private var hadBeenStartedOnSave = false
private var onboardingLaunched = AtomicBoolean(false)
private var onboardingDone = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LOG.info("onCreate()")
......@@ -87,17 +93,28 @@ class MainActivity : AppCompatActivity(), ActivityResultCallback<ActivityResult>
)
}
LOG.info { "do we have a saved instance state? " + (savedInstanceState != null) }
hadBeenStartedOnSave =
savedInstanceState?.getBoolean(BUNDLE_LIFECYCLE_HAS_STARTED) ?: false
onboardingDone = savedInstanceState?.getBoolean(BUNDLE_ONBOARDING_DONE) ?: false
launchAndRepeatWhileStarted {
viewModel.appState.collect { onAppStateChanged(it) }
}
LOG.info { "do we have a saved instance state? " + (savedInstanceState != null) }
checkForDbAsync()
}
lifecycleScope.launch {
val hasDb = viewModel.hasDb()
LOG.info { "do we have a db? $hasDb" }
onDbChecked(hasDb, savedInstanceState)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
LOG.info("onNewIntent()")
// We end up here when relaunching from the app icon. If the activity already existed, but
// the onboarding activity was on top, then the onboarding activity was finished because
// MainActivity has launchMode=singleTask and onCreate() will not necessarily be called
// before we reach this point.
onboardingLaunched.set(false)
if (viewModel.appState.value == NotStarted) checkForDbAsync()
}
private fun onAppStateChanged(state: MailboxAppState) {
......@@ -125,51 +142,69 @@ class MainActivity : AppCompatActivity(), ActivityResultCallback<ActivityResult>
}
}
private fun onDbChecked(hasDb: Boolean, savedInstanceState: Bundle?) {
private fun checkForDbAsync() {
lifecycleScope.launch {
val hasDb = viewModel.hasDb()
LOG.info(
"hasDb? $hasDb, hadBeenStartedOnSave? $hadBeenStartedOnSave, " +
"onboarding launched? $onboardingLaunched, onboarding done? $onboardingDone"
)
onDbChecked(hasDb)
}
}
private fun onDbChecked(hasDb: Boolean) {
if (lifecycle.currentState == DESTROYED) {
return
}
if (savedInstanceState == null) {
if (!hasDb) {
/** This is for the situation when we come back to the activity after a remote wipe has
* happened while the app was in the background and gets restored from the recent app list
* after wiping and stopping has already completed.
* In this case onSaveInstanceState() has written true to the bundle and this value
* got restored to [hadBeenStartedOnSave]. */
if (hadBeenStartedOnSave && !hasDb) {
finish()
startActivity(Intent(this, WipeCompleteActivity::class.java))
return
}
if (!hasDb && !onboardingDone) {
// Make sure not to launch onboarding twice. In case of MainActivity being killed (due
// to do-not-keep activities option enabled), onDbChecked() gets called twice when
// relaunched via app launcher (through onNewIntent()).
if (!onboardingLaunched.getAndSet(true)) {
startForResult.launch(Intent(this, OnboardingActivity::class.java))
} else if (needsDozeWhitelisting(this)) {
nav.navigate(actionGlobalDoNotKillMeFragment())
} else {
nav.navigate(actionGlobalStartupFragment())
}
return
}
if (viewModel.needToShowDoNotKillMeFragment) {
nav.navigate(actionGlobalDoNotKillMeFragment())
} else {
// At this point, when we do not have a db, this can be either of two situations:
// 1. We just came back from the onboarding activity and our MainActivity has been
// destroyed in the meantime (can be forced using the do-not-keep-activities developer
// option). In this case onSaveInstanceState() has written false to the bundle.
// 2. We come back to the activity after a remote wipe has happened while the app was
// in the background and gets restored from the recent app list after wiping and
// stopping has already completed. In this case onSaveInstanceState() has written
// true to the bundle.
val savedBeyondNotStarted =
savedInstanceState.getBoolean(BUNDLE_LIFECYCLE_BEYOND_NOT_STARTED)
if (!hasDb && savedBeyondNotStarted) {
finish()
startActivity(Intent(this, WipeCompleteActivity::class.java))
}
nav.navigate(actionGlobalStartupFragment())
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(
BUNDLE_LIFECYCLE_BEYOND_NOT_STARTED,
BUNDLE_LIFECYCLE_HAS_STARTED,
viewModel.lifecycleState.value != NOT_STARTED
)
outState.putBoolean(BUNDLE_ONBOARDING_DONE, onboardingDone)
}
override fun onActivityResult(result: ActivityResult) {
// only show next fragment when user went throw onboarding
// result doesn't matter as we kill the app when user backs out in onboarding
if (viewModel.needToShowDoNotKillMeFragment) {
nav.navigate(actionGlobalDoNotKillMeFragment())
} else {
nav.navigate(actionGlobalStartupFragment())
// Only show next fragment when user went through onboarding. We receive RESULT_CANCEL here
// when the activity is being relaunched through the app icon and the onboarding activity
// gets removed and the existing MainActivity is reused.
if (result.resultCode == RESULT_OK) {
onboardingDone = true
if (viewModel.needToShowDoNotKillMeFragment) {
nav.navigate(actionGlobalDoNotKillMeFragment())
} else {
nav.navigate(actionGlobalStartupFragment())
}
}
}
......
......@@ -19,6 +19,7 @@
package org.briarproject.mailbox.android.ui
import android.app.Activity.RESULT_OK
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
......@@ -40,7 +41,7 @@ class Onboarding0Fragment : OnboardingFragment(
description = R.string.onboarding_0_description,
bottomButtonText = R.string.button_skip_intro,
bottomButtonAction = {
requireActivity().supportFinishAfterTransition()
finishDone()
},
backButtonAction = {
requireActivity().finishAffinity()
......@@ -151,6 +152,12 @@ abstract class OnboardingFragment(
class FinishFragment : Fragment() {
override fun onResume() {
super.onResume()
requireActivity().supportFinishAfterTransition()
finishDone()
}
}
private fun Fragment.finishDone() {
requireActivity().setResult(RESULT_OK)
requireActivity().supportFinishAfterTransition()
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment