commit
bd3bed36ee
@ -1,46 +1,197 @@ |
|||||||
package io.legado.app.service |
package io.legado.app.service |
||||||
|
|
||||||
|
import android.app.DownloadManager |
||||||
|
import android.content.BroadcastReceiver |
||||||
|
import android.content.Context |
||||||
import android.content.Intent |
import android.content.Intent |
||||||
|
import android.content.IntentFilter |
||||||
|
import android.net.Uri |
||||||
|
import android.os.Build |
||||||
|
import android.os.Handler |
||||||
import androidx.core.app.NotificationCompat |
import androidx.core.app.NotificationCompat |
||||||
|
import androidx.core.content.FileProvider |
||||||
|
import androidx.core.os.bundleOf |
||||||
|
import io.legado.app.BuildConfig |
||||||
import io.legado.app.R |
import io.legado.app.R |
||||||
import io.legado.app.base.BaseService |
import io.legado.app.base.BaseService |
||||||
import io.legado.app.constant.AppConst |
import io.legado.app.constant.AppConst |
||||||
import io.legado.app.constant.IntentAction |
import io.legado.app.constant.IntentAction |
||||||
import io.legado.app.help.IntentHelp |
import io.legado.app.help.IntentHelp |
||||||
|
import io.legado.app.utils.RealPathUtil |
||||||
|
import io.legado.app.utils.msg |
||||||
|
import org.jetbrains.anko.downloadManager |
||||||
|
import org.jetbrains.anko.toast |
||||||
|
import java.io.File |
||||||
|
|
||||||
|
|
||||||
class DownloadService : BaseService() { |
class DownloadService : BaseService() { |
||||||
|
|
||||||
private val notificationBuilder by lazy { |
private val downloads = hashMapOf<Long, String>() |
||||||
val builder = NotificationCompat.Builder(this, AppConst.channelIdDownload) |
private val completeDownloads = hashSetOf<Long>() |
||||||
.setSmallIcon(R.drawable.ic_download) |
private val handler = Handler() |
||||||
.setOngoing(true) |
private val runnable = Runnable { |
||||||
.setContentTitle(getString(R.string.action_download)) |
checkDownloadState() |
||||||
builder.addAction( |
} |
||||||
R.drawable.ic_stop_black_24dp, |
|
||||||
getString(R.string.cancel), |
private val downloadReceiver = object : BroadcastReceiver() { |
||||||
IntentHelp.servicePendingIntent<DownloadService>(this, IntentAction.stop) |
override fun onReceive(context: Context, intent: Intent) { |
||||||
) |
queryState() |
||||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) |
} |
||||||
} |
} |
||||||
|
|
||||||
override fun onCreate() { |
override fun onCreate() { |
||||||
super.onCreate() |
super.onCreate() |
||||||
updateNotification("准备下载") |
registerReceiver(downloadReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onTaskRemoved(rootIntent: Intent?) { |
||||||
|
super.onTaskRemoved(rootIntent) |
||||||
|
stopSelf() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onDestroy() { |
||||||
|
super.onDestroy() |
||||||
|
unregisterReceiver(downloadReceiver) |
||||||
} |
} |
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
||||||
when (intent?.action) { |
when (intent?.action) { |
||||||
|
IntentAction.start -> startDownload( |
||||||
|
intent.getLongExtra("downloadId", 0), |
||||||
|
intent.getStringExtra("fileName") ?: "未知文件" |
||||||
|
) |
||||||
|
IntentAction.play -> { |
||||||
|
val id = intent.getLongExtra("downloadId", 0) |
||||||
|
if (downloads[id]?.endsWith(".apk") == true) { |
||||||
|
installApk(id) |
||||||
|
} |
||||||
|
} |
||||||
|
IntentAction.stop -> { |
||||||
|
val downloadId = intent.getLongExtra("downloadId", 0) |
||||||
|
downloads.remove(downloadId) |
||||||
|
if (downloads.isEmpty()) { |
||||||
|
stopSelf() |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
return super.onStartCommand(intent, flags, startId) |
return super.onStartCommand(intent, flags, startId) |
||||||
} |
} |
||||||
|
|
||||||
|
private fun startDownload(downloadId: Long, fileName: String) { |
||||||
|
if (downloadId > 0) { |
||||||
|
downloads[downloadId] = fileName |
||||||
|
queryState() |
||||||
|
checkDownloadState() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun checkDownloadState() { |
||||||
|
handler.removeCallbacks(runnable) |
||||||
|
queryState() |
||||||
|
handler.postDelayed(runnable, 1000) |
||||||
|
} |
||||||
|
|
||||||
|
//查询下载进度 |
||||||
|
private fun queryState() { |
||||||
|
val ids = downloads.keys |
||||||
|
val query = DownloadManager.Query() |
||||||
|
query.setFilterById(*ids.toLongArray()) |
||||||
|
downloadManager.query(query).use { cursor -> |
||||||
|
if (!cursor.moveToFirst()) return |
||||||
|
val id = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID)) |
||||||
|
val progress: Int = |
||||||
|
cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) |
||||||
|
val max: Int = |
||||||
|
cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) |
||||||
|
val status = |
||||||
|
when (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))) { |
||||||
|
DownloadManager.STATUS_PAUSED -> "暂停" |
||||||
|
DownloadManager.STATUS_PENDING -> "待下载" |
||||||
|
DownloadManager.STATUS_RUNNING -> "下载中" |
||||||
|
DownloadManager.STATUS_SUCCESSFUL -> { |
||||||
|
if (!completeDownloads.contains(id)) { |
||||||
|
completeDownloads.add(id) |
||||||
|
if (downloads[id]?.endsWith(".apk") == true) { |
||||||
|
installApk(id) |
||||||
|
} |
||||||
|
} |
||||||
|
"下载完成" |
||||||
|
} |
||||||
|
DownloadManager.STATUS_FAILED -> "下载失败" |
||||||
|
else -> "未知状态" |
||||||
|
} |
||||||
|
updateNotification(id, "${downloads[id]} $status", max, progress) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun installApk(downloadId: Long) { |
||||||
|
downloadManager.getUriForDownloadedFile(downloadId)?.let { |
||||||
|
val filePath = RealPathUtil.getPath(this, it) ?: return |
||||||
|
val file = File(filePath) |
||||||
|
//调用系统安装apk |
||||||
|
val intent = Intent() |
||||||
|
intent.action = Intent.ACTION_VIEW |
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) |
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //7.0版本以上 |
||||||
|
val uriForFile: Uri = |
||||||
|
FileProvider.getUriForFile( |
||||||
|
this, |
||||||
|
"${BuildConfig.APPLICATION_ID}.fileProvider", |
||||||
|
file |
||||||
|
) |
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) |
||||||
|
intent.setDataAndType(uriForFile, "application/vnd.android.package-archive") |
||||||
|
} else { |
||||||
|
val uri: Uri = Uri.fromFile(file) |
||||||
|
intent.setDataAndType(uri, "application/vnd.android.package-archive") |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
startActivity(intent) |
||||||
|
} catch (e: Exception) { |
||||||
|
toast(e.msg) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
/** |
/** |
||||||
* 更新通知 |
* 更新通知 |
||||||
*/ |
*/ |
||||||
private fun updateNotification(content: String) { |
private fun updateNotification(downloadId: Long, content: String, max: Int, progress: Int) { |
||||||
|
val notificationBuilder = NotificationCompat.Builder(this, AppConst.channelIdDownload) |
||||||
|
.setSmallIcon(R.drawable.ic_download) |
||||||
|
.setOngoing(true) |
||||||
|
.setContentTitle(getString(R.string.action_download)) |
||||||
|
notificationBuilder.setContentIntent( |
||||||
|
IntentHelp.servicePendingIntent<DownloadService>( |
||||||
|
this, |
||||||
|
IntentAction.play, |
||||||
|
bundleOf("downloadId" to downloadId) |
||||||
|
) |
||||||
|
) |
||||||
|
notificationBuilder.addAction( |
||||||
|
R.drawable.ic_stop_black_24dp, |
||||||
|
getString(R.string.cancel), |
||||||
|
IntentHelp.servicePendingIntent<DownloadService>( |
||||||
|
this, |
||||||
|
IntentAction.stop, |
||||||
|
bundleOf("downloadId" to downloadId) |
||||||
|
) |
||||||
|
) |
||||||
|
notificationBuilder.setDeleteIntent( |
||||||
|
IntentHelp.servicePendingIntent<DownloadService>( |
||||||
|
this, |
||||||
|
IntentAction.stop, |
||||||
|
bundleOf("downloadId" to downloadId) |
||||||
|
) |
||||||
|
) |
||||||
|
notificationBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) |
||||||
notificationBuilder.setContentText(content) |
notificationBuilder.setContentText(content) |
||||||
|
notificationBuilder.setProgress(max, progress, false) |
||||||
|
notificationBuilder.setAutoCancel(true) |
||||||
val notification = notificationBuilder.build() |
val notification = notificationBuilder.build() |
||||||
startForeground(AppConst.notificationIdDownload, notification) |
startForeground(downloadId.toInt(), notification) |
||||||
} |
} |
||||||
|
|
||||||
} |
} |
@ -0,0 +1,26 @@ |
|||||||
|
package io.legado.app.service.help |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import io.legado.app.constant.IntentAction |
||||||
|
import io.legado.app.service.DownloadService |
||||||
|
|
||||||
|
object Download { |
||||||
|
|
||||||
|
fun start(context: Context, downloadId: Long, fileName: String) { |
||||||
|
Intent(context, DownloadService::class.java).let { |
||||||
|
it.action = IntentAction.start |
||||||
|
it.putExtra("downloadId", downloadId) |
||||||
|
it.putExtra("fileName", fileName) |
||||||
|
context.startService(it) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun stop(context: Context) { |
||||||
|
Intent(context, DownloadService::class.java).let { |
||||||
|
it.action = IntentAction.stop |
||||||
|
context.startService(it) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue