Skip to content

Instantly share code, notes, and snippets.

@sajjadyousefnia
Created June 20, 2024 18:31
Show Gist options
  • Save sajjadyousefnia/40b1ef52855d35bc34c44c5bbf4bae12 to your computer and use it in GitHub Desktop.
Save sajjadyousefnia/40b1ef52855d35bc34c44c5bbf4bae12 to your computer and use it in GitHub Desktop.
// private var currentTitle: String = ""
// private var currentUrl: String = ""
private var periodicJob: Job? = null
private var service: DownloadService? = null
private var bound: Boolean = false
private val TAG = "DownloadActivity"
private lateinit var runningAdapter: AdapterRunningDownload
private lateinit var historyAdapter: AdapterHistoryDownload
private lateinit var downloadCompletedReceiver: BroadcastReceiver
private var isCursorOpen = true
private var runningDownloadList = mutableListOf<DownloadModel>()
private var historyDownloadList = mutableListOf<DownloadModel>()
private val downloadScope = CoroutineScope(Dispatchers.IO)
private val secondDownloadScope = CoroutineScope(Dispatchers.IO)
private val checkScope = CoroutineScope(Dispatchers.IO)
private var lastTime = System.currentTimeMillis()
private var lastDownloadedBytes: Long = 0
/*
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as DownloadService.LocalBinder
this@DownloadActivity.service = binder.getService()
this@DownloadActivity.service!!.updateServiceFromActivity(object :
OnServiceChangeListener {
override fun onDownloadStarted(id: Int) {
}
override fun onDownloadFailed(id: Int, error: String) {
}
override fun onDownloadProgress(id: Int, progress: Int) {
}
override fun onDownloadCompleted(id: Int) {
}
})
bound = true
}
override fun onServiceDisconnected(arg0: ComponentName) {
bound = false
}
}
*/
private lateinit var binding: ActivityDownloadBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
binding = ActivityDownloadBinding.inflate(layoutInflater)
setContentView(binding.root)
downloadCompletedReceiver = DownloadReceiver()
setupAdapters()
if (checkIfHasDownload()) {
downloadScope.launch {
downloadUsingSystemService()
}
startCheckRunningDownloadsInPeriod()
}
downloadScope.launch {
checkRunningDownloads()
}
checkScope.launch {
checkDownloadsHistory()
}
}
@SuppressLint("Range")
private fun startCheckRunningDownloadsInPeriod() {
periodicJob = CoroutineScope(Dispatchers.IO).launch {
while (isActive) { // Check if the coroutine is still active
// Your periodic work
runningDownloadList.forEach {
if (it.isComplete == false) {
val downloadId = it.downloadId
val query = DownloadManager.Query().setFilterById(downloadId)
val downloadManager =
getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val cursor = downloadManager.query(query)
if (cursor != null && cursor.moveToFirst() == false && it.isStopped == false && isCursorOpen) {
it.isStopped = true
withContext(Dispatchers.Main) {
try {
runningAdapter.notifyDataSetChanged()
} catch (e: Exception) {
e.printStackTrace()
}
}
} else if (cursor != null && cursor.moveToFirst() == true && it.isStopped == true && isCursorOpen) {
it.isStopped = false
withContext(Dispatchers.Main) {
try {
runningAdapter.notifyDataSetChanged()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
cursor.close()
}
}
delay(RUNNING_CHECK_TIME) // Delay for milliseconds
}
}
}
private fun setupAdapters() {
runningAdapter = AdapterRunningDownload(this@DownloadActivity, runningDownloadList)
historyAdapter = AdapterHistoryDownload(this@DownloadActivity, historyDownloadList)
binding.rvRunningDownload.adapter = runningAdapter
binding.rvRunningDownload.layoutManager = LinearLayoutManager(this)
binding.rvHistoryDownload.adapter = historyAdapter
binding.rvHistoryDownload.layoutManager = LinearLayoutManager(this)
}
private suspend fun checkRunningDownloads() {
val allDownloads = db.appDao().getUnfinishedDownloadsHistory()
runningDownloadList.addAll(allDownloads)
withContext(Dispatchers.Main) {
historyAdapter.notifyDataSetChanged()
}
val downloadManager = getSystemService(DownloadManager::class.java)
observerAllRunningDownload(downloadManager)
}
private suspend fun checkDownloadsHistory() {
val allDownloads = db.appDao().getCompleteDownloadsHistory()
historyDownloadList.addAll(allDownloads)
withContext(Dispatchers.Main) {
historyAdapter.notifyDataSetChanged()
}
}
/*
override fun onResume() {
super.onResume()
val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(downloadCompletedReceiver, intentFilter, RECEIVER_EXPORTED)
} else {
registerReceiver(downloadCompletedReceiver, intentFilter)
}
}
*/
fun checkStoragePermissions(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
//Android is 11 (R) or above
return Environment.isExternalStorageManager()
} else {
//Below android 11
val write = ContextCompat.checkSelfPermission(
this, Manifest.permission.WRITE_EXTERNAL_STORAGE
)
val read = ContextCompat.checkSelfPermission(
this, Manifest.permission.READ_EXTERNAL_STORAGE
)
return read == PackageManager.PERMISSION_GRANTED && write == PackageManager.PERMISSION_GRANTED
}
}
/*
private fun requestForStoragePermissions() {
//Android is 11 (R) or above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
val intent = Intent()
intent.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
val uri = Uri.fromParts("com.sands.android", this.packageName, null)
intent.setData(uri)
storageActivityResultLauncher.launch(intent)
} catch (e: Exception) {
val intent = Intent()
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
storageActivityResultLauncher.launch(intent)
}
} else {
//Below android 11
ActivityCompat.requestPermissions(
this, arrayOf<String>(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
), REQUEST_CODE_STORAGE_PERMISSION
)
}
}
*/
/*
private val storageActivityResultLauncher: ActivityResultLauncher<Intent> =
registerForActivityResult(ActivityResultContracts.StartActivityForResult(),
object : ActivityResultCallback<ActivityResult> {
override fun onActivityResult(result: ActivityResult) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
//Android is 11 (R) or above
if (Environment.isExternalStorageManager()) {
//Manage External Storage Permissions Granted
Log.d(
TAG, "onActivityResult: Manage External Storage Permissions Granted"
);
downloadScope.launch {
downloadUsingSystemService()
}
} else {
Toast.makeText(
this@DownloadActivity,
"Storage Permissions Denied",
Toast.LENGTH_SHORT
).show();
}
} else {
downloadScope.launch {
downloadUsingSystemService()
} //Below android 11
}
}
})
*/
/*
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_STORAGE_PERMISSION) {
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
// Permission granted
downloadScope.launch {
downloadUsingSystemService()
}
} else {
// Permission denied
}
}
}
*/
private suspend fun checkDownloadExistsDB(): Boolean {
val downloadModel = runningDownloadList.last()
val isExist = db.appDao().checkDownloadExist(downloadModel.url)
return isExist != 0
}
private suspend fun downloadUsingSystemService() {
try {
val downloadModel = runningDownloadList.last()
if (downloadModel.isNew) {
doDownload(downloadModel)
}
// updateDownloadData(downloadModel)
// doDownload(downloadModel)
} catch (e: Exception) {
e.printStackTrace()
}
}
private suspend fun updateDownloadData(downloadModel: DownloadModel) {
val retainedDownloadModel = db.appDao().getDownloadByUrl(downloadModel.url)
downloadModel.downloadedBytes = retainedDownloadModel.downloadedBytes
downloadModel.percentage = retainedDownloadModel.percentage
downloadModel.currentSpeed = retainedDownloadModel.currentSpeed
downloadModel.remaingTime = retainedDownloadModel.remaingTime
downloadModel.isComplete = retainedDownloadModel.isComplete
downloadModel.startDateTime = retainedDownloadModel.startDateTime
}
private fun getCurrentDateTime(): String {
val currentTime: String = Calendar.getInstance().getTime().toString()
return currentTime
}
@SuppressLint("Range")
private suspend fun doDownload(currentDownloadModel: DownloadModel) {
try {
val downloadManager = getSystemService(DownloadManager::class.java)
val request = DownloadManager.Request(currentDownloadModel.url.toUri())
.setMimeType("video/${currentDownloadModel.url.substringAfterLast(".")}")
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI and DownloadManager.Request.NETWORK_MOBILE)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
.setTitle("در حال دانلود فیلم " + currentDownloadModel.title)
.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
getDownloadedFileDirectory(currentDownloadModel)
)
val downloadId = downloadManager.enqueue(request)
currentDownloadModel.downloadId = downloadId
val newId = db.appDao().insertNewDownload(currentDownloadModel)
currentDownloadModel.id = newId
// observerAllRunningDownload(downloadId, currentDownloadModel, downloadManager)
/* val query = DownloadManager.Query().setFilterById(downloadId)
var isDownloading = true
val newId = db.appDao().insertNewDownload(currentDownloadModel)
currentDownloadModel.id = newId
withContext(Dispatchers.Main) {
try {
runningAdapter.notifyDataSetChanged()
} catch (e: Exception) {
e.printStackTrace()
}
}
var lastBytesDownladed = 0L
while (isDownloading) {
isCursorOpen = false
val cursor = downloadManager.query(query)
if (cursor.moveToFirst()) {
val bytesDownloaded = cursor.getLong(
cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
)
val bytesTotal = cursor.getLong(
cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
)
if (lastBytesDownladed == bytesDownloaded || bytesDownloaded == 0L) {
continue
} else {
lastBytesDownladed = bytesDownloaded
}
// if (downloadModel.totalBytes == 0L) {
currentDownloadModel.totalBytes = bytesTotal
// }
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
if (status == DownloadManager.STATUS_SUCCESSFUL) {
isDownloading = false
} else if (status == DownloadManager.STATUS_FAILED) {
val reason =
cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON))
// listener?.onFailed("Download failed: $reason")
} else if (status == DownloadManager.STATUS_PAUSED) {
}
if (status == DownloadManager.STATUS_RUNNING) {
Log.d(
TAG,
"downloadFromScratch: download running bytes Downloaded:${bytesDownloaded} total bytes:${bytesTotal}"
)
currentDownloadModel.isStopped = false
// db.appDao().checkDownloadStopStatus(currentDownloadModel.id, false)
db.appDao().updateDownload(currentDownloadModel)
withContext(Dispatchers.Main) {
try {
runningAdapter.notifyDataSetChanged()
} catch (e: Exception) {
e.printStackTrace()
}
}
} else {
Log.d(TAG, "downloadFromScratch: download stopped")
if (currentDownloadModel.isStopped == false) {
currentDownloadModel.isStopped = true
// db.appDao().checkDownloadStopStatus(currentDownloadModel.id, true)
db.appDao().updateDownload(currentDownloadModel)
withContext(Dispatchers.Main) {
runningAdapter.notifyDataSetChanged()
}
}
}
if (bytesTotal > 0) {
val progress = (bytesDownloaded * 100L / bytesTotal).toInt()
currentDownloadModel.downloadId = downloadId
currentDownloadModel.downloadedBytes = bytesDownloaded
currentDownloadModel.percentage = progress
db.appDao().updateDownload(currentDownloadModel)
// db.appDao().updatePercentageDownload(downloadId, bytesDownloaded, progress)
val downloadItem = runningDownloadList.first()
downloadItem.downloadedBytes = bytesDownloaded.toLong()
downloadItem.percentage = progress
val speed = calculateDownloadSpeed(cursor)
if (speed != -1L && speed != 0L) {
downloadItem.currentSpeed = speed
downloadItem.remaingTime = guessRemainingTimeFinish(
bytesDownloaded, bytesTotal, speed.toLong()
)
}
if (bytesDownloaded == bytesTotal) {
doOnCompleteDownload(downloadItem)
}
// listener?.onProgress(progress)
}
}
cursor.close()
isCursorOpen = true}*/
} catch (e: Exception) {
e.printStackTrace()
}
}
@SuppressLint("Range")
private suspend fun observerAllRunningDownload(
// downloadId: Long, currentDownloadModel: DownloadModel, downloadManager: DownloadManager
downloadManager: DownloadManager
) {
try {
while (true) {
runningDownloadList.filter { it.isComplete == false }
.forEach { currentDownloadModel ->
val query =
DownloadManager.Query().setFilterById(currentDownloadModel.downloadId)
var isDownloading = true
withContext(Dispatchers.Main) {
try {
runningAdapter.notifyDataSetChanged()
} catch (e: Exception) {
e.printStackTrace()
}
}
// var lastBytesDownladed = 0L
isCursorOpen = false
val cursor = downloadManager.query(query)
if (cursor.moveToFirst()) {
val bytesDownloaded = cursor.getLong(
cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
)
val bytesTotal = cursor.getLong(
cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
)
if (currentDownloadModel.downloadedBytes == bytesDownloaded || bytesDownloaded == 0L) {
} else {
currentDownloadModel.downloadedBytes = bytesDownloaded
// if (downloadModel.totalBytes == 0L) {
currentDownloadModel.totalBytes = bytesTotal
// }
val status =
cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
if (status == DownloadManager.STATUS_SUCCESSFUL) {
// isDownloading = false
// currentDownloadModel.isComplete = true
} else if (status == DownloadManager.STATUS_FAILED) {
val reason =
cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON))
// listener?.onFailed("Download failed: $reason")
} else if (status == DownloadManager.STATUS_PAUSED) {
}
if (status == DownloadManager.STATUS_RUNNING) {
Log.d(
TAG,
"downloadFromScratch: download running bytes Downloaded:${bytesDownloaded} total bytes:${bytesTotal}"
)
currentDownloadModel.isStopped = false
// db.appDao().checkDownloadStopStatus(currentDownloadModel.id, false)
db.appDao().updateDownload(currentDownloadModel)
withContext(Dispatchers.Main) {
try {
runningAdapter.notifyDataSetChanged()
} catch (e: Exception) {
e.printStackTrace()
}
}
} else {
Log.d(TAG, "downloadFromScratch: download stopped")
if (currentDownloadModel.isStopped == false) {
currentDownloadModel.isStopped = true
// db.appDao().checkDownloadStopStatus(currentDownloadModel.id, true)
db.appDao().updateDownload(currentDownloadModel)
withContext(Dispatchers.Main) {
runningAdapter.notifyDataSetChanged()
}
}
}
if (bytesTotal > 0) {
val progress = (bytesDownloaded * 100L / bytesTotal).toInt()
currentDownloadModel.downloadedBytes = bytesDownloaded
currentDownloadModel.percentage = progress
db.appDao().updateDownload(currentDownloadModel)
// db.appDao().updatePercentageDownload(downloadId, bytesDownloaded, progress)
// val downloadItem = runningDownloadList.first()
currentDownloadModel.downloadedBytes = bytesDownloaded.toLong()
currentDownloadModel.percentage = progress
val speed = calculateDownloadSpeed(cursor)
if (speed != -1L && speed != 0L) {
currentDownloadModel.currentSpeed = speed
currentDownloadModel.remaingTime = guessRemainingTimeFinish(
bytesDownloaded, bytesTotal, speed.toLong()
)
}
if (bytesDownloaded == bytesTotal) {
doOnCompleteDownload(currentDownloadModel)
}
// listener?.onProgress(progress)
}
}
cursor.close()
isCursorOpen = true
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
/*
@SuppressLint("Range")
private suspend fun continueExistingDownload() {
try {
val currentDownloadModel = runningDownloadList.last()
val retainedDownloadModel = db.appDao().getDownloadByUrl(currentDownloadModel.url)
updateDownloadModel(currentDownloadModel, retainedDownloadModel)
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val query = DownloadManager.Query().setFilterById(currentDownloadModel.downloadId)
val cursor = downloadManager.query(query)
if (cursor != null && cursor.moveToFirst()) {
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
if (status == DownloadManager.STATUS_PAUSED) {
val url = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI))
val downloadedBytes =
cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
resumeDownload(url, downloadedBytes, currentDownloadModel)
}
cursor.close()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
*/
private suspend fun doOnCompleteDownload(downloadModel: DownloadModel) {
downloadModel.endDateTime = getCurrentDateTime()
downloadModel.isComplete = true
downloadModel.percentage = 100
downloadModel.isStopped = true
// db.appDao().updateFinishedDownload(downloadModel.id, downloadModel.endDateTime)
db.appDao().updateFinishedDownload(downloadModel.id, getCurrentDateTime())
withContext(Dispatchers.Main) {
historyDownloadList.add(downloadModel)
runningDownloadList.removeAll { it.id == downloadModel.id }
try {
runningAdapter.notifyDataSetChanged()
} catch (e: Exception) {
e.printStackTrace()
}
try {
historyAdapter.notifyDataSetChanged()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
/**
* return false means no video provided to download
* */
private fun checkIfHasDownload(): Boolean {
try {
val url = intent.extras!!.getString(
"url", ""
)
val title = intent.extras!!.getString("title", "")
val videoId = intent.extras!!.getLong("vid_id", 0L)
val isMovie = intent.extras!!.getBoolean("is_movie", true)
val cover = intent.extras!!.getString("cover", "")
val format = intent.extras!!.getString("format", "")
val isNew = intent.extras!!.getBoolean("is_new", true)
val year = intent.extras!!.getInt("year", 0)
val imdb = intent.extras!!.getString("imdb", "")
if (url != "" && title != "" && videoId != 0L && cover != "" && format != "" && isMovie) {
addDownloadDataToRunningList(
isMovie, url, title, videoId, cover, format, isNew, year, imdb
)
return true
} else {
return false
}
} catch (e: Exception) {
e.printStackTrace()
return false
}
}
private fun addDownloadDataToRunningList(
isMovie: Boolean,
url: String,
title: String,
videoId: Long,
cover: String,
format: String,
isNew: Boolean,
year: Int,
imdb: String
) {
val downloadModel = DownloadModel(
isMovie = isMovie,
videoId = videoId,
cover = cover,
url = url,
title = title,
downloadedBytes = 0,
downloadId = 0,
directory = Environment.DIRECTORY_DOWNLOADS + DOWNLOADS_SUB_DIRECTORY + title,
percentage = 0,
startDateTime = getCurrentDateTime(),
totalBytes = 0,
currentSpeed = 0L,
remaingTime = "",
fileName = title + getCurrentDateTime(),
isComplete = false,
format = format,
isNew = isNew,
year = year,
imdb = imdb
)
runningDownloadList.add(downloadModel)
}
// Method to send messages to the service
fun sendMessageToService(msg: String) {
if (bound) {
val message = Message.obtain(null, 0, 0, 0)
message.data = Bundle().apply {
putString("msg", msg)
}
service?.LocalBinder()?.getMessenger()?.send(message)
}
}
override fun onDestroy() {
super.onDestroy()
if (periodicJob != null && periodicJob?.isActive == true) {
periodicJob!!.cancel()
}
}
/*
override fun onDestroy() {
super.onDestroy()
unbindService(connection)
bound = false
}
*/
// Method to handle messages from the service
fun handleServiceMessage(msg: String?) {
// Update the UI or perform necessary actions based on the message
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
@SuppressLint("Range")
private suspend fun calculateDownloadSpeed(cursor: Cursor): Long {
// var speedInKbps = "_"
var speedInBytes = -1L
val downloadedBytes =
cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
if (downloadedBytes == 0L) {
Log.e(TAG, "calculateDownloadSpeed: downloadedBytes is zero")
return -1
}
val currentTime = System.currentTimeMillis()
val deltaTime = currentTime - lastTime
val deltaBytes = downloadedBytes - lastDownloadedBytes
if (deltaTime > 0) {
// val speed = (deltaBytes * 1000) / deltaTime // bytes per second
// speedInKbps = (speed / 1024).toString() // convert to Kbps
// speedInBites = (deltaBytes / deltaTime).toString()
speedInBytes = (BigDecimal(deltaBytes * 10).divide(
BigDecimal(deltaTime), 2, RoundingMode.HALF_EVEN
)).toLong()
}
lastTime = currentTime
lastDownloadedBytes = downloadedBytes
// return speedInKbps
return speedInBytes
}
/*
private fun notifyRunningItemChanged(downloadModel: DownloadModel) {
val index = runningDownloadList.indexOf(downloadModel)
if (index != -1) {
runningAdapter.notifyItemChanged(index)
}
}
*/
/*
private fun notifyRunningItemAdded(downloadModel: DownloadModel) {
val index = runningDownloadList.indexOf(downloadModel)
if (index != -1) {
runningAdapter.notifyItemInserted(index)
}
}
*/
/*
private fun notifyRunningItemRemoved(downloadModel: DownloadModel) {
val index = runningDownloadList.indexOf(downloadModel)
if (index != -1) {
runningAdapter.notifyItemRemoved(index)
}
}
*/
private fun guessRemainingTimeFinish(
bytesDownloaded: Long, bytesTotal: Long, speed: Long
): String {
var time = "_"
try {
val remainingBytes = bytesTotal - bytesDownloaded
val remainingTimeSeconds = (remainingBytes / speed)
val hours = remainingTimeSeconds.toInt() / 3600
val minutes = (remainingTimeSeconds.toInt() % 3600) / 60
val seconds = remainingTimeSeconds.toInt() % 60
if (hours > 0) {
time += "$hours ساعت "
}
if (minutes > 0) {
time += " $minutes دقیقه "
}
if (seconds > 0) {
time += " $seconds ثانیه "
}
time = time.replace("_", "")
} catch (e: Exception) {
e.printStackTrace()
}
return time
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment