package com.pitchedapps.frost.utils import android.app.Notification import android.app.PendingIntent import android.content.Context import android.content.Intent import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationManagerCompat import android.support.v4.content.FileProvider import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE import ca.allanwang.kau.permissions.kauRequestPermissions import ca.allanwang.kau.utils.copyFromInputStream import ca.allanwang.kau.utils.string import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R import com.pitchedapps.frost.services.frostConfig import com.pitchedapps.frost.services.frostNotification import com.pitchedapps.frost.services.getNotificationPendingCancelIntent import okhttp3.MediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.ResponseBody import okio.* import org.jetbrains.anko.AnkoAsyncContext import org.jetbrains.anko.doAsync import java.io.File import java.io.IOException import java.lang.ref.WeakReference /** * Created by Allan Wang on 2017-08-04. * * With reference to the OkHttp3 sample */ fun Context.frostDownload(url: String) { L.d("Received download request", "Download $url") val type = if (url.contains("video")) DownloadType.VIDEO else return L.d("Download request does not match any type") kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) { granted, _ -> if (granted) doAsync { frostDownloadImpl(url, type) } } } private const val MAX_PROGRESS = 1000 //private val DOWNLOAD_GROUP: String? = null private const val DOWNLOAD_GROUP = "frost_downloads" private enum class DownloadType(val downloadingRes: Int, val downloadedRes: Int) { VIDEO(R.string.downloading_video, R.string.downloaded_video), FILE(R.string.downloading_file, R.string.downloaded_file); fun getPendingIntent(context: Context, file: File): PendingIntent { val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file) val type = context.contentResolver.getType(uri) L.d("DownloadType: retrieved pending intent - $uri $type") val intent = Intent(Intent.ACTION_VIEW, uri) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .setDataAndType(uri, type) return PendingIntent.getActivity(context, 0, intent, 0) } } private fun AnkoAsyncContext.frostDownloadImpl(url: String, type: DownloadType) { L.d("Starting download request") val notifId = Math.abs(url.hashCode() + System.currentTimeMillis().toInt()) var notifBuilderAttempt: NotificationCompat.Builder? = null weakRef.get()?.apply { notifBuilderAttempt = frostNotification .setContentTitle(string(type.downloadingRes)) .setCategory(Notification.CATEGORY_PROGRESS) .setWhen(System.currentTimeMillis()) .setProgress(MAX_PROGRESS, 0, false) .setOngoing(true) .addAction(R.drawable.ic_action_cancel, string(R.string.kau_cancel), getNotificationPendingCancelIntent(DOWNLOAD_GROUP, notifId)) .setGroup(DOWNLOAD_GROUP) } val notifBuilder = notifBuilderAttempt ?: return notifBuilder.show(weakRef, notifId) val request: Request = Request.Builder() .url(url) .tag(url) .build() var client: OkHttpClient? = null client = OkHttpClient.Builder() .addNetworkInterceptor { chain -> val original = chain.proceed(chain.request()) return@addNetworkInterceptor original.newBuilder().body(ProgressResponseBody(original.body()!!) { bytesRead, contentLength, done -> //cancel request if context reference is now invalid if (weakRef.get() == null) { client?.cancel(url) } val ctx = weakRef.get() ?: return@ProgressResponseBody client?.cancel(url) ?: Unit val percentage = bytesRead.toFloat() / contentLength.toFloat() * MAX_PROGRESS L.v("Download request progress: $percentage") notifBuilder.setProgress(MAX_PROGRESS, percentage.toInt(), false) if (done) { notifBuilder.setFinished(ctx, type) L.d("Download request finished") } notifBuilder.show(weakRef, notifId) }).build() } .build() client.newCall(request).execute().use { response -> if (!response.isSuccessful) throw IOException("Unexpected code $response") val stream = response.body()?.byteStream() if (stream != null) { val destination = createMediaFile(".mp4") destination.copyFromInputStream(stream) weakRef.get()?.apply { notifBuilder.setContentIntent(type.getPendingIntent(this, destination)) notifBuilder.show(weakRef, notifId) } } } } private fun NotificationCompat.Builder.setFinished(context: Context, type: DownloadType) = setContentTitle(context.string(type.downloadedRes)) .setProgress(0, 0, false).setOngoing(false).setAutoCancel(true) .apply { mActions.clear() } private fun OkHttpClient.cancel(url: String) { val call = dispatcher().runningCalls().firstOrNull { it.request().tag() == url } if (call != null && !call.isCanceled) call.cancel() } private fun NotificationCompat.Builder.show(weakRef: WeakReference, notifId: Int) { val c = weakRef.get() ?: return NotificationManagerCompat.from(c).notify(DOWNLOAD_GROUP, notifId, build().frostConfig()) } private class ProgressResponseBody( val responseBody: ResponseBody, val listener: (bytesRead: Long, contentLength: Long, done: Boolean) -> Unit) : ResponseBody() { private val bufferedSource: BufferedSource by lazy { Okio.buffer(source(responseBody.source())) } override fun contentLength(): Long = responseBody.contentLength() override fun contentType(): MediaType? = responseBody.contentType() override fun source(): BufferedSource = bufferedSource private fun source(source: Source): Source = object : ForwardingSource(source) { private var totalBytesRead = 0L override fun read(sink: Buffer?, byteCount: Long): Long { val bytesRead = super.read(sink, byteCount) // read() returns the number of bytes read, or -1 if this source is exhausted. totalBytesRead += if (bytesRead != -1L) bytesRead else 0 listener(totalBytesRead, responseBody.contentLength(), bytesRead == -1L) return bytesRead } } }