diff --git a/app/build.gradle b/app/build.gradle
index d5ddf9b3..806f25c6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -19,11 +19,11 @@ def getGitSha = {
 }
 
 android {
-    compileSdkVersion 31
+    compileSdkVersion 33
     defaultConfig {
         applicationId APP_ID
         minSdkVersion 23
-        targetSdkVersion 31
+        targetSdkVersion 33
         versionCode 94
         versionName "19.0"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -108,16 +108,17 @@ dependencies {
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx3:$coroutinesVersion"
 
-    implementation "androidx.core:core-ktx:1.8.0"
-    implementation "androidx.appcompat:appcompat:1.4.2"
-    implementation "androidx.fragment:fragment-ktx:1.5.1"
+    implementation "androidx.core:core-ktx:1.9.0"
+    implementation "androidx.appcompat:appcompat:1.5.1"
+    implementation "androidx.activity:activity-ktx:1.6.0"
+    implementation "androidx.fragment:fragment-ktx:1.5.3"
     implementation "androidx.browser:browser:1.4.0"
     implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
     implementation "androidx.recyclerview:recyclerview:1.2.1"
-    implementation "androidx.exifinterface:exifinterface:1.3.3"
+    implementation "androidx.exifinterface:exifinterface:1.3.4"
     implementation "androidx.cardview:cardview:1.0.0"
     implementation "androidx.preference:preference-ktx:1.2.0"
-    implementation "androidx.sharetarget:sharetarget:1.2.0-rc01"
+    implementation "androidx.sharetarget:sharetarget:1.2.0"
     implementation "androidx.emoji2:emoji2:$emoji2_version"
     implementation "androidx.emoji2:emoji2-views:$emoji2_version"
     implementation "androidx.emoji2:emoji2-views-helper:$emoji2_version"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5600bbe2..659786e1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,7 @@
     package="com.keylesspalace.tusky">
 
     <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.VIBRATE" /> <!-- For notifications -->
diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt
index ca23f791..723a3519 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt
@@ -20,6 +20,7 @@ import android.content.Intent
 import android.os.Bundle
 import com.keylesspalace.tusky.databinding.ActivityAccountListBinding
 import com.keylesspalace.tusky.fragment.AccountListFragment
+import com.keylesspalace.tusky.util.requireSerializableExtra
 import dagger.android.DispatchingAndroidInjector
 import dagger.android.HasAndroidInjector
 import javax.inject.Inject
@@ -44,7 +45,7 @@ class AccountListActivity : BaseActivity(), HasAndroidInjector {
         val binding = ActivityAccountListBinding.inflate(layoutInflater)
         setContentView(binding.root)
 
-        val type = intent.getSerializableExtra(EXTRA_TYPE) as Type
+        val type: Type = intent.requireSerializableExtra(EXTRA_TYPE)
         val id: String? = intent.getStringExtra(EXTRA_ID)
         val accountLocked: Boolean = intent.getBooleanExtra(EXTRA_ACCOUNT_LOCKED, false)
 
diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
index d34dd6df..eaf2db8b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
@@ -132,7 +132,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         if (item.getItemId() == android.R.id.home) {
-            onBackPressed();
+            getOnBackPressedDispatcher().onBackPressed();
             return true;
         }
         return super.onOptionsItemSelected(item);
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
index bc3b2b68..80bcfde3 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
@@ -15,9 +15,11 @@
 
 package com.keylesspalace.tusky
 
+import android.Manifest
 import android.content.Context
 import android.content.DialogInterface
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.content.res.ColorStateList
 import android.graphics.Bitmap
 import android.graphics.Color
@@ -31,8 +33,11 @@ import android.view.KeyEvent
 import android.view.MenuItem
 import android.view.View
 import android.widget.ImageView
+import androidx.activity.OnBackPressedCallback
 import androidx.appcompat.app.AlertDialog
 import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
 import androidx.core.content.pm.ShortcutManagerCompat
 import androidx.core.view.GravityCompat
 import androidx.lifecycle.Lifecycle
@@ -267,6 +272,33 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
         }
 
         selectedEmojiPack = preferences.getString(EMOJI_PREFERENCE, "")
+
+        onBackPressedDispatcher.addCallback(
+            this,
+            object : OnBackPressedCallback(true) {
+                override fun handleOnBackPressed() {
+                    when {
+                        binding.mainDrawerLayout.isOpen -> {
+                            binding.mainDrawerLayout.close()
+                        }
+                        binding.viewPager.currentItem != 0 -> {
+                            binding.viewPager.currentItem = 0
+                        }
+                        else -> {
+                            finish()
+                        }
+                    }
+                }
+            }
+        )
+
+        if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
+            ActivityCompat.requestPermissions(
+                this,
+                arrayOf(Manifest.permission.POST_NOTIFICATIONS),
+                1
+            )
+        }
     }
 
     override fun onResume() {
@@ -292,20 +324,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
         }
     }
 
-    override fun onBackPressed() {
-        when {
-            binding.mainDrawerLayout.isOpen -> {
-                binding.mainDrawerLayout.close()
-            }
-            binding.viewPager.currentItem != 0 -> {
-                binding.viewPager.currentItem = 0
-            }
-            else -> {
-                super.onBackPressed()
-            }
-        }
-    }
-
     override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
         when (keyCode) {
             KeyEvent.KEYCODE_MENU -> {
diff --git a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt
index 76418e01..0f20a785 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt
@@ -20,9 +20,9 @@ import android.os.Bundle
 import android.util.Log
 import android.view.View
 import android.widget.FrameLayout
+import androidx.activity.OnBackPressedCallback
 import androidx.appcompat.app.AlertDialog
 import androidx.appcompat.widget.AppCompatEditText
-import androidx.core.view.isVisible
 import androidx.core.view.updatePadding
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
@@ -74,6 +74,12 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
 
     private val hashtagRegex by lazy { Pattern.compile("([\\w_]*[\\p{Alpha}_][\\w_]*)", Pattern.CASE_INSENSITIVE) }
 
+    private val onFabDismissedCallback = object : OnBackPressedCallback(false) {
+        override fun handleOnBackPressed() {
+            toggleFab(false)
+        }
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
@@ -149,6 +155,8 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
         binding.maxTabsInfo.text = resources.getQuantityString(R.plurals.max_tab_number_reached, MAX_TAB_COUNT, MAX_TAB_COUNT)
 
         updateAvailableTabs()
+
+        onBackPressedDispatcher.addCallback(onFabDismissedCallback)
     }
 
     override fun onTabAdded(tab: TabData) {
@@ -209,6 +217,8 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
         binding.actionButton.visible(!expand)
         binding.sheet.visible(expand)
         binding.scrim.visible(expand)
+
+        onFabDismissedCallback.isEnabled = expand
     }
 
     private fun showAddHashtagDialog(tab: TabData? = null, tabPosition: Int = 0) {
@@ -338,14 +348,6 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
         tabsChanged = true
     }
 
-    override fun onBackPressed() {
-        if (binding.actionButton.isVisible) {
-            super.onBackPressed()
-        } else {
-            toggleFab(false)
-        }
-    }
-
     override fun onPause() {
         super.onPause()
         if (tabsChanged) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt
index 2a96cb7f..94bc288a 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt
@@ -54,6 +54,7 @@ import com.keylesspalace.tusky.fragment.ViewImageFragment
 import com.keylesspalace.tusky.pager.ImagePagerAdapter
 import com.keylesspalace.tusky.pager.SingleImagePagerAdapter
 import com.keylesspalace.tusky.util.getTemporaryMediaFilename
+import com.keylesspalace.tusky.util.parcelableArrayListExtra
 import com.keylesspalace.tusky.util.viewBinding
 import com.keylesspalace.tusky.viewdata.AttachmentViewData
 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
@@ -94,7 +95,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
         supportPostponeEnterTransition()
 
         // Gather the parameters.
-        attachments = intent.getParcelableArrayListExtra(EXTRA_ATTACHMENTS)
+        attachments = intent.parcelableArrayListExtra(EXTRA_ATTACHMENTS)
         val initialPosition = intent.getIntExtra(EXTRA_ATTACHMENT_INDEX, 0)
 
         // Adapter is actually of existential type PageAdapter & SharedElementsTransitionListener
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
index 8106c7ff..cb2c2130 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
@@ -40,6 +40,7 @@ import android.widget.ImageButton
 import android.widget.LinearLayout
 import android.widget.PopupMenu
 import android.widget.Toast
+import androidx.activity.OnBackPressedCallback
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.activity.viewModels
 import androidx.annotation.ColorInt
@@ -92,6 +93,8 @@ import com.keylesspalace.tusky.util.hide
 import com.keylesspalace.tusky.util.highlightSpans
 import com.keylesspalace.tusky.util.loadAvatar
 import com.keylesspalace.tusky.util.onTextChanged
+import com.keylesspalace.tusky.util.parcelableArrayListExtra
+import com.keylesspalace.tusky.util.parcelableExtra
 import com.keylesspalace.tusky.util.show
 import com.keylesspalace.tusky.util.viewBinding
 import com.keylesspalace.tusky.util.visible
@@ -237,8 +240,7 @@ class ComposeActivity :
 
         /* If the composer is started up as a reply to another post, override the "starting" state
          * based on what the intent from the reply request passes. */
-
-        val composeOptions: ComposeOptions? = intent.getParcelableExtra(COMPOSE_OPTIONS_EXTRA)
+        val composeOptions: ComposeOptions? = intent.parcelableExtra(COMPOSE_OPTIONS_EXTRA)
 
         viewModel.setup(composeOptions)
 
@@ -299,12 +301,12 @@ class ComposeActivity :
                 if (type.startsWith("image/") || type.startsWith("video/") || type.startsWith("audio/")) {
                     when (intent.action) {
                         Intent.ACTION_SEND -> {
-                            intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)?.let { uri ->
+                            intent.parcelableExtra<Uri>(Intent.EXTRA_STREAM)?.let { uri ->
                                 pickMedia(uri)
                             }
                         }
                         Intent.ACTION_SEND_MULTIPLE -> {
-                            intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)?.forEach { uri ->
+                            intent.parcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)?.forEach { uri ->
                                 pickMedia(uri)
                             }
                         }
@@ -510,6 +512,27 @@ class ComposeActivity :
         binding.actionPhotoTake.setOnClickListener { initiateCameraApp() }
         binding.actionPhotoPick.setOnClickListener { onMediaPick() }
         binding.addPollTextActionTextView.setOnClickListener { openPollDialog() }
+
+        onBackPressedDispatcher.addCallback(
+            this,
+            object : OnBackPressedCallback(true) {
+                override fun handleOnBackPressed() {
+                    if (composeOptionsBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
+                        addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
+                        emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
+                        scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED
+                    ) {
+                        composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
+                        addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
+                        emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
+                        scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
+                        return
+                    }
+
+                    handleCloseButton()
+                }
+            }
+        )
     }
 
     private fun setupLanguageSpinner(initialLanguage: String?) {
@@ -1069,23 +1092,6 @@ class ComposeActivity :
         return super.onOptionsItemSelected(item)
     }
 
-    override fun onBackPressed() {
-        // Acting like a teen: deliberately ignoring parent.
-        if (composeOptionsBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
-            addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
-            emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
-            scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED
-        ) {
-            composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
-            addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
-            emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
-            scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
-            return
-        }
-
-        handleCloseButton()
-    }
-
     override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
         Log.d(TAG, event.toString())
         if (event.action == KeyEvent.ACTION_DOWN) {
@@ -1098,7 +1104,7 @@ class ComposeActivity :
             }
 
             if (keyCode == KeyEvent.KEYCODE_BACK) {
-                onBackPressed()
+                onBackPressedDispatcher.onBackPressed()
                 return true
             }
         }
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt
index 614b87ee..54fb645d 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt
@@ -38,6 +38,7 @@ import com.bumptech.glide.request.target.CustomTarget
 import com.bumptech.glide.request.transition.Transition
 import com.github.chrisbanes.photoview.PhotoView
 import com.keylesspalace.tusky.R
+import com.keylesspalace.tusky.util.parcelable
 
 // https://github.com/tootsuite/mastodon/blob/c6904c0d3766a2ea8a81ab025c127169ecb51373/app/models/media_attachment.rb#L32
 private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 1500
@@ -93,8 +94,7 @@ class CaptionDialog : DialogFragment() {
         val window = dialog.window
         window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
 
-        val previewUri =
-            arguments?.getParcelable<Uri>(PREVIEW_URI_ARG) ?: error("Preview Uri is null")
+        val previewUri: Uri = arguments?.parcelable(PREVIEW_URI_ARG) ?: error("Preview Uri is null")
         // Load the image and manually set it into the ImageView because it doesn't have a fixed size.
         Glide.with(this)
             .load(previewUri)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
index 07bd5652..b38e86f1 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
@@ -23,6 +23,7 @@ import com.keylesspalace.tusky.R
 import com.keylesspalace.tusky.databinding.ActivityLoginWebviewBinding
 import com.keylesspalace.tusky.di.Injectable
 import com.keylesspalace.tusky.util.hide
+import com.keylesspalace.tusky.util.parcelableExtra
 import com.keylesspalace.tusky.util.viewBinding
 import kotlinx.parcelize.Parcelize
 
@@ -39,7 +40,7 @@ class OauthLogin : ActivityResultContract<LoginData, LoginResult>() {
         return if (resultCode == Activity.RESULT_CANCELED) {
             LoginResult.Cancel
         } else {
-            intent!!.getParcelableExtra(RESULT_EXTRA)!!
+            intent!!.parcelableExtra(RESULT_EXTRA)!!
         }
     }
 
@@ -48,7 +49,7 @@ class OauthLogin : ActivityResultContract<LoginData, LoginResult>() {
         private const val DATA_EXTRA = "data"
 
         fun parseData(intent: Intent): LoginData {
-            return intent.getParcelableExtra(DATA_EXTRA)!!
+            return intent.parcelableExtra(DATA_EXTRA)!!
         }
 
         fun makeResultIntent(result: LoginResult): Intent {
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt
index 8ae4e056..878766fa 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt
@@ -20,6 +20,7 @@ import android.content.Intent
 import android.content.SharedPreferences
 import android.os.Bundle
 import android.util.Log
+import androidx.activity.OnBackPressedCallback
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.commit
 import androidx.preference.PreferenceManager
@@ -47,7 +48,17 @@ class PreferencesActivity :
     @Inject
     lateinit var androidInjector: DispatchingAndroidInjector<Any>
 
-    private var restartActivitiesOnExit: Boolean = false
+    private val restartActivitiesOnBackPressedCallback = object : OnBackPressedCallback(false) {
+        override fun handleOnBackPressed() {
+            /* Switching themes won't actually change the theme of activities on the back stack.
+             * Either the back stack activities need to all be recreated, or do the easier thing, which
+             * is hijack the back button press and use it to launch a new MainActivity and clear the
+             * back stack. */
+            val intent = Intent(this@PreferencesActivity, MainActivity::class.java)
+            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+            startActivityWithSlideInAnimation(intent)
+        }
+    }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -92,7 +103,8 @@ class PreferencesActivity :
             replace(R.id.fragment_container, fragment, fragmentTag)
         }
 
-        restartActivitiesOnExit = intent.getBooleanExtra("restart", false)
+        onBackPressedDispatcher.addCallback(this, restartActivitiesOnBackPressedCallback)
+        restartActivitiesOnBackPressedCallback.isEnabled = savedInstanceState?.getBoolean(EXTRA_RESTART_ON_BACK, false) ?: false
     }
 
     override fun onResume() {
@@ -106,11 +118,11 @@ class PreferencesActivity :
     }
 
     private fun saveInstanceState(outState: Bundle) {
-        outState.putBoolean("restart", restartActivitiesOnExit)
+        outState.putBoolean(EXTRA_RESTART_ON_BACK, restartActivitiesOnBackPressedCallback.isEnabled)
     }
 
     override fun onSaveInstanceState(outState: Bundle) {
-        outState.putBoolean("restart", restartActivitiesOnExit)
+        outState.putBoolean(EXTRA_RESTART_ON_BACK, restartActivitiesOnBackPressedCallback.isEnabled)
         super.onSaveInstanceState(outState)
     }
 
@@ -121,16 +133,16 @@ class PreferencesActivity :
                 Log.d("activeTheme", theme)
                 ThemeUtils.setAppNightMode(theme)
 
-                restartActivitiesOnExit = true
+                restartActivitiesOnBackPressedCallback.isEnabled = true
                 this.restartCurrentActivity()
             }
             "statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars", "useBlurhash",
             "showSelfUsername", "showCardsInTimelines", "confirmReblogs", "confirmFavourites",
             "enableSwipeForTabs", "mainNavPosition", PrefKeys.HIDE_TOP_TOOLBAR -> {
-                restartActivitiesOnExit = true
+                restartActivitiesOnBackPressedCallback.isEnabled = true
             }
             "language" -> {
-                restartActivitiesOnExit = true
+                restartActivitiesOnBackPressedCallback.isEnabled = true
                 this.restartCurrentActivity()
             }
         }
@@ -148,20 +160,6 @@ class PreferencesActivity :
         overridePendingTransition(R.anim.fade_in, R.anim.fade_out)
     }
 
-    override fun onBackPressed() {
-        /* Switching themes won't actually change the theme of activities on the back stack.
-         * Either the back stack activities need to all be recreated, or do the easier thing, which
-         * is hijack the back button press and use it to launch a new MainActivity and clear the
-         * back stack. */
-        if (restartActivitiesOnExit) {
-            val intent = Intent(this, MainActivity::class.java)
-            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
-            startActivityWithSlideInAnimation(intent)
-        } else {
-            super.onBackPressed()
-        }
-    }
-
     override fun androidInjector() = androidInjector
 
     companion object {
@@ -172,6 +170,7 @@ class PreferencesActivity :
         const val TAB_FILTER_PREFERENCES = 3
         const val PROXY_PREFERENCES = 4
         private const val EXTRA_PREFERENCE_TYPE = "EXTRA_PREFERENCE_TYPE"
+        private const val EXTRA_RESTART_ON_BACK = "restart"
 
         @JvmStatic
         fun newIntent(context: Context, preferenceType: Int): Intent {
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
index a799bee3..b15b9e0f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
@@ -383,7 +383,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
         } != null
     }
 
-    private fun showOpenAsDialog(statusUrl: String, dialogTitle: CharSequence) {
+    private fun showOpenAsDialog(statusUrl: String, dialogTitle: CharSequence?) {
         bottomSheetActivity?.showAccountChooserDialog(
             dialogTitle, false,
             object : AccountSelectionListener {
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt
index cee6f046..9fe91b92 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt
@@ -102,7 +102,7 @@ class ViewThreadFragment : SFragment(), OnRefreshListener, StatusActionListener,
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 
         binding.toolbar.setNavigationOnClickListener {
-            activity?.onBackPressed()
+            activity?.onBackPressedDispatcher?.onBackPressed()
         }
         binding.toolbar.setOnMenuItemClickListener { menuItem ->
             when (menuItem.itemId) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt
index 465b9f21..8cc855ce 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt
@@ -49,6 +49,7 @@ import com.keylesspalace.tusky.network.MastodonApi
 import com.keylesspalace.tusky.settings.PrefKeys
 import com.keylesspalace.tusky.util.HttpHeaderLink
 import com.keylesspalace.tusky.util.hide
+import com.keylesspalace.tusky.util.requireSerializable
 import com.keylesspalace.tusky.util.show
 import com.keylesspalace.tusky.util.viewBinding
 import com.keylesspalace.tusky.view.EndlessOnScrollListener
@@ -78,8 +79,8 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        type = arguments?.getSerializable(ARG_TYPE) as Type
-        id = arguments?.getString(ARG_ID)
+        type = requireArguments().requireSerializable(ARG_TYPE)
+        id = requireArguments().getString(ARG_ID)
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -100,7 +101,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
             Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis)
             Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis)
             Type.FOLLOW_REQUESTS -> {
-                val headerAdapter = FollowRequestsHeaderAdapter(accountManager.activeAccount!!.domain, arguments?.get(ARG_ACCOUNT_LOCKED) == true)
+                val headerAdapter = FollowRequestsHeaderAdapter(accountManager.activeAccount!!.domain, arguments?.getBoolean(ARG_ACCOUNT_LOCKED) == true)
                 val followRequestsAdapter = FollowRequestsAdapter(this, animateAvatar, animateEmojis)
                 binding.recyclerView.adapter = ConcatAdapter(headerAdapter, followRequestsAdapter)
                 followRequestsAdapter
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt
index 0362da9c..53e174bf 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt
@@ -36,6 +36,7 @@ import com.keylesspalace.tusky.ViewMediaActivity
 import com.keylesspalace.tusky.databinding.FragmentViewImageBinding
 import com.keylesspalace.tusky.entity.Attachment
 import com.keylesspalace.tusky.util.hide
+import com.keylesspalace.tusky.util.parcelable
 import com.keylesspalace.tusky.util.visible
 import io.reactivex.rxjava3.subjects.BehaviorSubject
 import kotlin.math.abs
@@ -92,7 +93,7 @@ class ViewImageFragment : ViewMediaFragment() {
         super.onViewCreated(view, savedInstanceState)
 
         val arguments = this.requireArguments()
-        val attachment = arguments.getParcelable<Attachment>(ARG_ATTACHMENT)
+        val attachment: Attachment? = arguments.parcelable(ARG_ATTACHMENT)
         this.shouldStartTransition = arguments.getBoolean(ARG_START_POSTPONED_TRANSITION)
         val url: String?
         var description: String? = null
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt
index 214741a8..6ae33498 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt
@@ -31,6 +31,7 @@ import com.keylesspalace.tusky.ViewMediaActivity
 import com.keylesspalace.tusky.databinding.FragmentViewVideoBinding
 import com.keylesspalace.tusky.entity.Attachment
 import com.keylesspalace.tusky.util.hide
+import com.keylesspalace.tusky.util.parcelable
 import com.keylesspalace.tusky.util.visible
 import com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
 
@@ -170,7 +171,7 @@ class ViewVideoFragment : ViewMediaFragment() {
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        val attachment = arguments?.getParcelable<Attachment>(ARG_ATTACHMENT)
+        val attachment: Attachment? = requireArguments().parcelable(ARG_ATTACHMENT)
 
         if (attachment == null) {
             throw IllegalArgumentException("attachment has to be set")
diff --git a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt
index c1f5a2cc..6ef027f2 100644
--- a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt
@@ -29,6 +29,7 @@ import com.keylesspalace.tusky.entity.Status
 import com.keylesspalace.tusky.service.SendStatusService
 import com.keylesspalace.tusky.service.StatusToSend
 import com.keylesspalace.tusky.util.randomAlphanumericString
+import com.keylesspalace.tusky.util.requireSerializableExtra
 import dagger.android.AndroidInjection
 import javax.inject.Inject
 
@@ -48,7 +49,7 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
             val senderIdentifier = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_IDENTIFIER)
             val senderFullName = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_FULL_NAME)
             val citedStatusId = intent.getStringExtra(NotificationHelper.KEY_CITED_STATUS_ID)
-            val visibility = intent.getSerializableExtra(NotificationHelper.KEY_VISIBILITY) as Status.Visibility
+            val visibility: Status.Visibility = intent.requireSerializableExtra(NotificationHelper.KEY_VISIBILITY)!!
             val spoiler = intent.getStringExtra(NotificationHelper.KEY_SPOILER) ?: ""
             val mentions = intent.getStringArrayExtra(NotificationHelper.KEY_MENTIONS) ?: emptyArray()
 
diff --git a/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt b/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt
index 3cdd27c3..d4710552 100644
--- a/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt
@@ -31,6 +31,7 @@ import com.keylesspalace.tusky.entity.NewPoll
 import com.keylesspalace.tusky.entity.NewStatus
 import com.keylesspalace.tusky.entity.Status
 import com.keylesspalace.tusky.network.MastodonApi
+import com.keylesspalace.tusky.util.parcelableExtra
 import dagger.android.AndroidInjection
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -72,7 +73,7 @@ class SendStatusService : Service(), Injectable {
 
     override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
         if (intent.hasExtra(KEY_STATUS)) {
-            val statusToSend = intent.getParcelableExtra<StatusToSend>(KEY_STATUS)
+            val statusToSend: StatusToSend = intent.parcelableExtra(KEY_STATUS)
                 ?: throw IllegalStateException("SendStatusService started without $KEY_STATUS extra")
 
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/CompatExtensions.kt b/app/src/main/java/com/keylesspalace/tusky/util/CompatExtensions.kt
new file mode 100644
index 00000000..55d2f44b
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/CompatExtensions.kt
@@ -0,0 +1,49 @@
+@file:Suppress("DEPRECATION")
+
+package com.keylesspalace.tusky.util
+
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.os.Parcelable
+import java.io.Serializable
+
+inline fun <reified T : Serializable> Intent.requireSerializableExtra(name: String?): T {
+    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+        getSerializableExtra(name, T::class.java)!!
+    } else {
+        getSerializableExtra(name) as T
+    }
+}
+
+inline fun <reified T : Parcelable> Intent.parcelableArrayListExtra(name: String?): ArrayList<T>? {
+    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+        getParcelableArrayListExtra(name, T::class.java)
+    } else {
+        getParcelableArrayListExtra(name)
+    }
+}
+
+inline fun <reified T : Parcelable> Bundle.parcelable(name: String?): T? {
+    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+        getParcelable(name, T::class.java)
+    } else {
+        getParcelable(name)
+    }
+}
+
+inline fun <reified T : Parcelable> Intent.parcelableExtra(name: String?): T? {
+    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+        getParcelableExtra(name, T::class.java)
+    } else {
+        getParcelableExtra(name) as T?
+    }
+}
+
+inline fun <reified T : Serializable> Bundle.requireSerializable(name: String?): T {
+    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+        getSerializable(name, T::class.java)!!
+    } else {
+        getSerializable(name) as T
+    }
+}
diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt
index 6542b603..bfc4282b 100644
--- a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt
@@ -464,7 +464,7 @@ class ComposeActivityTest {
     }
 
     private fun clickBack() {
-        activity.onBackPressed()
+        activity.onBackPressedDispatcher.onBackPressed()
     }
 
     private fun insertSomeTextInContent(text: String? = null) {