2018-01-04 19:52:25 +01:00
|
|
|
package jp.juggler.subwaytooter
|
|
|
|
|
|
|
|
import android.Manifest
|
|
|
|
import android.annotation.SuppressLint
|
|
|
|
import android.app.DownloadManager
|
|
|
|
import android.content.ClipData
|
|
|
|
import android.content.ClipDescription
|
|
|
|
import android.content.ClipboardManager
|
|
|
|
import android.content.Intent
|
|
|
|
import android.content.pm.PackageManager
|
2018-11-22 17:10:34 +01:00
|
|
|
import android.graphics.*
|
2019-10-06 22:46:56 +02:00
|
|
|
import android.os.Build
|
|
|
|
import android.os.Bundle
|
|
|
|
import android.os.Environment
|
|
|
|
import android.os.SystemClock
|
2018-01-04 19:52:25 +01:00
|
|
|
import android.view.View
|
|
|
|
import android.view.Window
|
2019-11-16 02:58:09 +01:00
|
|
|
import android.widget.CheckBox
|
2018-01-04 19:52:25 +01:00
|
|
|
import android.widget.TextView
|
2019-10-06 22:46:56 +02:00
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
|
|
import androidx.core.app.ActivityCompat
|
|
|
|
import androidx.core.content.ContextCompat
|
2018-04-05 22:47:25 +02:00
|
|
|
import com.google.android.exoplayer2.*
|
2020-10-24 05:14:53 +02:00
|
|
|
import com.google.android.exoplayer2.Player.TimelineChangeReason
|
2018-01-04 19:52:25 +01:00
|
|
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
|
2020-10-24 05:14:53 +02:00
|
|
|
import com.google.android.exoplayer2.source.*
|
2018-04-05 22:47:25 +02:00
|
|
|
import com.google.android.exoplayer2.ui.PlayerView
|
2018-01-04 19:52:25 +01:00
|
|
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
|
|
|
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
|
|
|
import com.google.android.exoplayer2.util.Util
|
|
|
|
import jp.juggler.subwaytooter.api.TootApiClient
|
|
|
|
import jp.juggler.subwaytooter.api.TootApiResult
|
|
|
|
import jp.juggler.subwaytooter.api.TootTask
|
|
|
|
import jp.juggler.subwaytooter.api.TootTaskRunner
|
2018-01-10 16:47:35 +01:00
|
|
|
import jp.juggler.subwaytooter.api.entity.*
|
2018-01-04 19:52:25 +01:00
|
|
|
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
2018-12-01 00:02:18 +01:00
|
|
|
import jp.juggler.subwaytooter.util.ProgressResponseBody
|
2018-01-04 19:52:25 +01:00
|
|
|
import jp.juggler.subwaytooter.view.PinchBitmapView
|
2018-12-01 00:02:18 +01:00
|
|
|
import jp.juggler.util.*
|
2018-08-31 14:47:40 +02:00
|
|
|
import okhttp3.Request
|
2018-11-22 17:10:34 +01:00
|
|
|
import java.io.ByteArrayInputStream
|
2018-04-05 22:47:25 +02:00
|
|
|
import java.io.IOException
|
2018-10-01 11:45:51 +02:00
|
|
|
import java.util.*
|
2018-12-06 22:38:49 +01:00
|
|
|
import javax.net.ssl.HttpsURLConnection
|
2019-06-20 16:48:07 +02:00
|
|
|
import kotlin.math.max
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
2020-10-24 05:14:53 +02:00
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
|
|
|
internal val log = LogCategory("ActMediaViewer")
|
|
|
|
|
|
|
|
internal val download_history_list = LinkedList<DownloadHistory>()
|
|
|
|
internal const val DOWNLOAD_REPEAT_EXPIRE = 3000L
|
|
|
|
internal const val short_limit = 5000L
|
|
|
|
|
|
|
|
private const val PERMISSION_REQUEST_CODE = 1
|
|
|
|
|
|
|
|
internal const val EXTRA_IDX = "idx"
|
|
|
|
internal const val EXTRA_DATA = "data"
|
|
|
|
internal const val EXTRA_SERVICE_TYPE = "serviceType"
|
|
|
|
|
|
|
|
internal const val STATE_PLAYER_POS = "playerPos"
|
|
|
|
internal const val STATE_PLAYER_PLAY_WHEN_READY = "playerPlayWhenReady"
|
|
|
|
internal const val STATE_LAST_VOLUME = "lastVolume"
|
|
|
|
|
|
|
|
internal fun <T : TootAttachmentLike> encodeMediaList(list: ArrayList<T>?) =
|
|
|
|
list?.encodeJson()?.toString() ?: "[]"
|
|
|
|
|
|
|
|
internal fun decodeMediaList(src: String?) =
|
|
|
|
ArrayList<TootAttachment>().apply {
|
|
|
|
src?.decodeJsonArray()?.forEach {
|
|
|
|
if (it !is JsonObject) return@forEach
|
|
|
|
add(TootAttachment.decodeJson(it))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun open(
|
|
|
|
activity: ActMain,
|
|
|
|
serviceType: ServiceType,
|
|
|
|
list: ArrayList<TootAttachmentLike>,
|
|
|
|
idx: Int
|
2018-04-05 22:47:25 +02:00
|
|
|
) {
|
2020-10-24 05:14:53 +02:00
|
|
|
val intent = Intent(activity, ActMediaViewer::class.java)
|
|
|
|
intent.putExtra(EXTRA_IDX, idx)
|
|
|
|
intent.putExtra(EXTRA_SERVICE_TYPE, serviceType.ordinal)
|
|
|
|
intent.putExtra(EXTRA_DATA, encodeMediaList(list))
|
|
|
|
activity.startActivity(intent)
|
|
|
|
activity.overridePendingTransition(R.anim.slide_from_bottom, android.R.anim.fade_out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal var idx: Int = 0
|
|
|
|
private lateinit var media_list: ArrayList<TootAttachment>
|
|
|
|
private lateinit var serviceType: ServiceType
|
|
|
|
|
|
|
|
private lateinit var pbvImage: PinchBitmapView
|
|
|
|
private lateinit var btnPrevious: View
|
|
|
|
private lateinit var btnNext: View
|
|
|
|
private lateinit var tvError: TextView
|
|
|
|
private lateinit var exoPlayer: SimpleExoPlayer
|
|
|
|
private lateinit var exoView: PlayerView
|
|
|
|
private lateinit var svDescription: View
|
|
|
|
private lateinit var tvDescription: TextView
|
|
|
|
private lateinit var tvStatus: TextView
|
|
|
|
private lateinit var cbMute: CheckBox
|
|
|
|
private var lastVolume = Float.NaN
|
|
|
|
|
|
|
|
internal var buffering_last_shown: Long = 0
|
|
|
|
|
|
|
|
private val player_listener = object : Player.EventListener {
|
|
|
|
|
|
|
|
override fun onTimelineChanged(
|
|
|
|
timeline: Timeline,
|
|
|
|
@TimelineChangeReason reason: Int
|
2018-01-21 13:46:36 +01:00
|
|
|
) {
|
2020-10-24 05:14:53 +02:00
|
|
|
log.d("exoPlayer onTimelineChanged reason=$reason")
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSeekProcessed() {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onLoadingChanged(isLoading: Boolean) {
|
|
|
|
// かなり頻繁に呼ばれる
|
|
|
|
// warning.d( "exoPlayer onLoadingChanged %s" ,isLoading );
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
|
|
|
|
// かなり頻繁に呼ばれる
|
|
|
|
// warning.d( "exoPlayer onPlayerStateChanged %s %s", playWhenReady, playbackState );
|
|
|
|
if (playWhenReady && playbackState == Player.STATE_BUFFERING) {
|
|
|
|
val now = SystemClock.elapsedRealtime()
|
|
|
|
if (now - buffering_last_shown >= short_limit && exoPlayer.duration >= short_limit) {
|
|
|
|
buffering_last_shown = now
|
|
|
|
showToast(false, R.string.video_buffering)
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
exoPlayer.getDuration() may returns negative value (TIME_UNSET ,same as Long.MIN_VALUE + 1).
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onRepeatModeChanged(repeatMode: Int) {
|
|
|
|
log.d("exoPlayer onRepeatModeChanged %d", repeatMode)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPlayerError(error: ExoPlaybackException) {
|
|
|
|
log.d("exoPlayer onPlayerError")
|
|
|
|
showToast(error, "player error.")
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPositionDiscontinuity(reason: Int) {
|
|
|
|
log.d("exoPlayer onPositionDiscontinuity reason=$reason")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSaveInstanceState(outState: Bundle) {
|
|
|
|
super.onSaveInstanceState(outState)
|
|
|
|
|
|
|
|
log.d("onSaveInstanceState")
|
|
|
|
|
|
|
|
outState.putInt(EXTRA_IDX, idx)
|
|
|
|
outState.putInt(EXTRA_SERVICE_TYPE, serviceType.ordinal)
|
|
|
|
outState.putString(EXTRA_DATA, encodeMediaList(media_list))
|
|
|
|
|
|
|
|
outState.putLong(STATE_PLAYER_POS, exoPlayer.currentPosition)
|
|
|
|
outState.putBoolean(STATE_PLAYER_PLAY_WHEN_READY, exoPlayer.playWhenReady)
|
|
|
|
outState.putFloat(STATE_LAST_VOLUME, lastVolume)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
|
|
|
App1.setActivityTheme(this, noActionBar = true, forceDark = true)
|
|
|
|
|
|
|
|
val intent = intent
|
|
|
|
|
|
|
|
this.idx = savedInstanceState?.getInt(EXTRA_IDX) ?: intent.getIntExtra(EXTRA_IDX, idx)
|
|
|
|
|
|
|
|
this.serviceType = ServiceType.values()[
|
|
|
|
savedInstanceState?.getInt(EXTRA_SERVICE_TYPE)
|
|
|
|
?: intent.getIntExtra(EXTRA_SERVICE_TYPE, 0)
|
2018-08-18 12:58:14 +02:00
|
|
|
]
|
2020-10-24 05:14:53 +02:00
|
|
|
|
|
|
|
this.media_list = decodeMediaList(
|
|
|
|
savedInstanceState?.getString(EXTRA_DATA)
|
|
|
|
?: intent.getStringExtra(EXTRA_DATA)
|
2018-01-04 19:52:25 +01:00
|
|
|
)
|
2020-10-24 05:14:53 +02:00
|
|
|
|
|
|
|
if (idx < 0 || idx >= media_list.size) idx = 0
|
|
|
|
|
|
|
|
initUI()
|
|
|
|
|
|
|
|
load(savedInstanceState)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDestroy() {
|
|
|
|
super.onDestroy()
|
|
|
|
pbvImage.setBitmap(null)
|
|
|
|
exoPlayer.release()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun finish() {
|
|
|
|
super.finish()
|
|
|
|
overridePendingTransition(R.anim.fade_in, R.anim.slide_to_bottom)
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun initUI() {
|
|
|
|
setContentView(R.layout.act_media_viewer)
|
|
|
|
App1.initEdgeToEdge(this)
|
|
|
|
|
|
|
|
pbvImage = findViewById(R.id.pbvImage)
|
|
|
|
btnPrevious = findViewById(R.id.btnPrevious)
|
|
|
|
btnNext = findViewById(R.id.btnNext)
|
|
|
|
exoView = findViewById(R.id.exoView)
|
|
|
|
tvError = findViewById(R.id.tvError)
|
|
|
|
svDescription = findViewById(R.id.svDescription)
|
|
|
|
tvDescription = findViewById(R.id.tvDescription)
|
|
|
|
tvStatus = findViewById(R.id.tvStatus)
|
|
|
|
cbMute = findViewById(R.id.cbMute)
|
|
|
|
|
|
|
|
val enablePaging = media_list.size > 1
|
|
|
|
btnPrevious.isEnabled = enablePaging
|
|
|
|
btnNext.isEnabled = enablePaging
|
|
|
|
btnPrevious.alpha = if (enablePaging) 1f else 0.3f
|
|
|
|
btnNext.alpha = if (enablePaging) 1f else 0.3f
|
|
|
|
|
|
|
|
btnPrevious.setOnClickListener(this)
|
|
|
|
btnNext.setOnClickListener(this)
|
|
|
|
findViewById<View>(R.id.btnDownload).setOnClickListener(this)
|
|
|
|
findViewById<View>(R.id.btnMore).setOnClickListener(this)
|
|
|
|
|
|
|
|
cbMute.setOnCheckedChangeListener { _, isChecked ->
|
|
|
|
if (isChecked) {
|
|
|
|
// mute
|
|
|
|
lastVolume = exoPlayer.volume
|
|
|
|
exoPlayer.volume = 0f
|
|
|
|
} else {
|
|
|
|
// unmute
|
|
|
|
exoPlayer.volume = when {
|
|
|
|
lastVolume.isNaN() -> 1f
|
|
|
|
lastVolume <= 0f -> 1f
|
|
|
|
else -> lastVolume
|
|
|
|
}
|
|
|
|
lastVolume = Float.NaN
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pbvImage.setCallback(object : PinchBitmapView.Callback {
|
|
|
|
override fun onSwipe(deltaX: Int, deltaY: Int) {
|
|
|
|
if (isDestroyed) return
|
|
|
|
if (deltaX != 0) {
|
2018-12-03 21:00:03 +01:00
|
|
|
loadDelta(deltaX)
|
2019-06-20 16:48:07 +02:00
|
|
|
} else {
|
2018-12-03 21:00:03 +01:00
|
|
|
log.d("finish by vertical swipe")
|
|
|
|
finish()
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2018-01-21 13:46:36 +01:00
|
|
|
override fun onMove(
|
2020-10-24 05:14:53 +02:00
|
|
|
bitmap_w: Float,
|
|
|
|
bitmap_h: Float,
|
|
|
|
tx: Float,
|
|
|
|
ty: Float,
|
|
|
|
scale: Float
|
2018-01-21 13:46:36 +01:00
|
|
|
) {
|
2018-01-04 19:52:25 +01:00
|
|
|
App1.getAppState(this@ActMediaViewer).handler.post(Runnable {
|
2020-10-24 05:14:53 +02:00
|
|
|
if (isDestroyed) return@Runnable
|
|
|
|
if (tvStatus.visibility == View.VISIBLE) {
|
2018-01-21 13:46:36 +01:00
|
|
|
tvStatus.text = getString(
|
2020-10-24 05:14:53 +02:00
|
|
|
R.string.zooming_of,
|
|
|
|
bitmap_w.toInt(),
|
|
|
|
bitmap_h.toInt(),
|
|
|
|
scale
|
2018-01-21 13:46:36 +01:00
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
2020-10-24 05:14:53 +02:00
|
|
|
|
|
|
|
exoPlayer = SimpleExoPlayer.Builder(this).build()
|
|
|
|
exoPlayer.addListener(player_listener)
|
|
|
|
exoView.player = exoPlayer
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun loadDelta(delta: Int) {
|
|
|
|
if (media_list.size < 2) return
|
|
|
|
val size = media_list.size
|
|
|
|
idx = (idx + size + delta) % size
|
|
|
|
load()
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun load(state: Bundle? = null) {
|
|
|
|
|
|
|
|
exoPlayer.stop()
|
|
|
|
pbvImage.visibility = View.GONE
|
|
|
|
exoView.visibility = View.GONE
|
|
|
|
tvError.visibility = View.GONE
|
|
|
|
svDescription.visibility = View.GONE
|
|
|
|
tvStatus.visibility = View.GONE
|
|
|
|
|
|
|
|
if (idx < 0 || idx >= media_list.size) {
|
|
|
|
showError(getString(R.string.media_attachment_empty))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
val ta = media_list[idx]
|
|
|
|
val description = ta.description
|
|
|
|
if (description?.isNotEmpty() == true) {
|
|
|
|
svDescription.visibility = View.VISIBLE
|
|
|
|
tvDescription.text = description
|
|
|
|
}
|
|
|
|
|
|
|
|
when (ta.type) {
|
|
|
|
|
2019-06-23 16:58:53 +02:00
|
|
|
TootAttachmentType.Unknown ->
|
|
|
|
showError(getString(R.string.media_attachment_type_error, ta.type.id))
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2019-06-23 16:58:53 +02:00
|
|
|
TootAttachmentType.Image ->
|
|
|
|
loadBitmap(ta)
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2019-06-23 16:58:53 +02:00
|
|
|
TootAttachmentType.Video,
|
|
|
|
TootAttachmentType.GIFV,
|
|
|
|
TootAttachmentType.Audio ->
|
|
|
|
loadVideo(ta, state)
|
2020-10-24 05:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun showError(message: String) {
|
|
|
|
exoView.visibility = View.GONE
|
|
|
|
pbvImage.visibility = View.GONE
|
|
|
|
tvError.visibility = View.VISIBLE
|
|
|
|
tvError.text = message
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressLint("StaticFieldLeak")
|
|
|
|
private fun loadVideo(ta: TootAttachment, state: Bundle? = null) {
|
|
|
|
|
|
|
|
cbMute.vg(true)
|
|
|
|
if (cbMute.isChecked && lastVolume.isFinite()) {
|
|
|
|
exoPlayer.volume = 0f
|
|
|
|
}
|
|
|
|
|
|
|
|
val url = ta.getLargeUrl(App1.pref)
|
|
|
|
if (url == null) {
|
|
|
|
showError("missing media attachment url.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://github.com/google/ExoPlayer/issues/1819
|
|
|
|
HttpsURLConnection.setDefaultSSLSocketFactory(MySslSocketFactory)
|
|
|
|
|
|
|
|
exoView.visibility = View.VISIBLE
|
|
|
|
|
|
|
|
val defaultBandwidthMeter = DefaultBandwidthMeter.Builder(this).build()
|
|
|
|
val extractorsFactory = DefaultExtractorsFactory()
|
|
|
|
|
|
|
|
val dataSourceFactory = DefaultDataSourceFactory(
|
|
|
|
this, Util.getUserAgent(this, getString(R.string.app_name)), defaultBandwidthMeter
|
2018-01-04 19:52:25 +01:00
|
|
|
)
|
2020-10-24 05:14:53 +02:00
|
|
|
|
|
|
|
val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)
|
|
|
|
.createMediaSource(MediaItem.Builder().setUri(url.toUri()).build())
|
|
|
|
|
|
|
|
mediaSource.addEventListener(App1.getAppState(this).handler, mediaSourceEventListener)
|
|
|
|
|
|
|
|
exoPlayer.setMediaSource(mediaSource)
|
|
|
|
exoPlayer.prepare()
|
|
|
|
exoPlayer.repeatMode = when (ta.type) {
|
2019-06-23 16:58:53 +02:00
|
|
|
TootAttachmentType.Video -> Player.REPEAT_MODE_OFF
|
2020-10-24 05:14:53 +02:00
|
|
|
// GIFV or AUDIO
|
|
|
|
else -> Player.REPEAT_MODE_ALL
|
|
|
|
}
|
|
|
|
if (state == null) {
|
|
|
|
exoPlayer.playWhenReady = true
|
|
|
|
} else {
|
|
|
|
exoPlayer.playWhenReady = state.getBoolean(STATE_PLAYER_PLAY_WHEN_READY, true)
|
|
|
|
exoPlayer.seekTo(max(0L, state.getLong(STATE_PLAYER_POS, 0L)))
|
|
|
|
lastVolume = state.getFloat(STATE_LAST_VOLUME, 1f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private val mediaSourceEventListener = object : MediaSourceEventListener {
|
|
|
|
override fun onLoadStarted(
|
|
|
|
windowIndex: Int,
|
|
|
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
|
|
|
loadEventInfo: LoadEventInfo,
|
|
|
|
mediaLoadData: MediaLoadData
|
2018-04-05 22:47:25 +02:00
|
|
|
) {
|
2020-10-24 05:14:53 +02:00
|
|
|
log.d("onLoadStarted")
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDownstreamFormatChanged(
|
|
|
|
windowIndex: Int,
|
|
|
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
|
|
|
mediaLoadData: MediaLoadData
|
2018-04-05 22:47:25 +02:00
|
|
|
) {
|
2020-10-24 05:14:53 +02:00
|
|
|
log.d("onDownstreamFormatChanged")
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onUpstreamDiscarded(
|
|
|
|
windowIndex: Int,
|
|
|
|
mediaPeriodId: MediaSource.MediaPeriodId,
|
|
|
|
mediaLoadData: MediaLoadData
|
2018-04-05 22:47:25 +02:00
|
|
|
) {
|
2020-10-24 05:14:53 +02:00
|
|
|
log.d("onUpstreamDiscarded")
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onLoadCompleted(
|
|
|
|
windowIndex: Int,
|
|
|
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
|
|
|
loadEventInfo: LoadEventInfo,
|
|
|
|
mediaLoadData: MediaLoadData
|
2018-10-01 11:45:51 +02:00
|
|
|
) {
|
2020-10-24 05:14:53 +02:00
|
|
|
log.d("onLoadCompleted")
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onLoadCanceled(
|
|
|
|
windowIndex: Int,
|
|
|
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
|
|
|
loadEventInfo: LoadEventInfo,
|
|
|
|
mediaLoadData: MediaLoadData
|
2018-10-01 11:45:51 +02:00
|
|
|
) {
|
2020-10-24 05:14:53 +02:00
|
|
|
log.d("onLoadCanceled")
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onLoadError(
|
|
|
|
windowIndex: Int,
|
|
|
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
|
|
|
loadEventInfo: LoadEventInfo,
|
|
|
|
mediaLoadData: MediaLoadData,
|
|
|
|
error: IOException,
|
|
|
|
wasCanceled: Boolean
|
2018-10-01 11:45:51 +02:00
|
|
|
) {
|
2020-10-24 05:14:53 +02:00
|
|
|
showError(error.withCaption("load error."))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressLint("StaticFieldLeak")
|
|
|
|
private fun loadBitmap(ta: TootAttachment) {
|
|
|
|
|
|
|
|
cbMute.visibility = View.INVISIBLE
|
|
|
|
|
|
|
|
val urlList = ta.getLargeUrlList(App1.pref)
|
|
|
|
if (urlList.isEmpty()) {
|
|
|
|
showError("missing media attachment url.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
tvStatus.visibility = View.VISIBLE
|
|
|
|
tvStatus.text = null
|
|
|
|
|
|
|
|
pbvImage.visibility = View.VISIBLE
|
|
|
|
pbvImage.setBitmap(null)
|
|
|
|
|
|
|
|
TootTaskRunner(this, TootTaskRunner.PROGRESS_HORIZONTAL).run(object : TootTask {
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
private val options = BitmapFactory.Options()
|
2020-10-24 05:14:53 +02:00
|
|
|
|
|
|
|
var bitmap: Bitmap? = null
|
|
|
|
|
2019-09-12 15:43:11 +02:00
|
|
|
private fun decodeBitmap(
|
2020-10-24 05:14:53 +02:00
|
|
|
data: ByteArray,
|
|
|
|
@Suppress("SameParameterValue") pixel_max: Int
|
|
|
|
): Pair<Bitmap?, String?> {
|
|
|
|
|
|
|
|
val orientation: Int? = ByteArrayInputStream(data).imageOrientation()
|
|
|
|
|
2018-11-22 17:10:34 +01:00
|
|
|
// detects image size
|
2018-01-04 19:52:25 +01:00
|
|
|
options.inJustDecodeBounds = true
|
|
|
|
options.inScaled = false
|
|
|
|
options.outWidth = 0
|
|
|
|
options.outHeight = 0
|
2018-01-10 16:47:35 +01:00
|
|
|
BitmapFactory.decodeByteArray(data, 0, data.size, options)
|
2018-01-04 19:52:25 +01:00
|
|
|
var w = options.outWidth
|
|
|
|
var h = options.outHeight
|
2020-10-24 05:14:53 +02:00
|
|
|
if (w <= 0 || h <= 0) {
|
2019-10-18 09:01:57 +02:00
|
|
|
return Pair(null, "can't decode image bounds.")
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2018-11-22 17:10:34 +01:00
|
|
|
// calc bits to reduce size
|
2018-01-04 19:52:25 +01:00
|
|
|
var bits = 0
|
2020-10-24 05:14:53 +02:00
|
|
|
while (w > pixel_max || h > pixel_max) {
|
|
|
|
++bits
|
2018-01-04 19:52:25 +01:00
|
|
|
w = w shr 1
|
|
|
|
h = h shr 1
|
|
|
|
}
|
|
|
|
options.inJustDecodeBounds = false
|
|
|
|
options.inSampleSize = 1 shl bits
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2018-11-22 17:10:34 +01:00
|
|
|
// decode image
|
|
|
|
val bitmap1 = BitmapFactory.decodeByteArray(data, 0, data.size, options)
|
2020-10-24 05:14:53 +02:00
|
|
|
?: return Pair(null, "BitmapFactory.decodeByteArray returns null.")
|
|
|
|
|
2019-10-18 09:01:57 +02:00
|
|
|
val srcWidth = bitmap1.width.toFloat()
|
|
|
|
val srcHeight = bitmap1.height.toFloat()
|
2020-10-24 05:14:53 +02:00
|
|
|
if (srcWidth <= 0f || srcHeight <= 0f) {
|
2019-10-18 09:01:57 +02:00
|
|
|
bitmap1.recycle()
|
|
|
|
return Pair(null, "image size <= 0")
|
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2020-09-29 19:44:56 +02:00
|
|
|
val dstSize = rotateSize(orientation, srcWidth, srcHeight)
|
2019-10-18 09:01:57 +02:00
|
|
|
val dstSizeInt = Point(
|
2020-10-24 05:14:53 +02:00
|
|
|
max(1, (dstSize.x + 0.5f).toInt()),
|
|
|
|
max(1, (dstSize.y + 0.5f).toInt())
|
2019-10-18 09:01:57 +02:00
|
|
|
)
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2018-11-22 17:10:34 +01:00
|
|
|
// 回転行列を作る
|
|
|
|
val matrix = Matrix()
|
|
|
|
matrix.reset()
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2018-11-22 17:10:34 +01:00
|
|
|
// 画像の中心が原点に来るようにして
|
2020-10-24 05:14:53 +02:00
|
|
|
matrix.postTranslate(srcWidth * -0.5f, srcHeight * -0.5f)
|
|
|
|
|
2018-11-22 17:10:34 +01:00
|
|
|
// orientationに合わせた回転指定
|
2019-10-18 09:01:57 +02:00
|
|
|
matrix.resolveOrientation(orientation)
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2018-11-22 17:10:34 +01:00
|
|
|
// 表示領域に埋まるように平行移動
|
2019-10-18 09:01:57 +02:00
|
|
|
matrix.postTranslate(dstSize.x * 0.5f, dstSize.y * 0.5f)
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2018-11-22 17:10:34 +01:00
|
|
|
// 回転後の画像
|
|
|
|
val bitmap2 = try {
|
2019-10-18 09:01:57 +02:00
|
|
|
Bitmap.createBitmap(dstSizeInt.x, dstSizeInt.y, Bitmap.Config.ARGB_8888)
|
2020-10-24 05:14:53 +02:00
|
|
|
?: return Pair(bitmap1, "createBitmap returns null")
|
|
|
|
} catch (ex: Throwable) {
|
2018-11-22 17:10:34 +01:00
|
|
|
log.trace(ex)
|
2019-10-18 09:01:57 +02:00
|
|
|
return Pair(bitmap1, ex.withCaption("createBitmap failed."))
|
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2018-11-22 17:10:34 +01:00
|
|
|
try {
|
|
|
|
Canvas(bitmap2).drawBitmap(
|
2020-10-24 05:14:53 +02:00
|
|
|
bitmap1,
|
|
|
|
matrix,
|
|
|
|
Paint().apply { isFilterBitmap = true }
|
2018-11-22 17:10:34 +01:00
|
|
|
)
|
2020-10-24 05:14:53 +02:00
|
|
|
} catch (ex: Throwable) {
|
2018-11-22 17:10:34 +01:00
|
|
|
log.trace(ex)
|
|
|
|
bitmap2.recycle()
|
2019-10-18 09:01:57 +02:00
|
|
|
return Pair(bitmap1, ex.withCaption("drawBitmap failed."))
|
2018-11-22 17:10:34 +01:00
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2018-11-22 17:10:34 +01:00
|
|
|
try {
|
|
|
|
bitmap1.recycle()
|
2020-10-24 05:14:53 +02:00
|
|
|
} catch (ex: Throwable) {
|
2018-11-22 17:10:34 +01:00
|
|
|
}
|
2019-10-18 09:01:57 +02:00
|
|
|
return Pair(bitmap2, null)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2020-12-08 21:24:42 +01:00
|
|
|
suspend fun getHttpCached(
|
2020-10-24 05:14:53 +02:00
|
|
|
client: TootApiClient,
|
|
|
|
url: String
|
|
|
|
): Pair<TootApiResult?, ByteArray?> {
|
2018-01-14 22:47:42 +01:00
|
|
|
val result = TootApiResult.makeWithCaption(url)
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2020-09-29 19:44:56 +02:00
|
|
|
val request = try {
|
2020-02-07 19:54:23 +01:00
|
|
|
Request.Builder()
|
2020-10-24 05:14:53 +02:00
|
|
|
.url(url)
|
|
|
|
.cacheControl(App1.CACHE_CONTROL)
|
|
|
|
.addHeader("Accept", "image/webp,image/*,*/*;q=0.8")
|
|
|
|
.build()
|
|
|
|
} catch (ex: Throwable) {
|
2020-02-07 19:54:23 +01:00
|
|
|
result.setError(ex.withCaption("incorrect URL."))
|
|
|
|
return Pair(result, null)
|
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
|
|
|
|
if (!client.sendRequest(
|
|
|
|
result,
|
|
|
|
tmpOkhttpClient = App1.ok_http_client_media_viewer
|
|
|
|
) {
|
|
|
|
request
|
|
|
|
}
|
2019-06-20 16:48:07 +02:00
|
|
|
) return Pair(result, null)
|
2020-10-24 05:14:53 +02:00
|
|
|
|
|
|
|
if (client.isApiCancelled) return Pair(null, null)
|
|
|
|
|
2018-01-14 22:47:42 +01:00
|
|
|
val response = requireNotNull(result.response)
|
2020-10-24 05:14:53 +02:00
|
|
|
if (!response.isSuccessful) {
|
2019-01-16 16:27:37 +01:00
|
|
|
result.setError(TootApiClient.formatResponse(response, result.caption))
|
2019-06-20 16:48:07 +02:00
|
|
|
return Pair(result, null)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2018-01-14 22:47:42 +01:00
|
|
|
try {
|
2019-01-16 16:27:37 +01:00
|
|
|
val ba = ProgressResponseBody.bytes(response) { bytesRead, bytesTotal ->
|
2018-01-04 19:52:25 +01:00
|
|
|
// 50MB以上のデータはキャンセルする
|
2020-10-24 05:14:53 +02:00
|
|
|
if (max(bytesRead, bytesTotal) >= 50000000) {
|
2018-01-04 19:52:25 +01:00
|
|
|
throw RuntimeException("media attachment is larger than 50000000")
|
|
|
|
}
|
|
|
|
client.publishApiProgressRatio(bytesRead.toInt(), bytesTotal.toInt())
|
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
if (client.isApiCancelled) return Pair(null, null)
|
2019-06-20 16:48:07 +02:00
|
|
|
return Pair(result, ba)
|
2020-10-24 05:14:53 +02:00
|
|
|
} catch (ex: Throwable) {
|
2018-01-21 13:46:36 +01:00
|
|
|
result.setError(TootApiClient.formatResponse(response, result.caption, "?"))
|
2019-06-20 16:48:07 +02:00
|
|
|
return Pair(result, null)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2020-12-08 21:24:42 +01:00
|
|
|
override suspend fun background(client: TootApiClient): TootApiResult? {
|
2020-10-24 05:14:53 +02:00
|
|
|
if (urlList.isEmpty()) return TootApiResult("missing url")
|
|
|
|
var lastResult: TootApiResult? = null
|
|
|
|
for (url in urlList) {
|
2019-06-20 16:48:07 +02:00
|
|
|
val (result, ba) = getHttpCached(client, url)
|
2019-01-16 16:27:37 +01:00
|
|
|
lastResult = result
|
2020-10-24 05:14:53 +02:00
|
|
|
if (ba != null) {
|
2018-01-14 22:47:42 +01:00
|
|
|
client.publishApiProgress("decoding image…")
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2019-10-18 09:01:57 +02:00
|
|
|
val (bitmap, error) = decodeBitmap(ba, 2048)
|
2020-10-24 05:14:53 +02:00
|
|
|
if (bitmap != null) {
|
2018-01-14 22:47:42 +01:00
|
|
|
this.bitmap = bitmap
|
|
|
|
break
|
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
if (error != null) lastResult = TootApiResult(error)
|
2018-01-14 22:47:42 +01:00
|
|
|
}
|
|
|
|
}
|
2019-01-16 16:27:37 +01:00
|
|
|
return lastResult
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
|
2020-12-08 21:24:42 +01:00
|
|
|
override suspend fun handleResult(result: TootApiResult?) {
|
2018-01-10 16:47:35 +01:00
|
|
|
val bitmap = this.bitmap
|
2020-10-24 05:14:53 +02:00
|
|
|
if (bitmap != null) {
|
2018-01-04 19:52:25 +01:00
|
|
|
pbvImage.setBitmap(bitmap)
|
2020-10-24 05:14:53 +02:00
|
|
|
} else if (result != null) {
|
2020-09-29 19:44:56 +02:00
|
|
|
showToast(true, result.error)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2020-10-24 05:14:53 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onClick(v: View) {
|
|
|
|
try {
|
|
|
|
when (v.id) {
|
|
|
|
|
|
|
|
R.id.btnPrevious -> loadDelta(-1)
|
|
|
|
R.id.btnNext -> loadDelta(+1)
|
2018-01-04 19:52:25 +01:00
|
|
|
R.id.btnDownload -> download(media_list[idx])
|
|
|
|
R.id.btnMore -> more(media_list[idx])
|
2020-10-24 05:14:53 +02:00
|
|
|
}
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
showToast(ex, "action failed.")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
internal class DownloadHistory(val time: Long, val url: String)
|
|
|
|
|
|
|
|
private fun download(ta: TootAttachmentLike) {
|
|
|
|
|
|
|
|
val permissionCheck = ContextCompat.checkSelfPermission(
|
|
|
|
this,
|
|
|
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
2018-01-21 13:46:36 +01:00
|
|
|
)
|
2020-10-24 05:14:53 +02:00
|
|
|
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
|
|
|
|
preparePermission()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
val downLoadManager: DownloadManager = systemService(this)
|
|
|
|
?: error("missing DownloadManager system service")
|
|
|
|
|
|
|
|
val url = if (ta is TootAttachment) {
|
|
|
|
ta.getLargeUrl(App1.pref)
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
} ?: return
|
|
|
|
|
|
|
|
// ボタン連打対策
|
|
|
|
run {
|
|
|
|
val now = SystemClock.elapsedRealtime()
|
|
|
|
|
|
|
|
// 期限切れの履歴を削除
|
|
|
|
val it = download_history_list.iterator()
|
|
|
|
while (it.hasNext()) {
|
|
|
|
val dh = it.next()
|
|
|
|
if (now - dh.time >= DOWNLOAD_REPEAT_EXPIRE) {
|
|
|
|
// この履歴は十分に古いので捨てる
|
|
|
|
it.remove()
|
|
|
|
} else if (url == dh.url) {
|
|
|
|
// 履歴に同じURLがあればエラーとする
|
|
|
|
showToast(false, R.string.dont_repeat_download_to_same_url)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 履歴の末尾に追加(履歴は古い順に並ぶ)
|
|
|
|
download_history_list.addLast(DownloadHistory(now, url))
|
|
|
|
}
|
|
|
|
|
|
|
|
var fileName: String? = null
|
|
|
|
|
|
|
|
try {
|
|
|
|
val pathSegments = url.toUri().pathSegments
|
|
|
|
if (pathSegments != null) {
|
|
|
|
val size = pathSegments.size
|
|
|
|
for (i in size - 1 downTo 0) {
|
|
|
|
val s = pathSegments[i]
|
|
|
|
if (s?.isNotEmpty() == true) {
|
|
|
|
fileName = s
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fileName == null) {
|
|
|
|
fileName = url
|
|
|
|
.replaceFirst("https?://".asciiPattern(), "")
|
|
|
|
.replaceAll("[^.\\w\\d]+".asciiPattern(), "-")
|
|
|
|
}
|
|
|
|
if (fileName.length >= 20) fileName = fileName.substring(fileName.length - 20)
|
|
|
|
|
|
|
|
val request = DownloadManager.Request(url.toUri())
|
|
|
|
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
|
|
|
|
request.setTitle(fileName)
|
|
|
|
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE or DownloadManager.Request.NETWORK_WIFI)
|
|
|
|
|
|
|
|
// Android 10 以降では allowScanningByMediaScanner は無視される
|
|
|
|
if (Build.VERSION.SDK_INT < 29) {
|
|
|
|
//メディアスキャンを許可する
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
request.allowScanningByMediaScanner()
|
|
|
|
}
|
|
|
|
|
|
|
|
//ダウンロード中・ダウンロード完了時にも通知を表示する
|
|
|
|
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
|
|
|
|
|
|
|
downLoadManager.enqueue(request)
|
|
|
|
showToast(false, R.string.downloading)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun share(action: String, url: String) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
val intent = Intent(action)
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
|
if (action == Intent.ACTION_SEND) {
|
|
|
|
intent.type = "text/plain"
|
|
|
|
intent.putExtra(Intent.EXTRA_TEXT, url)
|
|
|
|
} else {
|
|
|
|
intent.data = url.toUri()
|
|
|
|
}
|
|
|
|
|
|
|
|
startActivity(intent)
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
showToast(ex, "can't open app.")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun copy(url: String) {
|
|
|
|
val cm = getSystemService(CLIPBOARD_SERVICE) as? ClipboardManager
|
|
|
|
?: throw NotImplementedError("missing ClipboardManager system service")
|
|
|
|
|
|
|
|
try {
|
|
|
|
//クリップボードに格納するItemを作成
|
|
|
|
val item = ClipData.Item(url)
|
|
|
|
|
|
|
|
val mimeType = arrayOfNulls<String>(1)
|
|
|
|
mimeType[0] = ClipDescription.MIMETYPE_TEXT_PLAIN
|
|
|
|
|
|
|
|
//クリップボードに格納するClipDataオブジェクトの作成
|
|
|
|
val cd = ClipData(ClipDescription("media URL", mimeType), item)
|
|
|
|
|
|
|
|
//クリップボードにデータを格納
|
|
|
|
cm.setPrimaryClip(cd)
|
|
|
|
|
|
|
|
showToast(false, R.string.url_is_copied)
|
|
|
|
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
showToast(ex, "clipboard access failed.")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun more(ta: TootAttachmentLike) {
|
|
|
|
val ad = ActionsDialog()
|
|
|
|
|
|
|
|
if (ta is TootAttachment) {
|
|
|
|
val url = ta.getLargeUrl(App1.pref) ?: return
|
|
|
|
|
|
|
|
ad.addAction(getString(R.string.open_in_browser)) { share(Intent.ACTION_VIEW, url) }
|
|
|
|
ad.addAction(getString(R.string.share_url)) { share(Intent.ACTION_SEND, url) }
|
|
|
|
ad.addAction(getString(R.string.copy_url)) { copy(url) }
|
|
|
|
|
|
|
|
addMoreMenu(ad, "url", ta.url, Intent.ACTION_VIEW)
|
|
|
|
addMoreMenu(ad, "remote_url", ta.remote_url, Intent.ACTION_VIEW)
|
|
|
|
addMoreMenu(ad, "preview_url", ta.preview_url, Intent.ACTION_VIEW)
|
|
|
|
addMoreMenu(ad, "preview_remote_url", ta.preview_remote_url, Intent.ACTION_VIEW)
|
|
|
|
addMoreMenu(ad, "text_url", ta.text_url, Intent.ACTION_VIEW)
|
|
|
|
|
|
|
|
} else if (ta is TootAttachmentMSP) {
|
|
|
|
val url = ta.preview_url
|
|
|
|
ad.addAction(getString(R.string.open_in_browser)) { share(Intent.ACTION_VIEW, url) }
|
|
|
|
ad.addAction(getString(R.string.share_url)) { share(Intent.ACTION_SEND, url) }
|
|
|
|
ad.addAction(getString(R.string.copy_url)) { copy(url) }
|
|
|
|
}
|
|
|
|
|
|
|
|
ad.show(this, null)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun addMoreMenu(
|
|
|
|
ad: ActionsDialog,
|
|
|
|
caption_prefix: String,
|
|
|
|
url: String?,
|
|
|
|
@Suppress("SameParameterValue") action: String
|
2018-01-21 13:46:36 +01:00
|
|
|
) {
|
2020-10-24 05:14:53 +02:00
|
|
|
val uri = url.mayUri() ?: return
|
|
|
|
|
|
|
|
val caption = getString(R.string.open_browser_of, caption_prefix)
|
|
|
|
|
|
|
|
ad.addAction(caption) {
|
|
|
|
try {
|
|
|
|
val intent = Intent(action, uri)
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
|
startActivity(intent)
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
showToast(ex, "can't open app.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun preparePermission() {
|
|
|
|
if (Build.VERSION.SDK_INT >= 23) {
|
|
|
|
ActivityCompat.requestPermissions(
|
|
|
|
this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE
|
2018-01-04 19:52:25 +01:00
|
|
|
)
|
2020-10-24 05:14:53 +02:00
|
|
|
} else {
|
|
|
|
showToast(true, R.string.missing_permission_to_access_media)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onRequestPermissionsResult(
|
|
|
|
requestCode: Int, permissions: Array<String>, grantResults: IntArray
|
2018-01-04 19:52:25 +01:00
|
|
|
) {
|
2020-10-24 05:14:53 +02:00
|
|
|
when (requestCode) {
|
2018-01-04 19:52:25 +01:00
|
|
|
PERMISSION_REQUEST_CODE -> {
|
|
|
|
var bNotGranted = false
|
|
|
|
var i = 0
|
|
|
|
val ie = permissions.size
|
2020-10-24 05:14:53 +02:00
|
|
|
while (i < ie) {
|
|
|
|
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
|
2018-01-04 19:52:25 +01:00
|
|
|
bNotGranted = true
|
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
++i
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
if (bNotGranted) {
|
2020-09-29 19:44:56 +02:00
|
|
|
showToast(true, R.string.missing_permission_to_access_media)
|
2018-01-04 19:52:25 +01:00
|
|
|
} else {
|
|
|
|
download(media_list[idx])
|
|
|
|
}
|
|
|
|
}
|
2020-10-24 05:14:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-26 21:36:14 +01:00
|
|
|
}
|