Repository: thundernest/k-9
Issue: Tapping the message list with two fingers could crash the app

### Checklist

- [X] I have used the search function to see if someone else has already submitted the same bug report.
- [X] I will describe the problem with as much detail as possible.

### App version

38547c22e4c40683d6a48d2581a08c37d27bba2b

### Where did you get the app from?

_No response_

### Android version

12

### Device model

_No response_

### Steps to reproduce

1. Open a folder with two threads that are displayed next to each other in the message list
2. Use two fingers to tap both threads in the message list at roughly the same time
3. Crash

Note: For the crash to happen only the first item clicked needs to be a thread. However, it's easier to reproduce if both list items are threads. Then the order of clicks doesn't matter.

### Expected behavior

Only one of the two threads will be opened. No crash.

### Actual behavior

Android posts two click events to the event queue. The second one is processed when the receiving `MessageListFragment` has already been moved to the back stack. However, it looks like the Fragment hasn't been properly paused yet, i.e. `isResumed`, `isAdded` etc still return `true` when the second click event is handled.

We should probably ignore click events that happen shortly after a click event to open a message has been processed. However, we don't want to do this when tapping a message list item will (de)select it. Selecting two messages using two fingers is fine and doesn't lead to problems.
But we definitely want to ignore click events that are delivered after the Fragment has been put on the back stack.

### Logs

```text
java.lang.IllegalStateException: Fragment MessageListFragment{45965f8} (7d7a06a7-b45b-42f1-b631-c7502fffef46 id=0x7f090271) not attached to a context.
   at androidx.fragment.app.Fragment.requireContext(Fragment.java:967)
   at androidx.fragment.app.Fragment.getDefaultViewModelCreationExtras(Fragment.java:474)
   at androidx.fragment.app.FragmentViewModelLazyKt$createViewModelLazy$1.invoke(FragmentViewModelLazy.kt:198)
   at androidx.fragment.app.FragmentViewModelLazyKt$createViewModelLazy$1.invoke(FragmentViewModelLazy.kt:195)
   at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:52)
   at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:35)
   at com.fsck.k9.fragment.MessageListFragment.getViewModel(MessageListFragment.kt:68)
   at com.fsck.k9.fragment.MessageListFragment.rememberSortOverride(MessageListFragment.kt:1339)
   at com.fsck.k9.fragment.MessageListFragment.setActiveMessage(MessageListFragment.kt:1316)
   at com.fsck.k9.activity.MessageList.showMessageViewPlaceHolder(MessageList.kt:1146)
   at com.fsck.k9.activity.MessageList.showThread(MessageList.kt:1122)
   at com.fsck.k9.fragment.MessageListFragment.onMessageClicked(MessageListFragment.kt:395)
   at com.fsck.k9.fragment.MessageListAdapter.messageClickedListener$lambda-6(MessageListAdapter.kt:174)
   at com.fsck.k9.fragment.MessageListAdapter.$r8$lambda$eGP6kgU1B27QiIncRujnT-sMY1w(Unknown Source:0)
   at com.fsck.k9.fragment.MessageListAdapter$$ExternalSyntheticLambda1.onClick(Unknown Source:2)
   at android.view.View.performClick(View.java:7441)
   at android.view.View.performClickInternal(View.java:7418)
   at android.view.View.access$3700(View.java:835)
   at android.view.View$PerformClick.run(View.java:28676)
   at android.os.Handler.handleCallback(Handler.java:938)
   at android.os.Handler.dispatchMessage(Handler.java:99)
   at android.os.Looper.loopOnce(Looper.java:201)
   at android.os.Looper.loop(Looper.java:288)
   at android.app.ActivityThread.main(ActivityThread.java:7839)
   at java.lang.reflect.Method.invoke(Native Method)
   at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
```

Changed files:
app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt, app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt

diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
index a78a33697..53627c9b4 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
@@ -1071,6 +1071,8 @@ open class MessageList :
     }
 
     private fun addMessageListFragment(fragment: MessageListFragment) {
+        messageListFragment?.isActive = false
+
         supportFragmentManager.commit {
             replace(R.id.message_list_container, fragment)
 
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
index 4308364e1..9f7d5af8a 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
@@ -6,6 +6,7 @@ import android.content.Context
 import android.content.Intent
 import android.os.Bundle
 import android.os.Parcelable
+import android.os.SystemClock
 import android.view.LayoutInflater
 import android.view.Menu
 import android.view.MenuItem
@@ -59,6 +60,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
 import timber.log.Timber
 
 private const val MAXIMUM_MESSAGE_SORT_OVERRIDES = 3
+private const val MINIMUM_CLICK_INTERVAL = 200L
 
 class MessageListFragment :
     Fragment(),
@@ -105,6 +107,7 @@ class MessageListFragment :
     private var isThreadDisplay = false
     private var activeMessage: MessageReference? = null
     private var rememberedSelected: Set<Long>? = null
+    private var lastMessageClick = 0L
 
     lateinit var localSearch: LocalSearch
         private set
@@ -388,9 +391,19 @@ class MessageListFragment :
     }
 
     override fun onMessageClicked(messageListItem: MessageListItem) {
+        if (!isActive) {
+            // Ignore click events that are delivered after the Fragment is no longer active. This could happen when
+            // the user taps two messages at almost the same time and the first tap opens a new MessageListFragment.
+            return
+        }
+
+        val clickTime = SystemClock.elapsedRealtime()
+        if (clickTime - lastMessageClick < MINIMUM_CLICK_INTERVAL) return
+
         if (adapter.selectedCount > 0) {
             toggleMessageSelect(messageListItem)
         } else {
+            lastMessageClick = clickTime
             if (showingThreadedList && messageListItem.threadCount > 1) {
                 fragmentListener.showThread(messageListItem.account, messageListItem.threadRoot)
             } else {