Skip to content

Commit

Permalink
Refactor the unmount listener to anki activity.
Browse files Browse the repository at this point in the history
This ensure that the same receiver appears in all activities and not
just the few one where the code was essentially copy pasted.

The Deck Picker also listen for mounting, so I renamed its receiver as
a `mountReceiver` instead
  • Loading branch information
Arthur-Milchior committed Sep 23, 2024
1 parent 4eab9d1 commit b0c3b08
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ package com.ichi2.anki
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.res.Configuration
import android.graphics.Bitmap
Expand Down Expand Up @@ -72,7 +70,6 @@ import androidx.annotation.CheckResult
import androidx.annotation.IdRes
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.children
Expand Down Expand Up @@ -110,7 +107,6 @@ import com.ichi2.anki.pages.AnkiServer
import com.ichi2.anki.pages.CongratsPage
import com.ichi2.anki.pages.PostRequestHandler
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.receiver.SdCardReceiver
import com.ichi2.anki.reviewer.AutomaticAnswer
import com.ichi2.anki.reviewer.AutomaticAnswer.AutomaticallyAnswered
import com.ichi2.anki.reviewer.AutomaticAnswerAction
Expand All @@ -129,7 +125,6 @@ import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.utils.OnlyOnce.Method.ANSWER_CARD
import com.ichi2.anki.utils.OnlyOnce.preventSimultaneousExecutions
import com.ichi2.annotations.NeedsTest
import com.ichi2.compat.CompatHelper.Companion.registerReceiverCompat
import com.ichi2.compat.CompatHelper.Companion.resolveActivityCompat
import com.ichi2.compat.ResolveInfoFlagsCompat
import com.ichi2.libanki.Card
Expand Down Expand Up @@ -192,10 +187,6 @@ abstract class AbstractFlashcardViewer :
@VisibleForTesting
val jsApi by lazy { AnkiDroidJsAPI(this) }

/**
* Broadcast that informs us when the sd card is about to be unmounted
*/
private var unmountReceiver: BroadcastReceiver? = null
private var tagsDialogFactory: TagsDialogFactory? = null

/**
Expand Down Expand Up @@ -590,7 +581,7 @@ abstract class AbstractFlashcardViewer :
super.onCollectionLoaded(col)
val mediaDir = col.media.dir
cardMediaPlayer = CardMediaPlayer.newInstance(this, getMediaBaseUrl(mediaDir))
registerExternalStorageListener()
registerReceiver()
restoreCollectionPreferences(col)
initLayout()
cardRenderContext = createInstance(this, col, typeAnswer!!)
Expand Down Expand Up @@ -653,9 +644,6 @@ abstract class AbstractFlashcardViewer :
override fun onDestroy() {
super.onDestroy()
tts.releaseTts(this)
if (unmountReceiver != null) {
unregisterReceiver(unmountReceiver)
}
// WebView.destroy() should be called after the end of use
// http://developer.android.com/reference/android/webkit/WebView.html#destroy()
if (cardFrame != null) {
Expand Down Expand Up @@ -805,24 +793,6 @@ abstract class AbstractFlashcardViewer :
return cardContent != null
}

/**
* Show/dismiss dialog when sd card is ejected/remounted (collection is saved by SdCardReceiver)
*/
private fun registerExternalStorageListener() {
if (unmountReceiver == null) {
unmountReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == SdCardReceiver.MEDIA_EJECT) {
finish()
}
}
}
val iFilter = IntentFilter()
iFilter.addAction(SdCardReceiver.MEDIA_EJECT)
registerReceiverCompat(unmountReceiver, iFilter, ContextCompat.RECEIVER_EXPORTED)
}
}

open fun undo(): Job {
return launchCatchingTask {
undoAndShowSnackbar(duration = Reviewer.ACTION_SNACKBAR_TIME)
Expand Down
50 changes: 50 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ package com.ichi2.anki
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.BitmapFactory
import android.graphics.Color
import android.media.AudioManager
Expand All @@ -16,6 +19,7 @@ import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import android.view.animation.Animation
import android.widget.ProgressBar
Expand All @@ -35,6 +39,7 @@ import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_LIGHT
import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_SYSTEM
import androidx.core.app.NotificationCompat
import androidx.core.app.PendingIntentCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
Expand All @@ -51,9 +56,11 @@ import com.ichi2.anki.dialogs.SimpleMessageDialog.SimpleMessageDialogListener
import com.ichi2.anki.preferences.Preferences
import com.ichi2.anki.preferences.Preferences.Companion.MINIMUM_CARDS_DUE_FOR_NOTIFICATION
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.receiver.SdCardReceiver
import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.workarounds.AppLoadedFromBackupWorkaround.showedActivityFailedScreen
import com.ichi2.async.CollectionLoader
import com.ichi2.compat.CompatHelper.Companion.registerReceiverCompat
import com.ichi2.compat.customtabs.CustomTabActivityHelper
import com.ichi2.compat.customtabs.CustomTabsFallback
import com.ichi2.compat.customtabs.CustomTabsHelper
Expand All @@ -68,6 +75,11 @@ import androidx.browser.customtabs.CustomTabsIntent.Builder as CustomTabsIntentB
@KotlinCleanup("set activityName")
open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener {

/**
* Broadcast that informs us when the sd card is about to be unmounted
*/
private var receiver: BroadcastReceiver? = null

/** The name of the parent class (example: 'Reviewer') */
private val activityName: String
val dialogHandler = DialogHandler(this)
Expand Down Expand Up @@ -112,6 +124,11 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener {
customTabActivityHelper.unbindCustomTabsService(this)
}

override fun onDestroy() {
super.onDestroy()
receiver?.let { unregisterReceiver(it) }
}

override fun onResume() {
super.onResume()
UsageAnalytics.sendAnalyticsScreenView(this)
Expand Down Expand Up @@ -150,6 +167,39 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener {
hideProgressBar()
}

/**
* Maps from intent name action to function to run when this action is received by [receiver].
* By default it handles [SdCardReceiver.MEDIA_EJECT], and show/dismiss dialog when sd card is ejected/remounted (collection is saved by SdCardReceiver)
*/
protected open val broadcastsActions = mapOf(
SdCardReceiver.MEDIA_EJECT to { onSdCardNotMounted() }
)

/**
* Register a broadcast receiver, associating an intent to an action as in [broadcastsActions].
* Add more values in [broadcastsActions] to react to more intents.
*/
protected fun registerReceiver() {
if (receiver != null) {
// Receiver already registered
return
}
receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
broadcastsActions[intent.action]?.invoke()
}
}.also {
val iFilter = IntentFilter()
broadcastsActions.keys.map(iFilter::addAction)
registerReceiverCompat(it, iFilter, ContextCompat.RECEIVER_EXPORTED)
}
}

protected fun onSdCardNotMounted() {
showThemedToast(this, resources.getString(R.string.sd_card_not_mounted), false)
finish()
}

/** Legacy code should migrate away from this, and use withCol {} instead.
* */
val getColUnsafe: Collection
Expand Down
33 changes: 1 addition & 32 deletions AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@

package com.ichi2.anki

import android.content.BroadcastReceiver
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Configuration
import android.os.Bundle
import android.os.SystemClock
Expand Down Expand Up @@ -52,7 +50,6 @@ import androidx.annotation.MainThread
import androidx.annotation.VisibleForTesting
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.ThemeUtils
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import anki.collection.OpChanges
Expand Down Expand Up @@ -99,7 +96,6 @@ import com.ichi2.anki.model.SortType
import com.ichi2.anki.noteeditor.NoteEditorLauncher
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.previewer.PreviewerFragment
import com.ichi2.anki.receiver.SdCardReceiver
import com.ichi2.anki.scheduling.ForgetCardsDialog
import com.ichi2.anki.scheduling.SetDueDateDialog
import com.ichi2.anki.servicelayer.NoteService
Expand All @@ -116,7 +112,6 @@ import com.ichi2.anki.utils.roundedTimeSpanUnformatted
import com.ichi2.anki.widgets.DeckDropDownAdapter.SubtitleListener
import com.ichi2.annotations.NeedsTest
import com.ichi2.async.renderBrowserQA
import com.ichi2.compat.CompatHelper.Companion.registerReceiverCompat
import com.ichi2.libanki.Card
import com.ichi2.libanki.CardId
import com.ichi2.libanki.ChangeManager
Expand Down Expand Up @@ -270,11 +265,6 @@ open class CardBrowser :
private var shouldRestoreScroll = false
private var postAutoScroll = false

/**
* Broadcast that informs us when the sd card is about to be unmounted
*/
private var unmountReceiver: BroadcastReceiver? = null

init {
ChangeManager.subscribe(this)
}
Expand Down Expand Up @@ -584,7 +574,7 @@ open class CardBrowser :
override fun onCollectionLoaded(col: Collection) {
super.onCollectionLoaded(col)
Timber.d("onCollectionLoaded()")
registerExternalStorageListener()
registerReceiver()
cards.reset()

cardsListView.setOnItemClickListener { _: AdapterView<*>?, view: View?, position: Int, _: Long ->
Expand Down Expand Up @@ -888,9 +878,6 @@ open class CardBrowser :
override fun onDestroy() {
invalidate()
super.onDestroy()
if (unmountReceiver != null) {
unregisterReceiver(unmountReceiver)
}
}

@Deprecated("Deprecated in Java")
Expand Down Expand Up @@ -2323,24 +2310,6 @@ open class CardBrowser :
}
}

/**
* Show/dismiss dialog when sd card is ejected/remounted (collection is saved by SdCardReceiver)
*/
private fun registerExternalStorageListener() {
if (unmountReceiver == null) {
unmountReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == SdCardReceiver.MEDIA_EJECT) {
finish()
}
}
}
val iFilter = IntentFilter()
iFilter.addAction(SdCardReceiver.MEDIA_EJECT)
registerReceiverCompat(unmountReceiver, iFilter, ContextCompat.RECEIVER_EXPORTED)
}
}

/**
* The views expand / contract when switching between multi-select mode so we manually
* adjust so that the vertical position of the given view is maintained
Expand Down
39 changes: 6 additions & 33 deletions AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@
package com.ichi2.anki

import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.database.SQLException
import android.graphics.PixelFormat
Expand Down Expand Up @@ -59,7 +57,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView
import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
Expand Down Expand Up @@ -154,7 +151,6 @@ import com.ichi2.annotations.NeedsTest
import com.ichi2.async.deleteMedia
import com.ichi2.compat.CompatHelper
import com.ichi2.compat.CompatHelper.Companion.getSerializableCompat
import com.ichi2.compat.CompatHelper.Companion.registerReceiverCompat
import com.ichi2.compat.CompatHelper.Companion.sdkVersion
import com.ichi2.libanki.ChangeManager
import com.ichi2.libanki.Consts
Expand Down Expand Up @@ -264,8 +260,6 @@ open class DeckPicker :

private lateinit var reviewSummaryTextView: TextView

@KotlinCleanup("make lateinit, but needs more changes")
private var unmountReceiver: BroadcastReceiver? = null
private lateinit var floatingActionMenu: DeckPickerFloatingActionMenu

// flag asking user to do a full sync which is used in upgrade path
Expand Down Expand Up @@ -503,7 +497,7 @@ open class DeckPicker :
if (fragmented && !startupError) {
loadStudyOptionsFragment(false)
}
registerExternalStorageListener()
registerReceiver()

// create inherited navigation drawer layout here so that it can be used by parent class
initNavigationDrawer(mainView)
Expand Down Expand Up @@ -1167,9 +1161,6 @@ open class DeckPicker :

override fun onDestroy() {
super.onDestroy()
if (unmountReceiver != null) {
unregisterReceiver(unmountReceiver)
}
if (progressDialog != null && progressDialog!!.isShowing) {
progressDialog!!.dismiss()
}
Expand Down Expand Up @@ -1702,11 +1693,6 @@ open class DeckPicker :
showAsyncDialogFragment(newFragment, Channel.SYNC)
}

fun onSdCardNotMounted() {
showThemedToast(this, resources.getString(R.string.sd_card_not_mounted), false)
finish()
}

// Callback method to submit error report
fun sendErrorReport() {
CrashReportService.sendExceptionReport(RuntimeException(), "DeckPicker.sendErrorReport")
Expand Down Expand Up @@ -1898,25 +1884,12 @@ open class DeckPicker :
}

/**
* Show a message when the SD card is ejected
* Refresh the deck pickre when the SD card is inserted.
*/
private fun registerExternalStorageListener() {
if (unmountReceiver == null) {
unmountReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == SdCardReceiver.MEDIA_EJECT) {
onSdCardNotMounted()
} else if (intent.action == SdCardReceiver.MEDIA_MOUNT) {
ActivityCompat.recreate(this@DeckPicker)
}
}
}
val iFilter = IntentFilter()
iFilter.addAction(SdCardReceiver.MEDIA_EJECT)
iFilter.addAction(SdCardReceiver.MEDIA_MOUNT)
registerReceiverCompat(unmountReceiver, iFilter, ContextCompat.RECEIVER_EXPORTED)
}
}
override val broadcastsActions = super.broadcastsActions + mapOf(
SdCardReceiver.MEDIA_MOUNT
to { ActivityCompat.recreate(this) }
)

fun openAnkiWebSharedDecks() {
val intent = Intent(this, SharedDecksActivity::class.java)
Expand Down
Loading

0 comments on commit b0c3b08

Please sign in to comment.