Skip to content

Commit

Permalink
fix: handle database-related errors / import in AnkiActivity
Browse files Browse the repository at this point in the history
this removes all the casting to DeckPicker that causes crashes at
the worst possible time (like when you lost collection permission
and the PermissionActivity is not a DeckPicker type)
  • Loading branch information
mikehardy committed Nov 27, 2024
1 parent c601271 commit 5109273
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 47 deletions.
10 changes: 10 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ import com.ichi2.anki.android.input.ShortcutGroup
import com.ichi2.anki.android.input.ShortcutGroupProvider
import com.ichi2.anki.android.input.shortcut
import com.ichi2.anki.dialogs.AsyncDialogFragment
import com.ichi2.anki.dialogs.DatabaseErrorDialog
import com.ichi2.anki.dialogs.DatabaseErrorDialog.DatabaseErrorDialogType
import com.ichi2.anki.dialogs.DialogHandler
import com.ichi2.anki.dialogs.SimpleMessageDialog
import com.ichi2.anki.dialogs.SimpleMessageDialog.SimpleMessageDialogListener
Expand Down Expand Up @@ -89,6 +91,8 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener, Shortc
*/
private var broadcastReceiver: BroadcastReceiver? = null

var importColpkgListener: ImportColpkgListener? = null

/** The name of the parent class (example: 'Reviewer') */
private val activityName: String
val dialogHandler = DialogHandler(this)
Expand Down Expand Up @@ -589,6 +593,12 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener, Shortc
}
}

// Show dialogs to deal with database loading issues etc
open fun showDatabaseErrorDialog(errorDialogType: DatabaseErrorDialogType) {
val newFragment: AsyncDialogFragment = DatabaseErrorDialog.newInstance(errorDialogType)
showAsyncDialogFragment(newFragment)
}

/**
* sets [.getSupportActionBar] and returns the action bar
* @return The action bar which was created
Expand Down
17 changes: 4 additions & 13 deletions AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ import com.ichi2.anki.dialogs.AsyncDialogFragment
import com.ichi2.anki.dialogs.BackupPromptDialog
import com.ichi2.anki.dialogs.ConfirmationDialog
import com.ichi2.anki.dialogs.CreateDeckDialog
import com.ichi2.anki.dialogs.DatabaseErrorDialog
import com.ichi2.anki.dialogs.DatabaseErrorDialog.DatabaseErrorDialogType
import com.ichi2.anki.dialogs.DeckPickerAnalyticsOptInDialog
import com.ichi2.anki.dialogs.DeckPickerBackupNoSpaceLeftDialog
Expand Down Expand Up @@ -321,8 +320,6 @@ open class DeckPicker :
@VisibleForTesting
internal var focusedDeck: DeckId = 0

var importColpkgListener: ImportColpkgListener? = null

private var toolbarSearchItem: MenuItem? = null
private var toolbarSearchView: AccessibleSearchView? = null
private lateinit var customStudyDialogFactory: CustomStudyDialogFactory
Expand Down Expand Up @@ -1708,12 +1705,6 @@ open class DeckPicker :
}
}

// Show dialogs to deal with database loading issues etc
open fun showDatabaseErrorDialog(errorDialogType: DatabaseErrorDialogType) {
val newFragment: AsyncDialogFragment = DatabaseErrorDialog.newInstance(errorDialogType)
showAsyncDialogFragment(newFragment)
}

override fun showMediaCheckDialog(dialogType: Int) {
showAsyncDialogFragment(MediaCheckDialog.newInstance(dialogType))
}
Expand Down Expand Up @@ -2602,9 +2593,9 @@ class CollectionLoadingErrorDialog : DialogHandlerMessage(
WhichDialogHandler.MSG_SHOW_COLLECTION_LOADING_ERROR_DIALOG,
"CollectionLoadErrorDialog"
) {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
override fun handleAsyncMessage(ankiActivity: AnkiActivity) {
// Collection could not be opened
deckPicker.showDatabaseErrorDialog(DatabaseErrorDialogType.DIALOG_LOAD_FAILED)
ankiActivity.showDatabaseErrorDialog(DatabaseErrorDialogType.DIALOG_LOAD_FAILED)
}

override fun toMessage() = emptyMessage(this.what)
Expand All @@ -2614,7 +2605,7 @@ class OneWaySyncDialog(val message: String?) : DialogHandlerMessage(
which = WhichDialogHandler.MSG_SHOW_ONE_WAY_SYNC_DIALOG,
analyticName = "OneWaySyncDialog"
) {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
override fun handleAsyncMessage(ankiActivity: AnkiActivity) {
// Confirmation dialog for one-way sync
val dialog = ConfirmationDialog()
val confirm = Runnable {
Expand All @@ -2623,7 +2614,7 @@ class OneWaySyncDialog(val message: String?) : DialogHandlerMessage(
}
dialog.setConfirm(confirm)
dialog.setArgs(message)
deckPicker.showDialogFragment(dialog)
ankiActivity.showDialogFragment(dialog)
}

override fun toMessage(): Message = Message.obtain().apply {
Expand Down
20 changes: 10 additions & 10 deletions AnkiDroid/src/main/java/com/ichi2/anki/Import.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ import timber.log.Timber
// onSelectedPackageToImport/onSelectedCsvForImport
// importUtils - copying selected file into local cache
// ImportDialog - confirmation screen after file copied to cache
// * ImportDialogListener - DeckPicker implementation of handler for the confirmation screen
// * DeckPicker.importAdd/importReplace - called from confirmation screen
// * ImportDialogListener - AnkiActivity implementation of handler for the confirmation screen
// * AnkiActivity.importAdd/importReplace - called from confirmation screen
// BackendBackups/BackendImporting - new backend for importing
// importReplaceListener - old backend listener for importing

Expand All @@ -46,7 +46,7 @@ fun interface ImportColpkgListener {
}

@NeedsTest("successful import from the app menu")
fun DeckPicker.onSelectedPackageToImport(data: Intent) {
fun AnkiActivity.onSelectedPackageToImport(data: Intent) {
val importResult = ImportUtils.handleFileImport(this, data)
if (!importResult.isSuccess) {
runOnUiThread {
Expand All @@ -71,12 +71,12 @@ fun Activity.onSelectedCsvForImport(data: Intent) {
stackBuilder.startActivities()
}

fun DeckPicker.showImportDialog(id: Int, importPath: String) {
fun AnkiActivity.showImportDialog(id: Int, importPath: String) {
Timber.d("showImportDialog() delegating to ImportDialog")
val newFragment: AsyncDialogFragment = ImportDialog.newInstance(id, importPath)
showAsyncDialogFragment(newFragment)
}
fun DeckPicker.showImportDialog() {
fun AnkiActivity.showImportDialog() {
showImportDialog(
ImportOptions(
importApkg = true,
Expand All @@ -86,18 +86,18 @@ fun DeckPicker.showImportDialog() {
)
}

fun DeckPicker.showImportDialog(options: ImportOptions) {
fun AnkiActivity.showImportDialog(options: ImportOptions) {
showDialogFragment(ImportFileSelectionFragment.newInstance(options))
}

class DatabaseRestorationListener(val deckPicker: DeckPicker, val newAnkiDroidDirectory: String) : ImportColpkgListener {
class DatabaseRestorationListener(val ankiActivity: AnkiActivity, val newAnkiDroidDirectory: String) : ImportColpkgListener {
override fun onImportColpkg(colpkgPath: String?) {
Timber.i("Database restoration correct")
deckPicker.sharedPrefs().edit {
ankiActivity.sharedPrefs().edit {
putString("deckPath", newAnkiDroidDirectory)
}
deckPicker.dismissAllDialogFragments()
deckPicker.importColpkgListener = null
ankiActivity.dismissAllDialogFragments()
ankiActivity.importColpkgListener = null
CollectionHelper.ankiDroidDirectoryOverride = null
}
}
10 changes: 9 additions & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import androidx.annotation.VisibleForTesting
import androidx.core.app.TaskStackBuilder
import androidx.core.content.FileProvider
import androidx.core.content.IntentCompat
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.dialogs.DialogHandler.Companion.storeMessage
import com.ichi2.anki.dialogs.DialogHandlerMessage
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.servicelayer.ScopedStorageService
import com.ichi2.anki.services.ReminderService
import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.ui.windows.reviewer.ReviewerFragment
import com.ichi2.anki.worker.SyncWorker
import com.ichi2.annotations.NeedsTest
Expand Down Expand Up @@ -341,7 +343,13 @@ class IntentHandler : AbstractIntentHandler() {
which = WhichDialogHandler.MSG_DO_SYNC,
analyticName = "DoSyncDialog"
) {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
override fun handleAsyncMessage(ankiActivity: AnkiActivity) {
// we may be called via any AnkiActivity but sync is a DeckPicker thing
if (ankiActivity !is DeckPicker) {
ankiActivity.showSnackbar(ankiActivity.getString(R.string.something_wrong), Snackbar.LENGTH_SHORT)
}
val deckPicker = ankiActivity as DeckPicker

val preferences = deckPicker.sharedPrefs()
val res = deckPicker.resources
val hkey = preferences.getString("hkey", "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,16 +408,16 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
R.string.restore_data_from_backup,
dismissesDialog = false,
{ activity ->
val deckPicker = activity as DeckPicker
val ankiActivity = activity
Timber.i("Restoring from colpkg")
val newAnkiDroidDirectory = CollectionHelper.getDefaultAnkiDroidDirectory(deckPicker)
deckPicker.importColpkgListener = DatabaseRestorationListener(deckPicker, newAnkiDroidDirectory)
val newAnkiDroidDirectory = CollectionHelper.getDefaultAnkiDroidDirectory(ankiActivity)
ankiActivity.importColpkgListener = DatabaseRestorationListener(ankiActivity, newAnkiDroidDirectory)

deckPicker.launchCatchingTask {
ankiActivity.launchCatchingTask {
CollectionHelper.ankiDroidDirectoryOverride = newAnkiDroidDirectory

CollectionManager.withCol {
deckPicker.showImportDialog(
ankiActivity.showImportDialog(
ImportOptions(
importTextFile = false,
importColpkg = true,
Expand Down Expand Up @@ -555,7 +555,7 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
private fun requireDialogType() = BundleCompat.getParcelable(requireArguments(), "dialog", DatabaseErrorDialogType::class.java)!!

fun dismissAllDialogFragments() {
(activity as DeckPicker).dismissAllDialogFragments()
(activity as AnkiActivity).dismissAllDialogFragments()
}

@Parcelize
Expand Down Expand Up @@ -608,8 +608,8 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
which = WhichDialogHandler.MSG_SHOW_DATABASE_ERROR_DIALOG,
analyticName = "DatabaseErrorDialog"
) {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
deckPicker.showDatabaseErrorDialog(dialogType)
override fun handleAsyncMessage(ankiActivity: AnkiActivity) {
ankiActivity.showDatabaseErrorDialog(dialogType)
}

override fun toMessage(): Message = Message.obtain().apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ class DialogHandler(activity: AnkiActivity) : Handler(getDefaultLooper()) {
val msg = DialogHandlerMessage.fromMessage(message)
UsageAnalytics.sendAnalyticsScreenView(msg.analyticName)
Timber.i("Handling Message: %s", msg.analyticName)
val deckPicker = activity.get() as DeckPicker
msg.handleAsyncMessage(deckPicker)
val ankiActivity = activity.get() as AnkiActivity
msg.handleAsyncMessage(ankiActivity)
}

/**
Expand Down Expand Up @@ -100,7 +100,7 @@ class DialogHandler(activity: AnkiActivity) : Handler(getDefaultLooper()) {
*/
abstract class DialogHandlerMessage protected constructor(val which: WhichDialogHandler, val analyticName: String) {
val what = which.what
abstract fun handleAsyncMessage(deckPicker: DeckPicker)
abstract fun handleAsyncMessage(ankiActivity: AnkiActivity)

protected fun emptyMessage(what: Int): Message = Message.obtain().apply { this.what = what }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ import android.os.Bundle
import android.os.Message
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.R
import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.utils.negativeButton
import com.ichi2.utils.positiveButton

Expand Down Expand Up @@ -64,9 +67,13 @@ class ExportReadyDialog(private val listener: ExportReadyDialogListener) : Async
which = WhichDialogHandler.MSG_EXPORT_READY,
analyticName = "ExportReadyDialog"
) {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
deckPicker.showDialogFragment(
deckPicker.exportingDelegate.dialogsFactory.newExportReadyDialog().withArguments(exportPath)
override fun handleAsyncMessage(ankiActivity: AnkiActivity) {
if (ankiActivity !is DeckPicker) {
ankiActivity.showSnackbar(ankiActivity.getString(R.string.something_wrong), Snackbar.LENGTH_SHORT)
return
}
ankiActivity.showDialogFragment(
ankiActivity.exportingDelegate.dialogsFactory.newExportReadyDialog().withArguments(exportPath)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import androidx.annotation.CheckResult
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.R
import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.libanki.MediaCheckResult

class MediaCheckDialog : AsyncDialogFragment() {
Expand Down Expand Up @@ -174,12 +177,16 @@ class MediaCheckDialog : AsyncDialogFragment() {
private val unused: ArrayList<String>?,
private val invalid: ArrayList<String>?
) : DialogHandlerMessage(WhichDialogHandler.MSG_SHOW_MEDIA_CHECK_COMPLETE_DIALOG, "MediaCheckCompleteDialog") {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
override fun handleAsyncMessage(ankiActivity: AnkiActivity) {
// Media check results
val id = dialogType
if (id != DIALOG_CONFIRM_MEDIA_CHECK) {
if (ankiActivity !is DeckPicker) {
ankiActivity.showSnackbar(ankiActivity.getString(R.string.something_wrong), Snackbar.LENGTH_SHORT)
return
}
val checkList = MediaCheckResult(noHave ?: arrayListOf(), unused ?: arrayListOf(), invalid ?: arrayListOf())
deckPicker.showMediaCheckDialog(id, checkList)
ankiActivity.showMediaCheckDialog(id, checkList)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ import androidx.annotation.CheckResult
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.ConflictResolution
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.R
import com.ichi2.anki.joinSyncMessages
import com.ichi2.anki.snackbar.showSnackbar

class SyncErrorDialog : AsyncDialogFragment() {
interface SyncErrorDialogListener {
Expand Down Expand Up @@ -277,8 +279,12 @@ class SyncErrorDialog : AsyncDialogFragment() {
private val dialogType: Int,
private val dialogMessage: String?
) : DialogHandlerMessage(WhichDialogHandler.MSG_SHOW_SYNC_ERROR_DIALOG, "SyncErrorDialog") {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
deckPicker.showSyncErrorDialog(dialogType, dialogMessage)
override fun handleAsyncMessage(ankiActivity: AnkiActivity) {
if (ankiActivity !is DeckPicker) {
ankiActivity.showSnackbar(ankiActivity.getString(R.string.something_wrong), Snackbar.LENGTH_SHORT)
return
}
ankiActivity.showSyncErrorDialog(dialogType, dialogMessage)
}

override fun toMessage(): Message = Message.obtain().apply {
Expand Down
10 changes: 5 additions & 5 deletions AnkiDroid/src/main/java/com/ichi2/utils/ImportUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import android.provider.OpenableColumns
import androidx.annotation.CheckResult
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.AnkiDroidApp
import com.ichi2.anki.CrashReportService
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.R
import com.ichi2.anki.dialogs.DialogHandler
import com.ichi2.anki.dialogs.DialogHandlerMessage
Expand Down Expand Up @@ -406,9 +406,9 @@ object ImportUtils {
which = WhichDialogHandler.MSG_SHOW_COLLECTION_IMPORT_REPLACE_DIALOG,
analyticName = "ImportReplaceDialog"
) {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
override fun handleAsyncMessage(ankiActivity: AnkiActivity) {
// Handle import of collection package APKG
deckPicker.showImportDialog(ImportDialog.DIALOG_IMPORT_REPLACE_CONFIRM, importPath)
ankiActivity.showImportDialog(ImportDialog.DIALOG_IMPORT_REPLACE_CONFIRM, importPath)
}

override fun toMessage(): Message = Message.obtain().apply {
Expand All @@ -427,9 +427,9 @@ object ImportUtils {
WhichDialogHandler.MSG_SHOW_COLLECTION_IMPORT_ADD_DIALOG,
"ImportAddDialog"
) {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
override fun handleAsyncMessage(ankiActivity: AnkiActivity) {
// Handle import of deck package APKG
deckPicker.showImportDialog(ImportDialog.DIALOG_IMPORT_ADD_CONFIRM, importPath)
ankiActivity.showImportDialog(ImportDialog.DIALOG_IMPORT_ADD_CONFIRM, importPath)
}

override fun toMessage(): Message = Message.obtain().apply {
Expand Down

0 comments on commit 5109273

Please sign in to comment.