mirror of https://github.com/lizongying/my-tv.git
parent
e7780434d7
commit
8a3720049f
@ -0,0 +1,36 @@ |
||||
package com.lizongying.mytv |
||||
|
||||
import android.app.AlertDialog |
||||
import android.app.Dialog |
||||
import android.os.Bundle |
||||
import androidx.fragment.app.DialogFragment |
||||
|
||||
class ConfirmationDialogFragment(private val listener: ConfirmationDialogListener) : |
||||
DialogFragment() { |
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { |
||||
return activity?.let { |
||||
val builder = AlertDialog.Builder(it) |
||||
builder.setTitle("确认") |
||||
.setMessage("确认更新吗?") |
||||
.setPositiveButton( |
||||
"确定" |
||||
) { _, _ -> |
||||
listener.onConfirm() |
||||
} |
||||
.setNegativeButton( |
||||
"取消" |
||||
) { _, _ -> |
||||
listener.onCancel() |
||||
} |
||||
// 创建并返回 AlertDialog 对象 |
||||
builder.create() |
||||
} ?: throw IllegalStateException("Activity cannot be null") |
||||
} |
||||
|
||||
interface ConfirmationDialogListener { |
||||
fun onConfirm() |
||||
fun onCancel() |
||||
} |
||||
} |
||||
|
||||
@ -1,5 +1,189 @@ |
||||
package com.lizongying.mytv |
||||
|
||||
class UpdateManager { |
||||
import android.app.DownloadManager |
||||
import android.app.DownloadManager.Request |
||||
import android.content.BroadcastReceiver |
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.content.IntentFilter |
||||
import android.database.Cursor |
||||
import android.net.Uri |
||||
import android.os.Build |
||||
import android.os.Environment |
||||
import android.os.Handler |
||||
import android.os.Looper |
||||
import android.util.Log |
||||
import android.widget.Toast |
||||
import com.lizongying.mytv.api.Release |
||||
import com.lizongying.mytv.requests.MyRequest |
||||
import kotlinx.coroutines.CoroutineScope |
||||
import kotlinx.coroutines.Dispatchers |
||||
import kotlinx.coroutines.launch |
||||
import java.io.File |
||||
|
||||
|
||||
class UpdateManager( |
||||
private var context: Context?, |
||||
private var settingFragment: SettingFragment, |
||||
private var versionCode: Long |
||||
) : |
||||
ConfirmationDialogFragment.ConfirmationDialogListener { |
||||
|
||||
private var myRequest = MyRequest() |
||||
private var release: Release? = null |
||||
|
||||
private var downloadReceiver: DownloadReceiver? = null |
||||
|
||||
fun checkAndUpdate() { |
||||
CoroutineScope(Dispatchers.Main).launch { |
||||
try { |
||||
release = myRequest.getRelease() |
||||
updateUI(release) |
||||
Log.i(TAG, "versionCode $versionCode ${release?.data?.versionCode}") |
||||
if (release != null) { |
||||
if (release?.data?.versionCode!! >= versionCode) { |
||||
val dialog = ConfirmationDialogFragment(this@UpdateManager) |
||||
dialog.show(settingFragment.fragmentManager, "ConfirmationDialogFragment") |
||||
} else { |
||||
Toast.makeText(context, "不需要更新", Toast.LENGTH_LONG) |
||||
.show() |
||||
} |
||||
} |
||||
} catch (e: Exception) { |
||||
Log.e(TAG, "Error occurred: ${e.message}", e) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun updateUI(release: Release?) { |
||||
if (release?.data?.versionName.isNullOrEmpty()) { |
||||
settingFragment.setVersionName("版本获取失败") |
||||
} else { |
||||
settingFragment.setVersionName("最新版本:${release?.data?.versionName!!}") |
||||
} |
||||
} |
||||
|
||||
private fun startDownload(release: Release) { |
||||
val apkFileName = "my-tv-${release.data.versionName}.apk" |
||||
Log.i(TAG, "apkFileName $apkFileName") |
||||
val downloadManager = |
||||
context!!.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager |
||||
val request = Request(Uri.parse(release.data.downloadUrl)) |
||||
Log.i(TAG, "url ${Uri.parse(release.data.downloadUrl)}") |
||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, apkFileName) |
||||
request.setTitle("New Version Download") |
||||
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) |
||||
|
||||
// 获取下载任务的引用 |
||||
val downloadReference = downloadManager.enqueue(request) |
||||
|
||||
downloadReceiver = DownloadReceiver(context!!, apkFileName, downloadReference) |
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
||||
context!!.registerReceiver( |
||||
downloadReceiver, |
||||
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), |
||||
Context.RECEIVER_NOT_EXPORTED, |
||||
) |
||||
} else { |
||||
context!!.registerReceiver( |
||||
downloadReceiver, |
||||
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) |
||||
) |
||||
} |
||||
|
||||
getDownloadProgress(context!!, downloadReference) { progress -> |
||||
println("Download progress: $progress%") |
||||
} |
||||
} |
||||
|
||||
private fun getDownloadProgress(context: Context, downloadId: Long, progressListener: (Int) -> Unit) { |
||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager |
||||
val handler = Handler(Looper.getMainLooper()) |
||||
val intervalMillis: Long = 1000 |
||||
|
||||
handler.post(object : Runnable { |
||||
override fun run() { |
||||
Log.i(TAG, "search") |
||||
val query = DownloadManager.Query().setFilterById(downloadId) |
||||
val cursor: Cursor = downloadManager.query(query) |
||||
cursor.use { |
||||
if (it.moveToFirst()) { |
||||
val bytesDownloadedIndex = |
||||
it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR) |
||||
val bytesTotalIndex = |
||||
it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES) |
||||
|
||||
// 检查列名是否存在 |
||||
if (bytesDownloadedIndex != -1 && bytesTotalIndex != -1) { |
||||
val bytesDownloaded = it.getInt(bytesDownloadedIndex) |
||||
val bytesTotal = it.getInt(bytesTotalIndex) |
||||
|
||||
if (bytesTotal != -1) { |
||||
val progress = (bytesDownloaded * 100L / bytesTotal).toInt() |
||||
progressListener(progress) |
||||
if (progress == 100) { |
||||
return |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// handler.postDelayed(this, intervalMillis) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
private class DownloadReceiver( |
||||
private val context: Context, |
||||
private val apkFileName: String, |
||||
private val downloadReference: Long |
||||
) : BroadcastReceiver() { |
||||
override fun onReceive(context: Context, intent: Intent) { |
||||
val reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) |
||||
Log.i(TAG, "reference $reference") |
||||
val progress = intent.getIntExtra("progress", 0) |
||||
Log.i(TAG, "progress $progress") |
||||
|
||||
// 检查是否是我们发起的下载 |
||||
if (reference == downloadReference) { |
||||
// 下载完成,触发安装 |
||||
installNewVersion() |
||||
} |
||||
} |
||||
|
||||
private fun installNewVersion() { |
||||
val installIntent = Intent(Intent.ACTION_VIEW) |
||||
val apkUri = Uri.fromFile( |
||||
File( |
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), |
||||
apkFileName |
||||
) |
||||
) |
||||
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive") |
||||
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) |
||||
context.startActivity(installIntent) |
||||
} |
||||
} |
||||
|
||||
companion object { |
||||
private const val TAG = "UpdateManager" |
||||
} |
||||
|
||||
override fun onConfirm() { |
||||
Log.i(TAG, "onConfirm $release") |
||||
release?.let { startDownload(it) } |
||||
} |
||||
|
||||
override fun onCancel() { |
||||
} |
||||
|
||||
|
||||
fun destroy() { |
||||
if (downloadReceiver != null) { |
||||
context!!.unregisterReceiver(downloadReceiver) |
||||
Log.i(TAG, "destroy downloadReceiver") |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,37 @@ |
||||
package com.lizongying.mytv.api |
||||
|
||||
import android.util.Log |
||||
import okhttp3.MediaType |
||||
import okhttp3.RequestBody |
||||
import okio.BufferedSink |
||||
import java.io.IOException |
||||
|
||||
data class Auth( |
||||
val code: Int, |
||||
val msg: String, |
||||
val data: AuthData, |
||||
) |
||||
|
||||
data class AuthData( |
||||
val token: String, |
||||
) |
||||
|
||||
data class AuthRequest( |
||||
var data: String, |
||||
) : RequestBody() { |
||||
override fun contentType(): MediaType? { |
||||
return MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8") |
||||
} |
||||
|
||||
override fun writeTo(sink: BufferedSink) { |
||||
try { |
||||
sink.writeUtf8(data) |
||||
} catch (e: IOException) { |
||||
Log.e(TAG, "$e") |
||||
} |
||||
} |
||||
|
||||
companion object { |
||||
private const val TAG = "AuthRequest" |
||||
} |
||||
} |
||||
@ -0,0 +1,11 @@ |
||||
package com.lizongying.mytv.api |
||||
|
||||
import retrofit2.Call |
||||
import retrofit2.http.GET |
||||
|
||||
|
||||
interface ReleaseService { |
||||
@GET("my-tv/v1/release") |
||||
fun getRelease( |
||||
): Call<Release> |
||||
} |
||||
@ -0,0 +1,44 @@ |
||||
package com.lizongying.mytv.requests |
||||
|
||||
import com.lizongying.mytv.api.ApiClient |
||||
import com.lizongying.mytv.api.Release |
||||
import kotlinx.coroutines.Dispatchers |
||||
import kotlinx.coroutines.withContext |
||||
import retrofit2.Call |
||||
import retrofit2.Callback |
||||
import retrofit2.Response |
||||
import kotlin.coroutines.resume |
||||
import kotlin.coroutines.suspendCoroutine |
||||
|
||||
class MyRequest { |
||||
private var releaseService = ApiClient().releaseService |
||||
|
||||
suspend fun getRelease(): Release? { |
||||
return withContext(Dispatchers.IO) { |
||||
fetchRelease() |
||||
} |
||||
} |
||||
|
||||
private suspend fun fetchRelease(): Release? { |
||||
return suspendCoroutine { continuation -> |
||||
releaseService.getRelease() |
||||
.enqueue(object : Callback<Release> { |
||||
override fun onResponse(call: Call<Release>, response: Response<Release>) { |
||||
if (response.isSuccessful) { |
||||
continuation.resume(response.body()) |
||||
} else { |
||||
continuation.resume(null) |
||||
} |
||||
} |
||||
|
||||
override fun onFailure(call: Call<Release>, t: Throwable) { |
||||
continuation.resume(null) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
companion object { |
||||
private const val TAG = "MyRequest" |
||||
} |
||||
} |
||||
Loading…
Reference in new issue