1
1
package org.thoughtcrime.securesms.preferences
2
2
3
+ import android.content.ContentResolver
4
+ import android.content.ContentValues
3
5
import android.content.Intent
6
+ import android.media.MediaScannerConnection
7
+ import android.net.Uri
4
8
import android.os.Build
9
+ import android.os.Environment
10
+ import android.provider.MediaStore
5
11
import android.view.LayoutInflater
12
+ import android.webkit.MimeTypeMap
6
13
import android.widget.Toast
7
14
import androidx.appcompat.app.AlertDialog
8
15
import androidx.lifecycle.lifecycleScope
@@ -12,9 +19,15 @@ import kotlinx.coroutines.Job
12
19
import kotlinx.coroutines.launch
13
20
import network.loki.messenger.BuildConfig
14
21
import network.loki.messenger.R
22
+ import org.session.libsignal.utilities.ExternalStorageUtil
15
23
import org.thoughtcrime.securesms.ApplicationContext
16
24
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
17
- import org.thoughtcrime.securesms.providers.BlobProvider
25
+ import org.thoughtcrime.securesms.util.StreamUtil
26
+ import java.io.File
27
+ import java.io.FileOutputStream
28
+ import java.io.IOException
29
+ import java.util.*
30
+ import java.util.concurrent.TimeUnit
18
31
19
32
class ShareLogsDialog : BaseDialog () {
20
33
@@ -39,16 +52,41 @@ class ShareLogsDialog : BaseDialog() {
39
52
shareJob = lifecycleScope.launch(Dispatchers .IO ) {
40
53
val persistentLogger = ApplicationContext .getInstance(context).persistentLogger
41
54
try {
42
- val logs = persistentLogger.logs.get()
43
- val fileName = " ${Build .MANUFACTURER } -${Build .DEVICE } -API${Build .VERSION .SDK_INT } -v${BuildConfig .VERSION_NAME } .txt"
44
- val logUri = BlobProvider ().forData(logs.toByteArray())
45
- .withFileName(fileName)
46
- .withMimeType(" text/plain" )
47
- .createForSingleSessionOnDisk(requireContext(),null )
55
+ val context = requireContext()
56
+ val outputUri: Uri = ExternalStorageUtil .getDownloadUri()
57
+ val mediaUri = getExternalFile()
58
+ if (mediaUri == null ) {
59
+ // show toast saying media saved
60
+ dismiss()
61
+ return @launch
62
+ }
63
+
64
+ val inputStream = persistentLogger.logs.get().byteInputStream()
65
+ val updateValues = ContentValues ()
66
+ if (outputUri.scheme == ContentResolver .SCHEME_FILE ) {
67
+ FileOutputStream (mediaUri.path).use { outputStream ->
68
+ StreamUtil .copy(inputStream, outputStream)
69
+ MediaScannerConnection .scanFile(context, arrayOf(mediaUri.path), arrayOf(" text/plain" ), null )
70
+ }
71
+ } else {
72
+ context.contentResolver.openOutputStream(mediaUri, " w" ).use { outputStream ->
73
+ val total: Long = StreamUtil .copy(inputStream, outputStream)
74
+ if (total > 0 ) {
75
+ updateValues.put(MediaStore .MediaColumns .SIZE , total)
76
+ }
77
+ }
78
+ }
79
+ if (Build .VERSION .SDK_INT > 28 ) {
80
+ updateValues.put(MediaStore .MediaColumns .IS_PENDING , 0 )
81
+ }
82
+ if (updateValues.size() > 0 ) {
83
+ requireContext().contentResolver.update(mediaUri, updateValues, null , null )
84
+ }
48
85
49
86
val shareIntent = Intent ().apply {
50
87
action = Intent .ACTION_SEND
51
- putExtra(Intent .EXTRA_STREAM , logUri)
88
+ putExtra(Intent .EXTRA_STREAM , mediaUri)
89
+ data = mediaUri
52
90
type = " text/plain"
53
91
}
54
92
@@ -62,4 +100,56 @@ class ShareLogsDialog : BaseDialog() {
62
100
}
63
101
}
64
102
103
+ @Throws(IOException ::class )
104
+ private fun pathTaken (outputUri : Uri , dataPath : String ): Boolean {
105
+ requireContext().contentResolver.query(outputUri, arrayOf(MediaStore .MediaColumns .DATA ),
106
+ MediaStore .MediaColumns .DATA + " = ?" , arrayOf(dataPath),
107
+ null ).use { cursor ->
108
+ if (cursor == null ) {
109
+ throw IOException (" Something is wrong with the filename to save" )
110
+ }
111
+ return cursor.moveToFirst()
112
+ }
113
+ }
114
+
115
+ private fun getExternalFile (): Uri ? {
116
+ val context = requireContext()
117
+ val base = " ${Build .MANUFACTURER } -${Build .DEVICE } -API${Build .VERSION .SDK_INT } -v${BuildConfig .VERSION_NAME } -${System .currentTimeMillis()} "
118
+ val extension = " txt"
119
+ val fileName = " $base .$extension "
120
+ val mimeType = MimeTypeMap .getSingleton().getExtensionFromMimeType(" text/plain" )
121
+ val outputUri: Uri = ExternalStorageUtil .getDownloadUri()
122
+ val contentValues = ContentValues ()
123
+ contentValues.put(MediaStore .MediaColumns .DISPLAY_NAME , fileName)
124
+ contentValues.put(MediaStore .MediaColumns .MIME_TYPE , mimeType)
125
+ contentValues.put(MediaStore .MediaColumns .DATE_ADDED , TimeUnit .MILLISECONDS .toSeconds(System .currentTimeMillis()))
126
+ contentValues.put(MediaStore .MediaColumns .DATE_MODIFIED , TimeUnit .MILLISECONDS .toSeconds(System .currentTimeMillis()))
127
+ if (Build .VERSION .SDK_INT > 28 ) {
128
+ contentValues.put(MediaStore .MediaColumns .IS_PENDING , 1 )
129
+ } else if (Objects .equals(outputUri.scheme, ContentResolver .SCHEME_FILE )) {
130
+ val outputDirectory = File (outputUri.path)
131
+ var outputFile = File (outputDirectory, " $base .$extension " )
132
+ var i = 0
133
+ while (outputFile.exists()) {
134
+ outputFile = File (outputDirectory, base + " -" + ++ i + " ." + extension)
135
+ }
136
+ if (outputFile.isHidden) {
137
+ throw IOException (" Specified name would not be visible" )
138
+ }
139
+ return Uri .fromFile(outputFile)
140
+ } else {
141
+ var outputFileName = fileName
142
+ val externalPath = context.getExternalFilesDir(Environment .DIRECTORY_DOCUMENTS )!!
143
+ var dataPath = String .format(" %s/%s" , externalPath, outputFileName)
144
+ var i = 0
145
+ while (pathTaken(outputUri, dataPath)) {
146
+ outputFileName = base + " -" + ++ i + " ." + extension
147
+ dataPath = String .format(" %s/%s" , externalPath, outputFileName)
148
+ }
149
+ contentValues.put(MediaStore .MediaColumns .DATA , dataPath)
150
+ }
151
+ return context.contentResolver.insert(outputUri, contentValues)
152
+ }
153
+
154
+
65
155
}
0 commit comments