2018-12-01 00:02:18 +01:00
|
|
|
package jp.juggler.util
|
|
|
|
|
|
|
|
import android.content.ContentResolver
|
|
|
|
import android.content.Context
|
|
|
|
import android.content.Intent
|
|
|
|
import android.net.Uri
|
|
|
|
import android.os.Build
|
|
|
|
import android.provider.OpenableColumns
|
|
|
|
import android.webkit.MimeTypeMap
|
2021-06-20 15:12:25 +02:00
|
|
|
import androidx.annotation.RawRes
|
2022-01-06 00:21:47 +01:00
|
|
|
import okhttp3.internal.closeQuietly
|
2018-12-01 00:02:18 +01:00
|
|
|
import java.io.InputStream
|
|
|
|
|
2019-10-06 13:23:33 +02:00
|
|
|
// internal object StorageUtils{
|
|
|
|
//
|
|
|
|
// private val log = LogCategory("StorageUtils")
|
|
|
|
//
|
|
|
|
// private const val PATH_TREE = "tree"
|
|
|
|
// private const val PATH_DOCUMENT = "document"
|
|
|
|
//
|
|
|
|
// internal class FileInfo(any_uri : String?) {
|
|
|
|
//
|
|
|
|
// var uri : Uri? = null
|
|
|
|
// private var mime_type : String? = null
|
|
|
|
//
|
|
|
|
// init {
|
|
|
|
// if(any_uri != null) {
|
|
|
|
// uri = if(any_uri.startsWith("/")) {
|
|
|
|
// Uri.fromFile(File(any_uri))
|
|
|
|
// } else {
|
|
|
|
// any_uri.toUri()
|
|
|
|
// }
|
|
|
|
// val ext = MimeTypeMap.getFileExtensionFromUrl(any_uri)
|
|
|
|
// if(ext != null) {
|
|
|
|
// mime_type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.toLowerCase(Locale.JAPAN))
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private fun getSecondaryStorageVolumesMap(context : Context) : Map<String, String> {
|
|
|
|
// val result = HashMap<String, String>()
|
|
|
|
// try {
|
|
|
|
// val sm = context.applicationContext.getSystemService(Context.STORAGE_SERVICE) as? StorageManager
|
|
|
|
// if(sm == null) {
|
|
|
|
// log.e("can't get StorageManager")
|
|
|
|
// } else {
|
|
|
|
//
|
|
|
|
// // SDカードスロットのある7.0端末が手元にないから検証できない
|
|
|
|
// // if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ){
|
|
|
|
// // for(StorageVolume volume : sm.getStorageVolumes() ){
|
|
|
|
// // // String path = volume.getPath();
|
|
|
|
// // String state = volume.getState();
|
|
|
|
// //
|
|
|
|
// // }
|
|
|
|
// // }
|
|
|
|
//
|
|
|
|
// val getVolumeList = sm.javaClass.getMethod("getVolumeList")
|
|
|
|
// val volumes = getVolumeList.invoke(sm)
|
|
|
|
// log.d("volumes type=%s", volumes.javaClass)
|
|
|
|
//
|
|
|
|
// if(volumes is ArrayList<*>) {
|
|
|
|
// //
|
|
|
|
// for(volume in volumes) {
|
|
|
|
// val volume_clazz = volume.javaClass
|
|
|
|
//
|
|
|
|
// val path = volume_clazz.getMethod("getPath").invoke(volume) as? String
|
|
|
|
// val state = volume_clazz.getMethod("getState").invoke(volume) as? String
|
|
|
|
// if(path != null && state == "mounted") {
|
|
|
|
// //
|
|
|
|
// val isPrimary = volume_clazz.getMethod("isPrimary").invoke(volume) as? Boolean
|
|
|
|
// if(isPrimary == true) result["primary"] = path
|
|
|
|
// //
|
|
|
|
// val uuid = volume_clazz.getMethod("getUuid").invoke(volume) as? String
|
|
|
|
// if(uuid != null) result[uuid] = path
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// } catch(ex : Throwable) {
|
|
|
|
// log.trace(ex)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return result
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private fun isExternalStorageDocument(uri : Uri) : Boolean {
|
|
|
|
// return "com.android.externalstorage.documents" == uri.authority
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private fun getDocumentId(documentUri : Uri) : String {
|
|
|
|
// val paths = documentUri.pathSegments
|
|
|
|
// if(paths.size >= 2 && PATH_DOCUMENT == paths[0]) {
|
|
|
|
// // document
|
|
|
|
// return paths[1]
|
|
|
|
// }
|
|
|
|
// if(paths.size >= 4 && PATH_TREE == paths[0]
|
|
|
|
// && PATH_DOCUMENT == paths[2]) {
|
|
|
|
// // document in tree
|
|
|
|
// return paths[3]
|
|
|
|
// }
|
|
|
|
// if(paths.size >= 2 && PATH_TREE == paths[0]) {
|
|
|
|
// // tree
|
|
|
|
// return paths[1]
|
|
|
|
// }
|
|
|
|
// throw IllegalArgumentException("Invalid URI: $documentUri")
|
|
|
|
// }
|
|
|
|
|
2019-09-12 15:43:11 +02:00
|
|
|
// fun getFile(context : Context, path : String) : File? {
|
|
|
|
// try {
|
|
|
|
// if(path.startsWith("/")) return File(path)
|
|
|
|
// val uri = path.toUri()
|
|
|
|
// if("file" == uri.scheme) return File(uri.path!!)
|
|
|
|
//
|
|
|
|
// // if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT )
|
|
|
|
// run {
|
|
|
|
// if(isExternalStorageDocument(uri)) {
|
|
|
|
// try {
|
|
|
|
// val docId = getDocumentId(uri)
|
|
|
|
// val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
|
|
|
// if(split.size >= 2) {
|
|
|
|
// val uuid = split[0]
|
|
|
|
// if("primary".equals(uuid, ignoreCase = true)) {
|
|
|
|
// return File(Environment.getExternalStorageDirectory().toString() + "/" + split[1])
|
|
|
|
// } else {
|
|
|
|
// val volume_map =
|
|
|
|
// getSecondaryStorageVolumesMap(
|
|
|
|
// context
|
|
|
|
// )
|
|
|
|
// val volume_path = volume_map[uuid]
|
|
|
|
// if(volume_path != null) {
|
|
|
|
// return File(volume_path + "/" + split[1])
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// } catch(ex : Throwable) {
|
|
|
|
// log.trace(ex)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// // MediaStore Uri
|
|
|
|
// context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
|
|
|
// if(cursor.moveToFirst()) {
|
|
|
|
// val col_count = cursor.columnCount
|
|
|
|
// for(i in 0 until col_count) {
|
|
|
|
// val type = cursor.getType(i)
|
|
|
|
// if(type != Cursor.FIELD_TYPE_STRING) continue
|
|
|
|
// val name = cursor.getColumnName(i)
|
|
|
|
// val value = cursor.getStringOrNull(i)
|
|
|
|
// if(value != null && value.isNotEmpty() && "filePath" == name) return File(value)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// } catch(ex : Throwable) {
|
|
|
|
// log.trace(ex)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return null
|
|
|
|
// }
|
2019-10-06 13:23:33 +02:00
|
|
|
//
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//}
|
|
|
|
|
|
|
|
private const val MIME_TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream"
|
|
|
|
|
2021-06-13 13:48:48 +02:00
|
|
|
private val mimeTypeExMap: HashMap<String, String> by lazy {
|
|
|
|
val map = HashMap<String, String>()
|
|
|
|
map["BDM"] = "application/vnd.syncml.dm+wbxml"
|
|
|
|
map["DAT"] = ""
|
|
|
|
map["TID"] = ""
|
|
|
|
map["js"] = "text/javascript"
|
|
|
|
map["sh"] = "application/x-sh"
|
|
|
|
map["lua"] = "text/x-lua"
|
|
|
|
map
|
2018-12-01 00:02:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Suppress("unused")
|
2021-06-13 13:48:48 +02:00
|
|
|
fun getMimeType(log: LogCategory?, src: String): String {
|
2021-06-20 05:02:30 +02:00
|
|
|
MimeTypeMap.getFileExtensionFromUrl(src)
|
|
|
|
?.notEmpty()?.lowercase()?.let { ext ->
|
|
|
|
MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)
|
|
|
|
?.notEmpty()?.let { return it }
|
2021-06-13 13:48:48 +02:00
|
|
|
|
2021-06-20 05:02:30 +02:00
|
|
|
val mimeType = mimeTypeExMap[ext]
|
|
|
|
mimeType?.notEmpty()?.let { return it }
|
2021-06-13 13:48:48 +02:00
|
|
|
|
2021-06-20 05:02:30 +02:00
|
|
|
// 戻り値が空文字列の場合とnullの場合があり、空文字列の場合は既知なのでログ出力しない
|
|
|
|
if (mimeType == null && log != null) {
|
2021-06-20 15:12:25 +02:00
|
|
|
log.w("getMimeType(): unknown file extension '$ext'")
|
2021-06-20 05:02:30 +02:00
|
|
|
}
|
2021-06-13 13:48:48 +02:00
|
|
|
}
|
2021-06-20 05:02:30 +02:00
|
|
|
|
2021-06-13 13:48:48 +02:00
|
|
|
return MIME_TYPE_APPLICATION_OCTET_STREAM
|
2018-12-01 00:02:18 +01:00
|
|
|
}
|
|
|
|
|
2021-06-13 13:48:48 +02:00
|
|
|
fun getDocumentName(contentResolver: ContentResolver, uri: Uri): String {
|
|
|
|
val errorName = "no_name"
|
|
|
|
return contentResolver.query(uri, null, null, null, null, null)
|
|
|
|
?.use { cursor ->
|
|
|
|
return if (!cursor.moveToFirst()) {
|
|
|
|
errorName
|
|
|
|
} else {
|
|
|
|
cursor.getStringOrNull(OpenableColumns.DISPLAY_NAME) ?: errorName
|
|
|
|
}
|
|
|
|
}
|
|
|
|
?: errorName
|
2018-12-01 00:02:18 +01:00
|
|
|
}
|
|
|
|
|
2021-06-13 13:48:48 +02:00
|
|
|
fun getStreamSize(bClose: Boolean, inStream: InputStream): Long {
|
|
|
|
try {
|
|
|
|
var size = 0L
|
2022-03-13 13:47:24 +01:00
|
|
|
val tmpBuffer = ByteArray(0x10000)
|
2021-06-13 13:48:48 +02:00
|
|
|
while (true) {
|
2022-03-13 13:49:21 +01:00
|
|
|
val delta = inStream.read(tmpBuffer, 0, tmpBuffer.size)
|
|
|
|
if (delta <= 0) break
|
2022-03-13 13:47:24 +01:00
|
|
|
size += delta.toLong()
|
2021-06-13 13:48:48 +02:00
|
|
|
}
|
|
|
|
return size
|
|
|
|
} finally {
|
2022-01-06 00:21:47 +01:00
|
|
|
if (bClose) inStream.closeQuietly()
|
2021-06-13 13:48:48 +02:00
|
|
|
}
|
2018-12-01 00:02:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//fun File.loadByteArray() : ByteArray {
|
|
|
|
// val size = this.length().toInt()
|
|
|
|
// val data = ByteArray(size)
|
|
|
|
// FileInputStream(this).use { inStream ->
|
|
|
|
// val nRead = 0
|
|
|
|
// while(nRead < size) {
|
|
|
|
// val delta = inStream.read(data, nRead, size - nRead)
|
|
|
|
// if(delta <= 0) break
|
|
|
|
// }
|
|
|
|
// return data
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
fun Context.loadRawResource(@RawRes resId: Int): ByteArray {
|
2022-03-13 13:05:54 +01:00
|
|
|
return resources.openRawResource(resId).use { it.readBytes() }
|
2018-12-01 00:02:18 +01:00
|
|
|
}
|
|
|
|
|
2021-06-13 13:48:48 +02:00
|
|
|
fun intentOpenDocument(mimeType: String): Intent {
|
|
|
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
|
|
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
|
|
intent.type = mimeType // "image/*"
|
|
|
|
return intent
|
2018-12-01 00:02:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fun intentGetContent(
|
2021-06-13 13:48:48 +02:00
|
|
|
allowMultiple: Boolean,
|
|
|
|
caption: String,
|
2021-06-20 15:46:07 +02:00
|
|
|
mimeTypes: Array<out String>,
|
2021-06-13 13:48:48 +02:00
|
|
|
): Intent {
|
|
|
|
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
|
|
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
|
|
|
|
|
|
if (allowMultiple) {
|
|
|
|
// EXTRA_ALLOW_MULTIPLE は API 18 (4.3)以降。ACTION_GET_CONTENT でも ACTION_OPEN_DOCUMENT でも指定できる
|
|
|
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// EXTRA_MIME_TYPES は API 19以降。ACTION_GET_CONTENT でも ACTION_OPEN_DOCUMENT でも指定できる
|
|
|
|
intent.putExtra("android.intent.extra.MIME_TYPES", mimeTypes)
|
|
|
|
|
|
|
|
intent.type = when {
|
|
|
|
mimeTypes.size == 1 -> mimeTypes[0]
|
|
|
|
|
|
|
|
// On Android 6.0 and above using "video/* image/" or "image/ video/*" type doesn't work
|
|
|
|
// it only recognizes the first filter you specify.
|
|
|
|
Build.VERSION.SDK_INT >= 23 -> "*/*"
|
|
|
|
|
|
|
|
else -> mimeTypes.joinToString(" ")
|
|
|
|
}
|
|
|
|
|
|
|
|
return Intent.createChooser(intent, caption)
|
2018-12-01 00:02:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
data class GetContentResultEntry(
|
2021-06-13 13:48:48 +02:00
|
|
|
val uri: Uri,
|
|
|
|
val mimeType: String? = null,
|
2021-06-20 15:46:07 +02:00
|
|
|
var time: Long? = null,
|
2018-12-01 00:02:18 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// returns list of pair of uri and mime-type.
|
2021-06-13 13:48:48 +02:00
|
|
|
fun Intent.handleGetContentResult(contentResolver: ContentResolver): ArrayList<GetContentResultEntry> {
|
|
|
|
val urlList = ArrayList<GetContentResultEntry>()
|
|
|
|
// 単一選択
|
|
|
|
this.data?.let {
|
|
|
|
urlList.add(GetContentResultEntry(it, this.type))
|
|
|
|
}
|
|
|
|
// 複数選択
|
2021-06-20 15:46:07 +02:00
|
|
|
this.clipData?.let { clipData ->
|
|
|
|
(0 until clipData.itemCount).mapNotNull { clipData.getItemAt(it)?.uri }.forEach { uri ->
|
2022-07-20 06:27:19 +02:00
|
|
|
if (urlList.none { it.uri == uri }) {
|
2021-06-20 15:46:07 +02:00
|
|
|
urlList.add(GetContentResultEntry(uri))
|
2021-06-13 13:48:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
urlList.forEach {
|
|
|
|
try {
|
|
|
|
contentResolver.takePersistableUriPermission(
|
|
|
|
it.uri,
|
|
|
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
|
|
)
|
|
|
|
} catch (_: Throwable) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return urlList
|
2018-12-01 00:02:18 +01:00
|
|
|
}
|