package jp.juggler.util import android.content.ContentResolver import android.content.Context import android.content.Intent import android.database.Cursor import android.net.Uri import android.os.Build import android.os.Environment import android.os.storage.StorageManager import android.provider.OpenableColumns import android.webkit.MimeTypeMap import org.apache.commons.io.IOUtils import java.io.ByteArrayOutputStream import java.io.File import java.io.InputStream import java.util.* 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()) } } } } private fun getSecondaryStorageVolumesMap(context : Context) : Map { val result = HashMap() 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") } // 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 // } internal val mimeTypeExMap : HashMap by lazy { val map = HashMap() 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 } const val MIME_TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream" } @Suppress("unused") fun getMimeType(log : LogCategory?, src : String) : String { var ext = MimeTypeMap.getFileExtensionFromUrl(src) if(ext != null && ext.isNotEmpty()) { ext = ext.toLowerCase(Locale.US) // var mime_type : String? = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) if(mime_type?.isNotEmpty() == true) return mime_type // mime_type = StorageUtils.mimeTypeExMap[ext] if(mime_type?.isNotEmpty() == true) return mime_type // 戻り値が空文字列の場合とnullの場合があり、空文字列の場合は既知なのでログ出力しない if(mime_type == null && log != null) { log.w("getMimeType(): unknown file extension '%s'", ext) } } return StorageUtils.MIME_TYPE_APPLICATION_OCTET_STREAM } 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 } fun getStreamSize(bClose : Boolean, inStream : InputStream) : Long { try { var size = 0L while(true) { val r = IOUtils.skip(inStream, 16384) if(r <= 0) break size += r } return size } finally { @Suppress("DEPRECATION") if(bClose) IOUtils.closeQuietly(inStream) } } //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 // } //} fun Context.loadRawResource(resId : Int) : ByteArray { resources.openRawResource(resId).use { inStream -> val bao = ByteArrayOutputStream(inStream.available()) IOUtils.copy(inStream, bao) return bao.toByteArray() } } fun intentOpenDocument(mimeType : String) : Intent { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) intent.addCategory(Intent.CATEGORY_OPENABLE) intent.type = mimeType // "image/*" return intent } fun intentGetContent( allowMultiple : Boolean, caption : String, mimeTypes : Array ) : 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) } data class GetContentResultEntry( val uri : Uri, val mimeType : String? = null, var time : Long? = null ) // returns list of pair of uri and mime-type. fun Intent.handleGetContentResult(contentResolver : ContentResolver) : ArrayList { val urlList = ArrayList() // 単一選択 this.data?.let { urlList.add(GetContentResultEntry(it, this.type)) } // 複数選択 val cd = this.clipData if(cd != null) { for(i in 0 until cd.itemCount) { cd.getItemAt(i)?.uri?.let { uri -> if(null == urlList.find { it.uri == uri }) { urlList.add(GetContentResultEntry(uri)) } } } } urlList.forEach { try { contentResolver.takePersistableUriPermission( it.uri, Intent.FLAG_GRANT_READ_URI_PERMISSION ) } catch(_ : Throwable) { } } return urlList }