diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt
index 7645f1b8aa8750ba5f04b387618888d32508c1ad..00ccf795235bae3fc73efa24865fafd46731301e 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt
@@ -35,7 +35,6 @@ class DebugLogger(
 ) : DebugLoggerBase() {
 
     private val triggerFile = File(debugDir, "debug.trigger")
-    val sharedDirectory = File(debugDir, "shared")
     internal val runningLog: File
         get() = logWriter.logFile
 
@@ -146,8 +145,6 @@ class DebugLogger(
         logJob = null
 
         logWriter.teardown()
-
-        clearSharedFiles()
     }
 
     private fun startNewLogJob(logLines: Flow<LogLine>) = scope.launch {
@@ -175,22 +172,6 @@ class DebugLogger(
         }
     }
 
-    fun getShareSize(): Long = sharedDirectory.listFiles()
-        ?.fold(0L) { prev, file -> prev + file.length() }
-        ?: 0L
-
-    fun clearSharedFiles() {
-        if (!sharedDirectory.exists()) return
-
-        sharedDirectory.listFiles()?.forEach {
-            if (it.delete()) {
-                Timber.tag(TAG).d("Deleted shared file: %s", it)
-            } else {
-                Timber.tag(TAG).w("Failed to delete shared file: %s", it)
-            }
-        }
-    }
-
     companion object {
         internal const val TAG = "DebugLogger"
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/sharing/LogSnapshotter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/sharing/LogSnapshotter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1cb67b8deb4352f0fcbd6c54db2722ed38e80ace
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/sharing/LogSnapshotter.kt
@@ -0,0 +1,58 @@
+package de.rki.coronawarnapp.bugreporting.debuglog.sharing
+
+import android.content.Context
+import dagger.Reusable
+import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger
+import de.rki.coronawarnapp.util.TimeStamper
+import de.rki.coronawarnapp.util.di.AppContext
+import de.rki.coronawarnapp.util.files.Zipper
+import org.joda.time.format.DateTimeFormat
+import timber.log.Timber
+import java.io.File
+import javax.inject.Inject
+
+@Reusable
+class LogSnapshotter @Inject constructor(
+    @AppContext private val context: Context,
+    private val debugLogger: DebugLogger,
+    private val timeStamper: TimeStamper
+) {
+
+    private val snapshotDir = File(context.cacheDir, "debuglog_snapshots")
+
+    /**
+     * Use **[Snapshot#delete]** after you are done.
+     * Otherwise it will be deleted when another snapshot is taken.
+     * @return a snapshot of the current debug log, or null if there was no log
+     */
+    fun snapshot(): Snapshot {
+        Timber.tag(TAG).d("snapshot()")
+        snapshotDir.listFiles()?.forEach {
+            Timber.tag(TAG).w("Deleting stale snapshot: %s", it)
+        }
+
+        val now = timeStamper.nowUTC
+        val formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS")
+        val formattedFileName = "CWA Log ${now.toString(formatter)}"
+        if (!snapshotDir.exists() && snapshotDir.mkdirs()) {
+            Timber.tag(TAG).v("Created %s", snapshotDir)
+        }
+        val zipFile = File(snapshotDir, "$formattedFileName.zip")
+
+        Zipper(zipFile).zip(
+            listOf(Zipper.Entry(name = "$formattedFileName.txt", path = debugLogger.runningLog))
+        )
+
+        return Snapshot(path = zipFile)
+    }
+
+    data class Snapshot(
+        val path: File
+    ) {
+        fun delete() = path.delete()
+    }
+
+    companion object {
+        private const val TAG = "LogSnapshots"
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/sharing/SAFLogSharing.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/sharing/SAFLogSharing.kt
new file mode 100644
index 0000000000000000000000000000000000000000..31df4941d7ce7521bfc216685519d1509e9e867f
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/sharing/SAFLogSharing.kt
@@ -0,0 +1,61 @@
+package de.rki.coronawarnapp.bugreporting.debuglog.sharing
+
+import android.content.ContentResolver
+import android.content.Intent
+import android.net.Uri
+import de.rki.coronawarnapp.util.files.determineMimeType
+import okio.buffer
+import okio.sink
+import okio.source
+import timber.log.Timber
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class SAFLogSharing @Inject constructor() {
+    private var lastId = 1
+    private val requestMap = mutableMapOf<Int, Request>()
+
+    fun createSAFRequest(snapshot: LogSnapshotter.Snapshot): Request {
+        val request = Request(
+            id = ++lastId,
+            snapshot = snapshot
+        )
+        requestMap[request.id] = request
+        return request
+    }
+
+    fun getRequest(id: Int): Request? = requestMap[id]
+
+    data class Request(
+        val id: Int,
+        val snapshot: LogSnapshotter.Snapshot,
+    ) {
+        fun createIntent() = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+            addCategory(Intent.CATEGORY_OPENABLE)
+            type = snapshot.path.determineMimeType()
+            putExtra(Intent.EXTRA_TITLE, snapshot.path.name)
+        }
+
+        fun storeSnapshot(resolver: ContentResolver, uri: Uri): Result {
+            Timber.tag(TAG).d("Writing to %s", uri)
+            resolver.openOutputStream(uri)!!.sink().buffer().use { dest ->
+                snapshot.path.source().buffer().use { source ->
+                    dest.writeAll(source)
+                }
+            }
+            Timber.tag(TAG).i("%s was written to %s", snapshot, uri)
+
+            snapshot.delete().also {
+                Timber.tag(TAG).d("Snapshot deleted: %s", snapshot)
+            }
+            return Result(uri)
+        }
+
+        data class Result(val storageUri: Uri)
+    }
+
+    companion object {
+        private const val TAG = "SAFLogSharing"
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragment.kt
index f4d9c0c415347204523485fe1ae585cbf8619dc5..7e1135d109d24be2ae74d2b837bf25ac1f18e608 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragment.kt
@@ -1,5 +1,7 @@
 package de.rki.coronawarnapp.bugreporting.debuglog.ui
 
+import android.app.Activity
+import android.content.Intent
 import android.os.Bundle
 import android.text.format.Formatter
 import android.view.View
@@ -18,6 +20,7 @@ import de.rki.coronawarnapp.util.ui.setGone
 import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
 import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
+import timber.log.Timber
 import javax.inject.Inject
 
 class DebugLogFragment : Fragment(R.layout.bugreporting_debuglog_fragment), AutoInject {
@@ -30,6 +33,8 @@ class DebugLogFragment : Fragment(R.layout.bugreporting_debuglog_fragment), Auto
         super.onViewCreated(view, savedInstanceState)
 
         binding.apply {
+            toolbar.setNavigationOnClickListener { popBackStack() }
+
             if (explanationSectionTwo.text ==
                 getString(R.string.debugging_debuglog_intro_explanation_section_two)
             ) {
@@ -39,6 +44,7 @@ class DebugLogFragment : Fragment(R.layout.bugreporting_debuglog_fragment), Auto
                     R.string.debugging_debuglog_intro_explanation_section_two_faq_link
                 )
             }
+            debugLogPrivacyInformation.setOnClickListener { vm.onPrivacyButtonPress() }
         }
 
         vm.state.observe2(this) {
@@ -66,24 +72,45 @@ class DebugLogFragment : Fragment(R.layout.bugreporting_debuglog_fragment), Auto
 
                 toggleRecording.apply {
                     isActivated = it.isRecording
-                    isEnabled = !it.sharingInProgress
+                    isEnabled = !it.isActionInProgress
                     text = getString(
                         if (it.isRecording) R.string.debugging_debuglog_action_stop_recording
                         else R.string.debugging_debuglog_action_start_recording
                     )
-                    setOnClickListener { vm.toggleRecording() }
+                    setOnClickListener { vm.onToggleRecording() }
                 }
 
                 toggleSendErrorLog.apply {
                     isGone = !it.isRecording
-                    isEnabled = it.currentSize > 0L && !it.sharingInProgress
-                    setOnClickListener { vm.shareRecording() }
+                    isEnabled = it.currentSize > 0L && !it.isActionInProgress
+                    setOnClickListener { vm.onUploadLog() }
                 }
 
-                toggleStoreLog.isGone = !it.isRecording
+                toggleStoreLog.apply {
+                    isGone = !it.isRecording
+                    isEnabled = it.currentSize > 0L && !it.isActionInProgress
+                    setOnClickListener { vm.onStoreLog() }
+                }
             }
         }
 
+        vm.shareEvent.observe2(this@DebugLogFragment) {
+            startActivityForResult(it.createIntent(), it.id)
+        }
+
+        vm.logStoreResult.observe2(this) {
+            Toast.makeText(
+                requireContext(),
+                "TODO: Show store result dialog: ${it.storageUri}",
+                Toast.LENGTH_LONG
+            ).show()
+        }
+
+        vm.logUploads.observe2(this@DebugLogFragment) {
+            binding.debugLogHistoryContainer.setGone(it.logs.isEmpty())
+        }
+        binding.debugLogHistoryContainer.setOnClickListener { vm.onIdHistoryPress() }
+
         vm.routeToScreen.observe2(this) {
             when (it) {
                 DebugLogNavigationEvents.NavigateToPrivacyFragment -> doNavigate(
@@ -98,19 +125,13 @@ class DebugLogFragment : Fragment(R.layout.bugreporting_debuglog_fragment), Auto
         vm.errorEvent.observe2(this) {
             Toast.makeText(requireContext(), it.toString(), Toast.LENGTH_LONG).show()
         }
+    }
 
-        vm.shareEvent.observe2(this) {
-            startActivity(it.get(requireActivity()))
-        }
-
-        vm.logUploads.observe2(this@DebugLogFragment) {
-            binding.debugLogHistoryContainer.setGone(it.logs.isEmpty())
-        }
-
-        binding.apply {
-            debugLogHistoryContainer.setOnClickListener { vm.onIdHistoryPress() }
-            debugLogPrivacyInformation.setOnClickListener { vm.onPrivacyButtonPress() }
-            toolbar.setNavigationOnClickListener { popBackStack() }
-        }
+    override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
+        Timber.d("onActivityResult(requestCode=$requestCode, resultCode=$resultCode, resultData=$resultData")
+        vm.processSAFResult(
+            requestCode,
+            if (resultCode == Activity.RESULT_OK) resultData?.data else null
+        )
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt
index 4db122764569d8307f7471e72a847a8869841834..542d5ff9f8f0e03b152bb797e2e5c9332043dfd5 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt
@@ -1,57 +1,58 @@
 package de.rki.coronawarnapp.bugreporting.debuglog.ui
 
+import android.content.ContentResolver
+import android.net.Uri
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.asLiveData
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.bugreporting.BugReportingSettings
 import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger
+import de.rki.coronawarnapp.bugreporting.debuglog.sharing.LogSnapshotter
+import de.rki.coronawarnapp.bugreporting.debuglog.sharing.SAFLogSharing
 import de.rki.coronawarnapp.nearby.ENFClient
 import de.rki.coronawarnapp.util.CWADebug
-import de.rki.coronawarnapp.util.TimeStamper
-import de.rki.coronawarnapp.util.compression.Zipper
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
-import de.rki.coronawarnapp.util.sharing.FileSharing
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
-import org.joda.time.format.DateTimeFormat
 import timber.log.Timber
-import java.io.File
 
 class DebugLogViewModel @AssistedInject constructor(
     private val debugLogger: DebugLogger,
     dispatcherProvider: DispatcherProvider,
-    private val timeStamper: TimeStamper,
-    private val fileSharing: FileSharing,
     private val enfClient: ENFClient,
-    bugReportingSettings: BugReportingSettings
+    bugReportingSettings: BugReportingSettings,
+    private val logSnapshotter: LogSnapshotter,
+    private val safLogSharing: SAFLogSharing,
+    private val contentResolver: ContentResolver,
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
-    val logUploads = bugReportingSettings.uploadHistory.flow
-        .asLiveData(context = dispatcherProvider.Default)
-
-    private val sharingInProgress = MutableStateFlow(false)
+    private val isActionInProgress = MutableStateFlow(false)
 
     val routeToScreen = SingleLiveEvent<DebugLogNavigationEvents>()
 
+    val logUploads = bugReportingSettings.uploadHistory.flow
+        .asLiveData(context = dispatcherProvider.Default)
+
     val state: LiveData<State> = combine(
-        sharingInProgress,
+        isActionInProgress,
         debugLogger.logState
-    ) { sharingInProgress, logState ->
+    ) { isActionInProgress, logState ->
         State(
             isRecording = logState.isLogging,
             isLowStorage = logState.isLowStorage,
-            currentSize = logState.logSize + debugLogger.getShareSize(),
-            sharingInProgress = sharingInProgress
+            currentSize = logState.logSize,
+            isActionInProgress = isActionInProgress
         )
     }.asLiveData(context = dispatcherProvider.Default)
 
     val errorEvent = SingleLiveEvent<Throwable>()
-    val shareEvent = SingleLiveEvent<FileSharing.ShareIntentProvider>()
+    val shareEvent = SingleLiveEvent<SAFLogSharing.Request>()
+    val logStoreResult = SingleLiveEvent<SAFLogSharing.Request.Result>()
 
     fun onPrivacyButtonPress() {
         routeToScreen.postValue(DebugLogNavigationEvents.NavigateToPrivacyFragment)
@@ -61,56 +62,69 @@ class DebugLogViewModel @AssistedInject constructor(
         routeToScreen.postValue(DebugLogNavigationEvents.NavigateToUploadHistory)
     }
 
-    fun toggleRecording() = launch {
-        try {
-            if (debugLogger.isLogging.value) {
-                debugLogger.stop()
-            } else {
-                debugLogger.start()
-                printExtendedLogInfos()
+    fun onToggleRecording() = launchWithProgress {
+        if (debugLogger.isLogging.value) {
+            debugLogger.stop()
+        } else {
+            debugLogger.start()
+
+            CWADebug.logDeviceInfos()
+            try {
+                val enfVersion = enfClient.getENFClientVersion()
+                Timber.tag("ENFClient").i("ENF Version: %d", enfVersion)
+            } catch (e: Exception) {
+                Timber.tag("ENFClient").e(e, "Failed to get ENF version for debug log.")
             }
-        } catch (e: Exception) {
-            errorEvent.postValue(e)
         }
     }
 
-    private suspend fun printExtendedLogInfos() {
-        CWADebug.logDeviceInfos()
-        try {
-            val enfVersion = enfClient.getENFClientVersion()
-            Timber.tag("ENFClient").i("ENF Version: %d", enfVersion)
-        } catch (e: Exception) {
-            Timber.tag("ENFClient").e(e, "Failed to get ENF version for debug log.")
-        }
+    fun onUploadLog() = launchWithProgress {
+        Timber.d("uploadLog()")
+        throw NotImplementedError("TODO")
     }
 
-    fun shareRecording() {
-        sharingInProgress.value = true
-        launch {
-            try {
-                debugLogger.clearSharedFiles()
+    fun onStoreLog() = launchWithProgress(finishProgressAction = false) {
+        Timber.d("storeLog()")
+        val snapshot = logSnapshotter.snapshot()
+        val shareRequest = safLogSharing.createSAFRequest(snapshot)
+        shareEvent.postValue(shareRequest)
+    }
 
-                val now = timeStamper.nowUTC
-                val formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS")
-                val formattedFileName = "CWA Log ${now.toString(formatter)}"
-                val zipFile = File(debugLogger.sharedDirectory, "$formattedFileName.zip")
+    fun processSAFResult(requestCode: Int, safPath: Uri?) = launchWithProgress {
+        if (safPath == null) {
+            Timber.i("No SAF path available.")
+            return@launchWithProgress
+        }
 
-                Zipper(zipFile).zip(
-                    listOf(Zipper.Entry(name = "$formattedFileName.txt", path = debugLogger.runningLog))
-                )
+        val request = safLogSharing.getRequest(requestCode)
+        if (request == null) {
+            Timber.w("Unknown request with code $requestCode")
+            return@launchWithProgress
+        }
 
-                val intentProvider = fileSharing.getIntentProvider(
-                    path = zipFile,
-                    title = zipFile.name,
-                    chooserTitle = R.string.debugging_debuglog_sharing_dialog_title
-                )
+        val storageResult = request.storeSnapshot(contentResolver, safPath)
+        Timber.i("Log stored %s", storageResult)
 
-                shareEvent.postValue(intentProvider)
-            } catch (e: Exception) {
-                Timber.e(e, "Sharing debug log failed.")
+        logStoreResult.postValue(storageResult)
+    }
+
+    private fun launchWithProgress(
+        finishProgressAction: Boolean = true,
+        block: suspend CoroutineScope.() -> Unit
+    ) {
+        val startTime = System.currentTimeMillis()
+        isActionInProgress.value = true
+
+        launch {
+            try {
+                block()
+            } catch (e: Throwable) {
+                Timber.e(e, "launchWithProgress() failed.")
                 errorEvent.postValue(e)
             } finally {
-                sharingInProgress.value = false
+                val duration = System.currentTimeMillis() - startTime
+                Timber.v("launchWithProgress() took ${duration}ms")
+                if (finishProgressAction) isActionInProgress.value = false
             }
         }
     }
@@ -118,7 +132,7 @@ class DebugLogViewModel @AssistedInject constructor(
     data class State(
         val isRecording: Boolean,
         val isLowStorage: Boolean,
-        val sharingInProgress: Boolean = false,
+        val isActionInProgress: Boolean = false,
         val currentSize: Long = 0
     )
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt
index e70f708ad45a87382060f765cff9ee3bb4d31560..d548e33cdd1043f678b41558e4d97da5c38a2a43 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt
@@ -4,6 +4,7 @@ import android.app.ActivityManager
 import android.app.Application
 import android.app.NotificationManager
 import android.bluetooth.BluetoothAdapter
+import android.content.ContentResolver
 import android.content.Context
 import android.content.SharedPreferences
 import androidx.core.app.NotificationManagerCompat
@@ -76,4 +77,7 @@ class AndroidModule {
     @Provides
     @Singleton
     fun safetyNet(@AppContext context: Context): SafetyNetClient = SafetyNet.getClient(context)
+
+    @Provides
+    fun contentResolver(@AppContext context: Context): ContentResolver = context.contentResolver
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8bf768b7824255e8be19e89883b8ae0b2172823d
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileExtensions.kt
@@ -0,0 +1,8 @@
+package de.rki.coronawarnapp.util.files
+
+import java.io.File
+
+fun File.determineMimeType(): String = when {
+    name.endsWith(".zip") -> "application/zip"
+    else -> throw UnsupportedOperationException("Unsupported MIME type: $path")
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/sharing/FileSharing.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileSharing.kt
similarity index 84%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/sharing/FileSharing.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileSharing.kt
index 59dff05d68a47b0d753c7edfafbc7c5f01a9e156..4303506d6e561362417e660cb3a0e2833f31af53 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/sharing/FileSharing.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileSharing.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.util.sharing
+package de.rki.coronawarnapp.util.files
 
 import android.app.Activity
 import android.content.Context
@@ -32,7 +32,7 @@ class FileSharing @Inject constructor(
     ): ShareIntentProvider = object : ShareIntentProvider {
         override fun get(activity: Activity): Intent {
             val builder = ShareCompat.IntentBuilder.from(activity).apply {
-                setType(determineMimeType(path))
+                setType(path.determineMimeType())
                 setStream(getFileUri(path))
                 setSubject(title)
                 chooserTitle?.let { setChooserTitle(it) }
@@ -54,11 +54,6 @@ class FileSharing @Inject constructor(
         fun get(activity: Activity): Intent
     }
 
-    private fun determineMimeType(path: File): String = when {
-        path.name.endsWith(".zip") -> "application/zip"
-        else -> throw UnsupportedOperationException("Unsupported MIME type: $path")
-    }
-
     companion object {
         private const val AUTHORITY = "${BuildConfig.APPLICATION_ID}.fileProvider"
         private const val TAG = "FileSharing"
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/compression/Zipper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/Zipper.kt
similarity index 96%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/compression/Zipper.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/Zipper.kt
index 2c7924c2b5e2a462303ef3f74b32452e090e466a..49e5fd32ef870263fb573d4babe0e2b87657abb7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/compression/Zipper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/Zipper.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.util.compression
+package de.rki.coronawarnapp.util.files
 
 import timber.log.Timber
 import java.io.File
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt
index 83495f172e2d50f4dccd7317b15e79a20257ad76..61dbd80eafbcd892ac79d14ba4b32cd4f0eb0b6a 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt
@@ -23,7 +23,6 @@ import testhelpers.coroutines.test
 import testhelpers.logging.JUnitTree
 import timber.log.Timber
 import java.io.File
-import kotlin.random.Random
 
 @Suppress("BlockingMethodInNonBlockingContext")
 class DebugLoggerTest : BaseIOTest() {
@@ -35,7 +34,6 @@ class DebugLoggerTest : BaseIOTest() {
     private val testDir = File(IO_TEST_BASEDIR, this::class.simpleName!!)
     private val cacheDir = File(testDir, "cache")
     private val debugLogDir = File(cacheDir, "debuglog")
-    private val sharedDir = File(debugLogDir, "shared")
     private val runningLog = File(debugLogDir, "debug.log")
     private val triggerFile = File(debugLogDir, "debug.trigger")
 
@@ -140,24 +138,17 @@ class DebugLoggerTest : BaseIOTest() {
 
         Timber.forest().none { it is DebugLogTree } shouldBe true
 
-        File(sharedDir, "1").apply {
-            parentFile?.mkdirs()
-            appendBytes(Random.nextBytes(10))
-        }
-
         instance.start()
         instance.start()
         instance.start()
 
         Timber.forest().single { it is DebugLogTree } shouldNotBe null
-        sharedDir.listFiles()!!.size shouldBe 1
 
         instance.stop()
         instance.stop()
 
         Timber.forest().none { it is DebugLogTree } shouldBe true
         instance.isLogging.value shouldBe false
-        sharedDir.listFiles()!!.size shouldBe 0
     }
 
     @Test
@@ -180,17 +171,6 @@ class DebugLoggerTest : BaseIOTest() {
         runningLog.exists() shouldBe false
     }
 
-    @Test
-    fun `shared size aggregates shared folder size`() = runBlockingTest {
-        sharedDir.mkdirs()
-        File(sharedDir, "1").apply { appendBytes(Random.nextBytes(10)) }
-        File(sharedDir, "2").apply { appendBytes(Random.nextBytes(15)) }
-        createInstance(scope = this).apply {
-            init()
-            getShareSize() shouldBe 25
-        }
-    }
-
     @Test
     fun `logwriter is setup and used`() = runBlockingTest {
         val instance = createInstance(scope = this).apply {
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/sharing/LogSnapshotterTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/sharing/LogSnapshotterTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f823097df42336229e965cff9951b9a82ef3263c
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/sharing/LogSnapshotterTest.kt
@@ -0,0 +1,76 @@
+package de.rki.coronawarnapp.bugreporting.debuglog.sharing
+
+import android.content.Context
+import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger
+import de.rki.coronawarnapp.util.TimeStamper
+import io.kotest.matchers.shouldBe
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import org.joda.time.Instant
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseIOTest
+import timber.log.Timber
+import java.io.File
+
+class LogSnapshotterTest : BaseIOTest() {
+
+    @MockK lateinit var context: Context
+    @MockK lateinit var debugLogger: DebugLogger
+    @MockK lateinit var timeStamper: TimeStamper
+
+    private val testDir = File(IO_TEST_BASEDIR, this::class.simpleName!!)
+    private val cacheDir = File(testDir, "cache")
+    private val runningLogFake = File(testDir, "running.log")
+
+    private val snapshotDir = File(cacheDir, "debuglog_snapshots")
+    private val expectedSnapshot = File(snapshotDir, "CWA Log 1970-01-01 00:00:00.000.zip")
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+
+        every { context.cacheDir } returns cacheDir
+
+        testDir.mkdirs()
+        testDir.exists() shouldBe true
+
+        every { debugLogger.runningLog } returns runningLogFake
+        every { timeStamper.nowUTC } returns Instant.EPOCH
+
+        runningLogFake.parentFile!!.mkdirs()
+        runningLogFake.writeText("1 Doge = 1 Doge")
+    }
+
+    @AfterEach
+    fun teardown() {
+        testDir.deleteRecursively()
+        Timber.uprootAll()
+    }
+
+    private fun createInstance() = LogSnapshotter(
+        context = context,
+        debugLogger = debugLogger,
+        timeStamper = timeStamper
+    )
+
+    @Test
+    fun `normal snapshot`() {
+        val instance = createInstance()
+
+        val snapshot = instance.snapshot()
+
+        snapshot.apply {
+            path shouldBe expectedSnapshot
+            path.exists() shouldBe true
+            path.length() shouldBe 197L
+        }
+
+        snapshot.apply {
+            delete()
+            snapshot.path.exists() shouldBe false
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/sharing/SAFLogSharingTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/sharing/SAFLogSharingTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..eb323f351d0bdd530bb575518706fbf5ddbe731f
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/sharing/SAFLogSharingTest.kt
@@ -0,0 +1,71 @@
+package de.rki.coronawarnapp.bugreporting.debuglog.sharing
+
+import android.content.ContentResolver
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.shouldNotBe
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.mockk
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseIOTest
+import timber.log.Timber
+import java.io.File
+
+class SAFLogSharingTest : BaseIOTest() {
+
+    @MockK lateinit var contentResolver: ContentResolver
+
+    private val testDir = File(IO_TEST_BASEDIR, this::class.simpleName!!)
+    private val zipFile = File(testDir, "logfile.zip")
+    private val uriFakeFile = File(testDir, "urifakefile")
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+
+        testDir.mkdirs()
+        testDir.exists() shouldBe true
+
+        every { contentResolver.openOutputStream(any()) } answers {
+            uriFakeFile.outputStream()
+        }
+    }
+
+    @AfterEach
+    fun teardown() {
+        testDir.deleteRecursively()
+        Timber.uprootAll()
+    }
+
+    private fun createInstance() = SAFLogSharing()
+
+    @Test
+    fun `request creation and write`() {
+        val instance = createInstance()
+
+        zipFile.createNewFile()
+        zipFile.exists() shouldBe true
+        zipFile.writeText("testcontent")
+
+        val snapshot = LogSnapshotter.Snapshot(zipFile)
+        val request = instance.createSAFRequest(snapshot)
+        request.snapshot shouldBe snapshot
+
+        request.storeSnapshot(contentResolver, mockk())
+        zipFile.exists() shouldBe false
+        uriFakeFile.readText() shouldBe "testcontent"
+    }
+
+    @Test
+    fun `new requests increase id`() {
+        val instance = createInstance()
+        instance.createSAFRequest(mockk()).id shouldBe 2
+        instance.getRequest(2) shouldNotBe null
+        instance.createSAFRequest(mockk()).id shouldBe 3
+        instance.getRequest(3) shouldNotBe null
+        instance.getRequest(4) shouldBe null
+    }
+}