منبع منتشرشده
دانلود ZIP منبع
CH-J SM Mobile
پوشهها و فایلهای یک نسخهٔ مشخص از برنامه را مرور کنید.
CH-J / SM Mobile
175 فایل
sm-mobile-20261016T185235Z-8e93ecc1b59a
افزودهشده: · ساختهشده:
1
package eu.chj.sm_mobile3
import android.app.Activity4
import android.content.ActivityNotFoundException5
import android.content.Intent6
import android.database.Cursor7
import android.graphics.Bitmap8
import android.graphics.Color9
import android.graphics.pdf.PdfRenderer10
import android.net.Uri11
import android.os.Handler12
import android.os.Looper13
import android.os.ParcelFileDescriptor14
import android.provider.DocumentsContract15
import android.provider.OpenableColumns16
import androidx.activity.result.ActivityResultLauncher17
import androidx.activity.result.contract.ActivityResultContracts18
import androidx.core.content.FileProvider19
import androidx.fragment.app.FragmentActivity20
import io.flutter.plugin.common.MethodCall21
import io.flutter.plugin.common.MethodChannel22
import java.io.ByteArrayOutputStream23
import java.io.File24
import java.io.FileNotFoundException25
import java.io.IOException26
import java.util.concurrent.Executors28
/**29
* Files on the device through the Storage Access Framework. Only documents30
* the user picks (and folders the user grants) are accessed; no broad31
* storage permission is requested.32
*/33
class Documents(private val activity: FragmentActivity) : MethodChannel.MethodCallHandler {34
var channel: MethodChannel? = null35
private val io = Executors.newSingleThreadExecutor()36
private val main = Handler(Looper.getMainLooper())37
private var pending: ((Intent?) -> Unit)? = null38
private val incoming = mutableListOf<Map<String, Any?>>()39
private val launcher: ActivityResultLauncher<Intent> =40
activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { r ->41
val callback = pending42
pending = null43
callback?.invoke(if (r.resultCode == Activity.RESULT_OK) r.data else null)44
}46
private val resolver get() = activity.contentResolver48
private fun launch(intent: Intent, result: MethodChannel.Result, then: (Intent?) -> Unit) {49
if (pending != null) {50
result.error("busy", "A picker is already open", null)51
return52
}53
pending = then54
try {55
launcher.launch(intent)56
} catch (e: ActivityNotFoundException) {57
pending = null58
result.error("unsupported", "No document provider", null)59
}60
}62
private fun background(result: MethodChannel.Result, work: () -> Any?) {63
io.execute {64
try {65
val value = work()66
main.post { result.success(value) }67
} catch (e: SecurityException) {68
main.post { result.error("denied", e.message, null) }69
} catch (e: FileNotFoundException) {70
main.post { result.error("notFound", e.message, null) }71
} catch (e: ReadOnly) {72
main.post { result.error("readOnly", e.message, null) }73
} catch (e: IOException) {74
val code = if (e.message?.contains("ENOSPC") == true ||75
e.message?.contains("No space") == true) "noSpace" else "io"76
main.post { result.error(code, e.message, null) }77
} catch (e: Exception) {78
main.post { result.error("io", e.message, null) }79
}80
}81
}83
class ReadOnly(message: String) : Exception(message)85
private fun persist(uri: Uri, flags: Int) {86
val rw = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION87
try {88
resolver.takePersistableUriPermission(uri, flags and rw)89
} catch (_: SecurityException) {90
try {91
resolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)92
} catch (_: SecurityException) {93
// A temporary grant only (for example a file shared by another app).94
}95
}96
}98
private fun canWrite(uri: Uri): Boolean =99
activity.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) ==100
android.content.pm.PackageManager.PERMISSION_GRANTED102
private fun describe(uri: Uri, location: String? = null): Map<String, Any?> {103
var name: String? = null104
var size: Long? = null105
var mime: String? = null106
var modified: Long? = null107
var flags = 0108
try {109
resolver.query(uri, null, null, null, null)?.use { c: Cursor ->110
if (c.moveToFirst()) {111
fun col(n: String) = c.getColumnIndex(n)112
col(OpenableColumns.DISPLAY_NAME).takeIf { it >= 0 }?.let { name = c.getString(it) }113
col(OpenableColumns.SIZE).takeIf { it >= 0 && !c.isNull(it) }?.let { size = c.getLong(it) }114
col(DocumentsContract.Document.COLUMN_MIME_TYPE).takeIf { it >= 0 }?.let { mime = c.getString(it) }115
col(DocumentsContract.Document.COLUMN_LAST_MODIFIED).takeIf { it >= 0 && !c.isNull(it) }116
?.let { modified = c.getLong(it) }117
col(DocumentsContract.Document.COLUMN_FLAGS).takeIf { it >= 0 }?.let { flags = c.getInt(it) }118
}119
}120
} catch (_: Exception) {121
// Some providers answer only part of the columns.122
}123
if (mime == null) mime = resolver.getType(uri)124
val directory = mime == DocumentsContract.Document.MIME_TYPE_DIR125
val providerWrite = flags and DocumentsContract.Document.FLAG_SUPPORTS_WRITE != 0 ||126
!DocumentsContract.isDocumentUri(activity, uri)127
return mapOf(128
"id" to uri.toString(),129
"name" to (name ?: uri.lastPathSegment ?: "?"),130
"size" to size,131
"mime" to mime,132
"modified" to modified,133
"directory" to directory,134
"writable" to (!directory && providerWrite && canWrite(uri)),135
"location" to (location ?: uri.authority),136
)137
}139
private fun readAll(uri: Uri): ByteArray =140
resolver.openInputStream(uri)?.use { it.readBytes() } ?: throw FileNotFoundException(uri.toString())142
private fun writeAll(uri: Uri, bytes: ByteArray) {143
val out = try {144
resolver.openOutputStream(uri, "wt")145
} catch (_: IllegalArgumentException) {146
resolver.openOutputStream(uri, "w")147
} ?: throw ReadOnly("No output stream")148
out.use {149
it.write(bytes)150
it.flush()151
}152
}154
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {155
when (call.method) {156
"pick" -> {157
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)158
.addCategory(Intent.CATEGORY_OPENABLE)159
.setType("*/*")160
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, call.argument<Boolean>("multiple") == true)161
.addFlags(162
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or163
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION,164
)165
launch(intent, result) { data ->166
if (data == null) return@launch result.success(emptyList<Any>())167
val uris = mutableListOf<Uri>()168
data.clipData?.let { clip -> for (i in 0 until clip.itemCount) uris.add(clip.getItemAt(i).uri) }169
if (uris.isEmpty()) data.data?.let { uris.add(it) }170
background(result) {171
uris.map {172
persist(it, data.flags)173
describe(it)174
}175
}176
}177
}178
"pickFolder" -> {179
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).addFlags(180
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or181
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION,182
)183
launch(intent, result) { data ->184
val tree = data?.data ?: return@launch result.success(null)185
background(result) {186
persist(tree, data.flags)187
val doc = DocumentsContract.buildDocumentUriUsingTree(tree, DocumentsContract.getTreeDocumentId(tree))188
describe(doc)189
}190
}191
}192
"list" -> background(result) {193
val folder = Uri.parse(call.argument<String>("id")!!)194
val children = DocumentsContract.buildChildDocumentsUriUsingTree(195
folder, DocumentsContract.getDocumentId(folder),196
)197
val out = mutableListOf<Map<String, Any?>>()198
resolver.query(199
children, arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID), null, null, null,200
)?.use { c ->201
while (c.moveToNext()) {202
out.add(describe(DocumentsContract.buildDocumentUriUsingTree(folder, c.getString(0))))203
}204
}205
out206
}207
"stat" -> background(result) { describe(Uri.parse(call.argument<String>("id")!!)) }208
"read" -> background(result) { readAll(Uri.parse(call.argument<String>("id")!!)) }209
"write" -> background(result) {210
val uri = Uri.parse(call.argument<String>("id")!!)211
val bytes = call.argument<ByteArray>("bytes")!!212
if (!canWrite(uri)) throw ReadOnly("No write permission")213
// SAF has no atomic replace: keep the old content and put it back if214
// writing fails part way.215
val previous = try { readAll(uri) } catch (_: Exception) { null }216
try {217
writeAll(uri, bytes)218
} catch (e: Exception) {219
if (previous != null) try { writeAll(uri, previous) } catch (_: Exception) {}220
throw e221
}222
describe(uri)223
}224
"saveAs" -> {225
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)226
.addCategory(Intent.CATEGORY_OPENABLE)227
.setType(call.argument<String>("mime") ?: "application/octet-stream")228
.putExtra(Intent.EXTRA_TITLE, call.argument<String>("name"))229
val bytes = call.argument<ByteArray>("bytes")!!230
launch(intent, result) { data ->231
val uri = data?.data ?: return@launch result.success(null)232
background(result) {233
persist(uri, data.flags or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)234
writeAll(uri, bytes)235
describe(uri)236
}237
}238
}239
"create" -> background(result) {240
val folder = Uri.parse(call.argument<String>("folder")!!)241
val uri = DocumentsContract.createDocument(242
resolver, folder, call.argument<String>("mime") ?: "application/octet-stream",243
call.argument<String>("name")!!,244
) ?: throw ReadOnly("Cannot create")245
writeAll(uri, call.argument<ByteArray>("bytes")!!)246
describe(uri)247
}248
"preview" -> {249
val file = File(call.argument<String>("path")!!)250
if (!file.canonicalPath.startsWith(activity.cacheDir.canonicalPath)) {251
return result.error("denied", "Only private temporary copies", null)252
}253
val uri = FileProvider.getUriForFile(activity, "${activity.packageName}.smfiles", file)254
val view = Intent(Intent.ACTION_VIEW)255
.setDataAndType(uri, call.argument<String>("mime") ?: resolver.getType(uri) ?: "*/*")256
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)257
try {258
activity.startActivity(Intent.createChooser(view, file.name))259
result.success(true)260
} catch (_: ActivityNotFoundException) {261
result.success(false)262
}263
}264
"pdfPageCount" -> background(result) {265
ParcelFileDescriptor.open(File(call.argument<String>("path")!!), ParcelFileDescriptor.MODE_READ_ONLY).use { fd ->266
PdfRenderer(fd).use { it.pageCount }267
}268
}269
"pdfPage" -> background(result) {270
val width = call.argument<Int>("width")!!.coerceIn(200, 3000)271
ParcelFileDescriptor.open(File(call.argument<String>("path")!!), ParcelFileDescriptor.MODE_READ_ONLY).use { fd ->272
PdfRenderer(fd).use { renderer ->273
renderer.openPage(call.argument<Int>("index")!!).use { page ->274
val height = (width.toLong() * page.height / page.width).toInt().coerceAtLeast(1)275
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)276
bitmap.eraseColor(Color.WHITE)277
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)278
val out = ByteArrayOutputStream()279
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)280
bitmap.recycle()281
out.toByteArray()282
}283
}284
}285
}286
"takeIncoming" -> {287
val list = incoming.toList()288
incoming.clear()289
result.success(list)290
}291
"forget" -> {292
try {293
resolver.releasePersistableUriPermission(294
Uri.parse(call.argument<String>("id")!!),295
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION,296
)297
} catch (_: Exception) {}298
result.success(null)299
}300
else -> result.notImplemented()301
}302
}304
/** Files from "Open with" / "Share" in other apps. */305
fun handle(intent: Intent?) {306
if (intent == null) return307
val uris = mutableListOf<Uri>()308
when (intent.action) {309
Intent.ACTION_VIEW -> intent.data?.let { uris.add(it) }310
Intent.ACTION_SEND -> @Suppress("DEPRECATION")311
(intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM))?.let { uris.add(it) }312
Intent.ACTION_SEND_MULTIPLE -> @Suppress("DEPRECATION")313
intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)?.let { uris.addAll(it) }314
}315
val content = uris.filter { it.scheme == "content" }316
if (content.isEmpty()) return317
io.execute {318
val docs = content.map { describe(it) }319
main.post {320
val ch = channel321
if (ch == null) incoming.addAll(docs) else {322
incoming.addAll(docs)323
ch.invokeMethod("incoming", incoming.toList())324
incoming.clear()325
}326
}327
}328
}329
}SHA-256
8a6973643dbcb318eadeb1ed102e88b0aabf56021e4aa39ada3081fe95f65dd9CH-J Proprietary Software License 1.14 · کد منبع تحت CH-J Proprietary Software License 1.14 ارائه میشود. در دسترس بودن آن شرایط مجوز را تغییر نمیدهد و حق اضافهای اعطا نمیکند.
SHA-256 بایگانی
46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655