Created
June 19, 2024 20:50
-
-
Save sajjadyousefnia/36ae4f41fb13fbf72bee80c87ae6c397 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.sands.android.view.activity | |
import android.Manifest | |
import android.annotation.SuppressLint | |
import android.app.DownloadManager | |
import android.content.BroadcastReceiver | |
import android.content.Context | |
import android.content.pm.PackageManager | |
import android.database.Cursor | |
import android.net.Uri | |
import android.os.Build | |
import android.os.Bundle | |
import kotlinx.coroutines.* | |
import android.os.Environment | |
import android.os.Message | |
import android.util.Log | |
import android.widget.Toast | |
import androidx.appcompat.app.AppCompatActivity | |
import androidx.appcompat.app.AppCompatDelegate | |
import androidx.core.content.ContextCompat | |
import androidx.core.net.toUri | |
import androidx.recyclerview.widget.LinearLayoutManager | |
import com.sands.android.App.Companion.DOWNLOADS_SUB_DIRECTORY | |
import com.sands.android.App.Companion.RUNNING_CHECK_TIME | |
import com.sands.android.App.Companion.RUNNING_CHECK_VOLUME | |
import com.sands.android.App.Companion.db | |
import com.sands.android.App.Companion.getDownloadedFileDirectory | |
import com.sands.android.BroadcastReceiver.DownloadReceiver | |
import com.sands.android.Service.DownloadService | |
import com.sands.android.dao.entity.DownloadModel | |
import com.sands.android.databinding.ActivityDownloadBinding | |
import com.sands.android.view.adapter.AdapterHistoryDownload | |
import com.sands.android.view.adapter.AdapterRunningDownload | |
import kotlinx.coroutines.CoroutineScope | |
import kotlinx.coroutines.Dispatchers | |
import kotlinx.coroutines.Job | |
import kotlinx.coroutines.launch | |
import kotlinx.coroutines.withContext | |
import java.util.Calendar | |
import java.math.BigDecimal | |
import java.math.RoundingMode | |
class DownloadActivity : AppCompatActivity() { | |
// 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 runningDownloadList = mutableListOf<DownloadModel>() | |
private var historyDownloadList = mutableListOf<DownloadModel>() | |
private val downloadScope = 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 | |
[email protected] = binder.getService() | |
[email protected]!!.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() | |
} | |
checkScope.launch { | |
checkDownloadsHistory() | |
checkRunningDownloads() | |
} | |
} | |
@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 | |
) { | |
it.isStopped = true | |
withContext(Dispatchers.Main) { | |
runningAdapter.notifyDataSetChanged() | |
} | |
} else if (cursor != null && | |
cursor.moveToFirst() == true && | |
it.isStopped == true | |
) { | |
it.isStopped = false | |
withContext(Dispatchers.Main) { | |
runningAdapter.notifyDataSetChanged() | |
} | |
} | |
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() { | |
} | |
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) | |
} else { | |
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) | |
/* | |
if (currentDownloadModel.downloadId == 0L) { | |
currentDownloadModel.downloadId = downloadId | |
db.appDao() | |
.updateDownloadId(currentDownloadModel.id, currentDownloadModel.downloadId) | |
} | |
*/ | |
val query = DownloadManager.Query().setFilterById(downloadId) | |
var isDownloading = true | |
db.appDao().insertNewDownload(currentDownloadModel) | |
withContext(Dispatchers.Main) { | |
runningAdapter.notifyDataSetChanged() | |
} | |
var lastBytesDownladed = 0L | |
while (isDownloading) { | |
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}" | |
) | |
if (currentDownloadModel.isStopped == true) { | |
currentDownloadModel.isStopped = false | |
db.appDao().checkDownloadStopStatus(currentDownloadModel.id, false) | |
withContext(Dispatchers.Main) { | |
runningAdapter.notifyDataSetChanged() | |
} | |
} | |
} else { | |
Log.d(TAG, "downloadFromScratch: download stopped") | |
if (currentDownloadModel.isStopped == false) { | |
currentDownloadModel.isStopped = true | |
db.appDao().checkDownloadStopStatus(currentDownloadModel.id, true) | |
withContext(Dispatchers.Main) { | |
runningAdapter.notifyDataSetChanged() | |
} | |
} | |
} | |
if (bytesTotal > 0) { | |
val progress = (bytesDownloaded * 100L / bytesTotal).toInt() | |
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() | |
} | |
} 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 fun resumeDownload( | |
url: String, downloadedBytes: Long, downloadModel: DownloadModel | |
) { | |
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager | |
downloadManager.remove(downloadModel.downloadId) | |
val newRequest = DownloadManager.Request(Uri.parse(url)).setTitle(downloadModel.title) | |
.setDescription("انجام ادامه دانلود " + downloadModel.title) | |
.setDestinationInExternalPublicDir( | |
Environment.DIRECTORY_DOWNLOADS, | |
DOWNLOADS_SUB_DIRECTORY + runningDownloadList.last().title | |
).addRequestHeader("Range", "bytes=$downloadedBytes-") | |
downloadManager.enqueue(newRequest) | |
} | |
private suspend fun doOnCompleteDownload(downloadModel: DownloadModel) { | |
downloadModel.endDateTime = getCurrentDateTime() | |
downloadModel.isComplete = true | |
db.appDao().updateFinishedDownload(downloadModel.id, downloadModel.endDateTime) | |
historyDownloadList.add(downloadModel) | |
runningDownloadList.removeAll { it.id == downloadModel.id } | |
withContext(Dispatchers.Main) { | |
runningAdapter.notifyDataSetChanged() | |
historyAdapter.notifyDataSetChanged() | |
} | |
} | |
/** | |
* return false means no video provided to download | |
* */ | |
private fun checkIfHasDownload(): Boolean { | |
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("cover", "") | |
val isNew = intent.extras!!.getBoolean("is_new", true) | |
if (url != "" && title != "" && videoId != 0L && cover != "" && format != "" && isMovie) { | |
addDownloadDataToRunningList(isMovie, url, title, videoId, cover, format, isNew) | |
return true | |
} else { | |
return false | |
} | |
} | |
private fun addDownloadDataToRunningList( | |
isMovie: Boolean, | |
url: String, | |
title: String, | |
videoId: Long, | |
cover: String, | |
format: String, | |
isNew: Boolean | |
) { | |
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 | |
) | |
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