From 4c00e78a35589128a228eed5d3070e5e2eac73e5 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Thu, 28 Nov 2024 09:59:02 -0500 Subject: [PATCH 1/4] fix: sendExceptionReport even if Dialog display fails previously we would ignore our sendExceptionReport param if a dialog post fails, meaning that we would not get exception reports from ACRA in circumstances we really want them --- AnkiDroid/src/main/java/com/ichi2/anki/CoroutineHelpers.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CoroutineHelpers.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CoroutineHelpers.kt index 65568780110e..d13aa8a585df 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CoroutineHelpers.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CoroutineHelpers.kt @@ -279,6 +279,12 @@ fun showError(context: Context, msg: String, exception: Throwable, crashReport: } catch (ex: BadTokenException) { // issue 12718: activity provided by `context` was not running Timber.w(ex, "unable to display error dialog") + if (crashReport) { + CrashReportService.sendExceptionReport( + exception, + origin = context::class.java.simpleName + ) + } } } From 0ea48e71dbe80d1e7b6e81dee36cc1a788c11710 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Thu, 28 Nov 2024 11:46:41 -0500 Subject: [PATCH 2/4] test: new dev pref to set collection path to pre-scoped storage this patch has been floating around a lot, and it is very useful you can use it to test the permanently inaccessible storage dialog, which you can then also use to test the restore from backup dialog --- .../anki/preferences/DevOptionsFragment.kt | 21 +++++++++++++++++++ AnkiDroid/src/main/res/values/preferences.xml | 1 + .../main/res/xml/preferences_dev_options.xml | 4 ++++ 3 files changed, 26 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/DevOptionsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/DevOptionsFragment.kt index f063621b31a3..ba5a7237ef78 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/DevOptionsFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/DevOptionsFragment.kt @@ -17,9 +17,12 @@ package com.ichi2.anki.preferences import android.content.Context import androidx.appcompat.app.AlertDialog +import androidx.core.content.edit import androidx.preference.Preference import androidx.preference.SwitchPreferenceCompat +import com.ichi2.anki.AnkiDroidApp import com.ichi2.anki.BuildConfig +import com.ichi2.anki.CollectionHelper import com.ichi2.anki.CollectionManager import com.ichi2.anki.CrashReportService import com.ichi2.anki.R @@ -86,6 +89,24 @@ class DevOptionsFragment : SettingsFragment() { launchCatchingTask { CollectionManager.withCol { Thread.sleep(1000 * 86400) } } false } + // Make it possible to test crash reporting + requirePreference(R.string.pref_set_database_path_debug_key).setOnPreferenceClickListener { + AlertDialog.Builder(requireContext()).show { + setTitle("Warning!") + setMessage("This will most likely make it so that you cannot access your collection. It will be very difficult to recover your data.") + setPositiveButton(R.string.dialog_ok) { _, _ -> + Timber.w("Setting collection path to /storage/emulated/0/AnkiDroid") + AnkiDroidApp.sharedPrefs().edit { + putString( + CollectionHelper.PREF_COLLECTION_PATH, + "/storage/emulated/0/AnkiDroid" + ) + } + } + setNegativeButton(R.string.dialog_cancel) { _, _ -> } + } + false + } val sizePreference = requirePreference(getString(R.string.pref_fill_collection_size_file_key)) val numberOfFilePreference = requirePreference(getString(R.string.pref_fill_collection_number_file_key)) diff --git a/AnkiDroid/src/main/res/values/preferences.xml b/AnkiDroid/src/main/res/values/preferences.xml index 7b9f2c97823d..b936fadbcf4b 100644 --- a/AnkiDroid/src/main/res/values/preferences.xml +++ b/AnkiDroid/src/main/res/values/preferences.xml @@ -175,6 +175,7 @@ devOptionsKey trigger_crash_preference analytics_debug_preference + debug_set_database_path debug_lock_database new_congrats_screen newReviewer diff --git a/AnkiDroid/src/main/res/xml/preferences_dev_options.xml b/AnkiDroid/src/main/res/xml/preferences_dev_options.xml index 9db550cda1e8..875d49c1f512 100644 --- a/AnkiDroid/src/main/res/xml/preferences_dev_options.xml +++ b/AnkiDroid/src/main/res/xml/preferences_dev_options.xml @@ -44,6 +44,10 @@ android:title="Lock Database" android:summary="Touch here to lock the database (all threads block in-process, exception if using second process)" android:key="@string/pref_lock_database_key"/> + Date: Thu, 28 Nov 2024 11:47:49 -0500 Subject: [PATCH 3/4] chore: these are expected errors and we have no trouble recovering from them so we do not need the full stack trace and they shouldn't be warn --- .../main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt index fed2a4822f7c..934762598d31 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt @@ -89,9 +89,9 @@ class DatabaseErrorDialog : AsyncDialogFragment() { try { sqliteInstalled = Runtime.getRuntime().exec("sqlite3 --version").waitFor() == 0 } catch (e: IOException) { - Timber.w(e) + Timber.i("sqlite3 not installed: ${e.message}") } catch (e: InterruptedException) { - Timber.w(e) + Timber.i("test for sqlite3 failed: ${e.message}") } return when (requireDialogType()) { DIALOG_LOAD_FAILED -> { From 1a5dcaff4938c679bbbc1976d48abf7adb27e2a7 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Wed, 27 Nov 2024 17:50:42 -0500 Subject: [PATCH 4/4] fix: handle database-related errors / import in AnkiActivity 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) --- .../main/java/com/ichi2/anki/AnkiActivity.kt | 10 ++++++++++ .../main/java/com/ichi2/anki/DeckPicker.kt | 17 ++++------------ .../src/main/java/com/ichi2/anki/Import.kt | 20 +++++++++---------- .../main/java/com/ichi2/anki/IntentHandler.kt | 15 +++++++++++++- .../ichi2/anki/dialogs/DatabaseErrorDialog.kt | 15 +++++++------- .../com/ichi2/anki/dialogs/DialogHandler.kt | 5 ++--- .../ichi2/anki/dialogs/ExportReadyDialog.kt | 18 ++++++++++++++--- .../ichi2/anki/dialogs/MediaCheckDialog.kt | 16 +++++++++++++-- .../com/ichi2/anki/dialogs/SyncErrorDialog.kt | 15 ++++++++++++-- .../main/java/com/ichi2/utils/ImportUtils.kt | 10 +++++----- 10 files changed, 94 insertions(+), 47 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt index 41163bf34d56..1d777302b2db 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt @@ -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 @@ -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) @@ -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 diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt index e3bbdbb63614..b71d785295ef 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt @@ -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 @@ -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 @@ -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)) } @@ -2602,9 +2593,9 @@ class CollectionLoadingErrorDialog : DialogHandlerMessage( WhichDialogHandler.MSG_SHOW_COLLECTION_LOADING_ERROR_DIALOG, "CollectionLoadErrorDialog" ) { - override fun handleAsyncMessage(deckPicker: DeckPicker) { + override fun handleAsyncMessage(activity: AnkiActivity) { // Collection could not be opened - deckPicker.showDatabaseErrorDialog(DatabaseErrorDialogType.DIALOG_LOAD_FAILED) + activity.showDatabaseErrorDialog(DatabaseErrorDialogType.DIALOG_LOAD_FAILED) } override fun toMessage() = emptyMessage(this.what) @@ -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(activity: AnkiActivity) { // Confirmation dialog for one-way sync val dialog = ConfirmationDialog() val confirm = Runnable { @@ -2623,7 +2614,7 @@ class OneWaySyncDialog(val message: String?) : DialogHandlerMessage( } dialog.setConfirm(confirm) dialog.setArgs(message) - deckPicker.showDialogFragment(dialog) + activity.showDialogFragment(dialog) } override fun toMessage(): Message = Message.obtain().apply { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Import.kt b/AnkiDroid/src/main/java/com/ichi2/anki/Import.kt index 663b3348a483..c61f26d3bd1f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Import.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Import.kt @@ -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 @@ -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 { @@ -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, @@ -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 activity: AnkiActivity, val newAnkiDroidDirectory: String) : ImportColpkgListener { override fun onImportColpkg(colpkgPath: String?) { Timber.i("Database restoration correct") - deckPicker.sharedPrefs().edit { + activity.sharedPrefs().edit { putString("deckPath", newAnkiDroidDirectory) } - deckPicker.dismissAllDialogFragments() - deckPicker.importColpkgListener = null + activity.dismissAllDialogFragments() + activity.importColpkgListener = null CollectionHelper.ankiDroidDirectoryOverride = null } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt b/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt index 4d8ad6065f47..686ea1344ce2 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt @@ -341,7 +341,20 @@ class IntentHandler : AbstractIntentHandler() { which = WhichDialogHandler.MSG_DO_SYNC, analyticName = "DoSyncDialog" ) { - override fun handleAsyncMessage(deckPicker: DeckPicker) { + override fun handleAsyncMessage(activity: AnkiActivity) { + // we may be called via any AnkiActivity but sync is a DeckPicker thing + if (activity !is DeckPicker) { + showError( + activity, + activity.getString(R.string.something_wrong), + ClassCastException(activity.javaClass.simpleName + " is not " + DeckPicker.javaClass.simpleName), + true + ) + return + } + // let's be clear about the type now that we've checked + val deckPicker = activity + val preferences = deckPicker.sharedPrefs() val res = deckPicker.resources val hkey = preferences.getString("hkey", "") diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt index 934762598d31..035fbc625550 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt @@ -408,16 +408,15 @@ class DatabaseErrorDialog : AsyncDialogFragment() { R.string.restore_data_from_backup, dismissesDialog = false, { activity -> - val deckPicker = activity as DeckPicker Timber.i("Restoring from colpkg") - val newAnkiDroidDirectory = CollectionHelper.getDefaultAnkiDroidDirectory(deckPicker) - deckPicker.importColpkgListener = DatabaseRestorationListener(deckPicker, newAnkiDroidDirectory) + val newAnkiDroidDirectory = CollectionHelper.getDefaultAnkiDroidDirectory(activity) + activity.importColpkgListener = DatabaseRestorationListener(activity, newAnkiDroidDirectory) - deckPicker.launchCatchingTask { + activity.launchCatchingTask { CollectionHelper.ankiDroidDirectoryOverride = newAnkiDroidDirectory CollectionManager.withCol { - deckPicker.showImportDialog( + activity.showImportDialog( ImportOptions( importTextFile = false, importColpkg = true, @@ -555,7 +554,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 @@ -608,8 +607,8 @@ class DatabaseErrorDialog : AsyncDialogFragment() { which = WhichDialogHandler.MSG_SHOW_DATABASE_ERROR_DIALOG, analyticName = "DatabaseErrorDialog" ) { - override fun handleAsyncMessage(deckPicker: DeckPicker) { - deckPicker.showDatabaseErrorDialog(dialogType) + override fun handleAsyncMessage(activity: AnkiActivity) { + activity.showDatabaseErrorDialog(dialogType) } override fun toMessage(): Message = Message.obtain().apply { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DialogHandler.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DialogHandler.kt index 482795f81d55..0ab54b1ae298 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DialogHandler.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DialogHandler.kt @@ -42,8 +42,7 @@ 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) + msg.handleAsyncMessage(activity.get() as AnkiActivity) } /** @@ -100,7 +99,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(activity: AnkiActivity) protected fun emptyMessage(what: Int): Message = Message.obtain().apply { this.what = what } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportReadyDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportReadyDialog.kt index 343e23f08f25..cb7567defaa9 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportReadyDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportReadyDialog.kt @@ -21,8 +21,10 @@ import android.os.Bundle import android.os.Message import androidx.appcompat.app.AlertDialog import androidx.core.os.bundleOf +import com.ichi2.anki.AnkiActivity import com.ichi2.anki.DeckPicker import com.ichi2.anki.R +import com.ichi2.anki.showError import com.ichi2.utils.negativeButton import com.ichi2.utils.positiveButton @@ -64,9 +66,19 @@ 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(activity: AnkiActivity) { + // we may be called via any AnkiActivity but export is a DeckPicker thing + if (activity !is DeckPicker) { + showError( + activity, + activity.getString(R.string.something_wrong), + ClassCastException(activity.javaClass.simpleName + " is not " + DeckPicker.javaClass.simpleName), + true + ) + return + } + activity.showDialogFragment( + activity.exportingDelegate.dialogsFactory.newExportReadyDialog().withArguments(exportPath) ) } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/MediaCheckDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/MediaCheckDialog.kt index ce9ea38f2b0f..d2f53ea7bcc4 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/MediaCheckDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/MediaCheckDialog.kt @@ -13,8 +13,10 @@ import androidx.annotation.CheckResult import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AlertDialog import androidx.core.os.bundleOf +import com.ichi2.anki.AnkiActivity import com.ichi2.anki.DeckPicker import com.ichi2.anki.R +import com.ichi2.anki.showError import com.ichi2.libanki.MediaCheckResult class MediaCheckDialog : AsyncDialogFragment() { @@ -174,12 +176,22 @@ class MediaCheckDialog : AsyncDialogFragment() { private val unused: ArrayList?, private val invalid: ArrayList? ) : DialogHandlerMessage(WhichDialogHandler.MSG_SHOW_MEDIA_CHECK_COMPLETE_DIALOG, "MediaCheckCompleteDialog") { - override fun handleAsyncMessage(deckPicker: DeckPicker) { + override fun handleAsyncMessage(activity: AnkiActivity) { // Media check results val id = dialogType if (id != DIALOG_CONFIRM_MEDIA_CHECK) { + // we may be called via any AnkiActivity but media check is a DeckPicker thing + if (activity !is DeckPicker) { + showError( + activity, + activity.getString(R.string.something_wrong), + ClassCastException(activity.javaClass.simpleName + " is not " + DeckPicker.javaClass.simpleName), + true + ) + return + } val checkList = MediaCheckResult(noHave ?: arrayListOf(), unused ?: arrayListOf(), invalid ?: arrayListOf()) - deckPicker.showMediaCheckDialog(id, checkList) + activity.showMediaCheckDialog(id, checkList) } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SyncErrorDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SyncErrorDialog.kt index 394f7a17ed99..2934ac322d58 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SyncErrorDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SyncErrorDialog.kt @@ -29,6 +29,7 @@ import com.ichi2.anki.ConflictResolution import com.ichi2.anki.DeckPicker import com.ichi2.anki.R import com.ichi2.anki.joinSyncMessages +import com.ichi2.anki.showError class SyncErrorDialog : AsyncDialogFragment() { interface SyncErrorDialogListener { @@ -277,8 +278,18 @@ 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(activity: AnkiActivity) { + // we may be called via any AnkiActivity but media check is a DeckPicker thing + if (activity !is DeckPicker) { + showError( + activity, + activity.getString(R.string.something_wrong), + ClassCastException(activity.javaClass.simpleName + " is not " + DeckPicker.javaClass.simpleName), + true + ) + return + } + activity.showSyncErrorDialog(dialogType, dialogMessage) } override fun toMessage(): Message = Message.obtain().apply { diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/ImportUtils.kt b/AnkiDroid/src/main/java/com/ichi2/utils/ImportUtils.kt index c18dee7951bf..3240f5629361 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/ImportUtils.kt +++ b/AnkiDroid/src/main/java/com/ichi2/utils/ImportUtils.kt @@ -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 @@ -406,9 +406,9 @@ object ImportUtils { which = WhichDialogHandler.MSG_SHOW_COLLECTION_IMPORT_REPLACE_DIALOG, analyticName = "ImportReplaceDialog" ) { - override fun handleAsyncMessage(deckPicker: DeckPicker) { + override fun handleAsyncMessage(activity: AnkiActivity) { // Handle import of collection package APKG - deckPicker.showImportDialog(ImportDialog.DIALOG_IMPORT_REPLACE_CONFIRM, importPath) + activity.showImportDialog(ImportDialog.DIALOG_IMPORT_REPLACE_CONFIRM, importPath) } override fun toMessage(): Message = Message.obtain().apply { @@ -427,9 +427,9 @@ object ImportUtils { WhichDialogHandler.MSG_SHOW_COLLECTION_IMPORT_ADD_DIALOG, "ImportAddDialog" ) { - override fun handleAsyncMessage(deckPicker: DeckPicker) { + override fun handleAsyncMessage(activity: AnkiActivity) { // Handle import of deck package APKG - deckPicker.showImportDialog(ImportDialog.DIALOG_IMPORT_ADD_CONFIRM, importPath) + activity.showImportDialog(ImportDialog.DIALOG_IMPORT_ADD_CONFIRM, importPath) } override fun toMessage(): Message = Message.obtain().apply {