package jp.juggler.subwaytooter import android.content.Intent import android.net.Uri import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.content.FileProvider import jp.juggler.util.LogCategory import jp.juggler.util.digestSHA256Hex import jp.juggler.util.showToast import okhttp3.internal.toHexString import org.apache.commons.io.IOUtils import java.io.File import java.io.FileOutputStream import java.util.* import java.util.concurrent.atomic.AtomicReference class ActCallback : AppCompatActivity() { companion object { private val log = LogCategory("ActCallback") internal val last_uri = AtomicReference(null) internal val sent_intent = AtomicReference(null) private fun String?.isMediaMimeType() = when { this == null -> false this.startsWith("image/") -> true this.startsWith("video/") -> true this.startsWith("audio/") -> true else -> false } @Volatile private var uriFromApp: Uri? = null fun setUriFromApp(uri: Uri?) { synchronized(this) { uriFromApp = uri } } fun containsUriFromApp(uri: Uri?) = synchronized(this) { uri == uriFromApp } } override fun onCreate(savedInstanceState: Bundle?) { log.d("onCreate flags=0x${intent.flags.toHexString()}") super.onCreate(savedInstanceState) var intent: Intent? = intent when { intent == null -> { // 多分起きないと思う } (intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0 -> { // 履歴から開いた場合はIntentの中味を読まない } else -> { val action = intent.action val type = intent.type // ACTION_SEND か ACTION_SEND_MULTIPLE // ACTION_VIEW かつ type が 画像かビデオか音声 if ( Intent.ACTION_SEND == action || Intent.ACTION_SEND_MULTIPLE == action || (Intent.ACTION_VIEW == action && type.isMediaMimeType()) ) { // Google Photo などから送られるIntentに含まれるuriの有効期間はActivityが閉じられるまで // http://qiita.com/pside/items/a821e2fe9ae6b7c1a98c // 有効期間を延長する intent = remake(intent) if (intent != null) { sent_intent.set(intent) } } else if (forbidUriFromApp(intent)) { // last_uriをクリアする last_uri.set(null) // ダイアログを閉じるまで画面遷移しない return } else { val uri = intent.data if (uri != null) { last_uri.set(uri) } } } } // どうであれメイン画面に戻る afterDispatch() } private fun afterDispatch() { val intent = Intent(this, ActMain::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY or Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(intent) finish() } private fun copyExtraTexts(dst: Intent, src: Intent) { var sv: String? // sv = src.getStringExtra(Intent.EXTRA_TEXT) if (sv != null) dst.putExtra(Intent.EXTRA_TEXT, sv) // sv = src.getStringExtra(Intent.EXTRA_SUBJECT) if (sv != null) dst.putExtra(Intent.EXTRA_SUBJECT, sv) } private fun remake(src: Intent): Intent? { sweepOldCache() try { val action = src.action val type = src.type if (type.isMediaMimeType()) { if (Intent.ACTION_VIEW == action) { src.data?.let { uriOriginal -> try { val uri = saveToCache(uriOriginal) val dst = Intent(action) dst.setDataAndType(uri, type) copyExtraTexts(dst, src) return dst } catch (ex: Throwable) { log.trace(ex) } } } else if (Intent.ACTION_SEND == action) { var uri: Uri? = src.getParcelableExtra(Intent.EXTRA_STREAM) if (uri == null) { // text/plain return src } else { try { uri = saveToCache(uri) val dst = Intent(action) dst.type = type dst.putExtra(Intent.EXTRA_STREAM, uri) copyExtraTexts(dst, src) return dst } catch (ex: Throwable) { log.trace(ex) } } } else if (Intent.ACTION_SEND_MULTIPLE == action) { val listUri = src.getParcelableArrayListExtra(Intent.EXTRA_STREAM) ?: return null val listDst = ArrayList() for (uriOriginal in listUri) { if (uriOriginal != null) { try { val uri = saveToCache(uriOriginal) listDst.add(uri) } catch (ex: Throwable) { log.trace(ex) } } } if (listDst.isEmpty()) return null val dst = Intent(action) dst.type = type dst.putParcelableArrayListExtra(Intent.EXTRA_STREAM, listDst) copyExtraTexts(dst, src) return dst } } else if (Intent.ACTION_SEND == action) { // Swarmアプリから送られたインテントは getType()==null だが EXTRA_TEXT は含まれている // EXTRA_TEXT の存在を確認してからtypeがnullもしくは text/plain なら受け取る val sv = src.getStringExtra(Intent.EXTRA_TEXT) if (sv?.isNotEmpty() == true && (type == null || type.startsWith("text/"))) { val dst = Intent(action) dst.type = "text/plain" copyExtraTexts(dst, src) return dst } } } catch (ex: Throwable) { log.trace(ex) } return null } @Throws(Throwable::class) private fun saveToCache(uri: Uri): Uri { // prepare cache directory val cacheDir = this.cacheDir cacheDir.mkdirs() val name = "img." + System.currentTimeMillis().toString() + "." + uri.toString().digestSHA256Hex() val dst = File(cacheDir, name) FileOutputStream(dst).use { outStream -> val source = contentResolver.openInputStream(uri) ?: error("getContentResolver.openInputStream returns null.") source.use { inStream -> IOUtils.copy(inStream, outStream) } } return FileProvider.getUriForFile(this, App1.FILE_PROVIDER_AUTHORITY, dst) } private fun sweepOldCache() { // sweep old cache try { // prepare cache directory val cacheDir = this.cacheDir cacheDir.mkdirs() val now = System.currentTimeMillis() val files = cacheDir.listFiles() if (files != null) for (f in files) { try { if (f.isFile && f.name.startsWith("img.") && now - f.lastModified() >= 86400000L ) { f.delete() } } catch (ex: Throwable) { log.trace(ex) } } } catch (ex: Throwable) { log.trace(ex) } } // return true if open app chooser dialog private fun forbidUriFromApp(src: Intent): Boolean { if (src.action != Intent.ACTION_VIEW) return false val uri = src.data ?: return false if (!containsUriFromApp(uri)) return false setUriFromApp(null) try { startActivity(Intent.createChooser(src, uri.toString())) finish() } catch (ex: Throwable) { showToast(ex, "can't open chooser for $uri") } return true } }