diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/helper/UpdateManagerHelper.kt b/app/src/main/java/com/github/droidworksstudio/launcher/helper/UpdateManagerHelper.kt new file mode 100644 index 0000000..9466f08 --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/launcher/helper/UpdateManagerHelper.kt @@ -0,0 +1,173 @@ +package com.github.droidworksstudio.launcher.helper + +import android.app.DownloadManager +import android.content.* +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.Settings +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.core.content.FileProvider +import androidx.fragment.app.Fragment +import com.github.droidworksstudio.common.showLongToast +import com.github.droidworksstudio.launcher.BuildConfig +import com.github.droidworksstudio.launcher.utils.Constants +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.IOException + +class UpdateManagerHelper(private val fragment: Fragment) { + private val sharedPreferences by lazy { + fragment.requireContext().getSharedPreferences("update_prefs", Context.MODE_PRIVATE) + } + + private val context = fragment.requireContext() + private val activity = fragment.requireActivity() + + fun checkForUpdates() { + val currentVersion = BuildConfig.VERSION_NAME + val url = "https://api.github.com/repos/DroidWorksStudio/EasyLauncher/releases/latest" + + val request = Request.Builder().url(url).build() + OkHttpClient().newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + // Handle the error + Log.e("UpdateManager", "Failed to check for updates", e) + } + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + val json = response.body()?.string() + val jsonObject = JSONObject(json.toString()) + val tagName = jsonObject.getString("tag_name") + val latestVersion = tagName.replace("v", "") + val assets = jsonObject.getJSONArray("assets") + val apkUrl = (assets.get(1) as JSONObject).getString("browser_download_url") + + Log.d("UpdateManager", "Latest version: $latestVersion | Current version: $currentVersion") + + if (latestVersion > currentVersion) { + val declinedVersion = sharedPreferences.getString("declined_version", "") + + Log.d("UpdateManager", "Declined version: $declinedVersion") + + if (latestVersion != declinedVersion) { + // Ask the user if they want to update + activity.runOnUiThread { + showUpdateDialog(latestVersion, apkUrl) + } + } + } + } + } + }) + } + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + private fun showUpdateDialog(latestVersion: String, apkUrl: String) { + MaterialAlertDialogBuilder(context).apply { + setTitle("Update Available") + setMessage("A new version of the app is available. Do you want to update?") + setPositiveButton("Update") { _, _ -> + downloadApk(apkUrl) + } + setNegativeButton("Later") { _, _ -> + // Save the declined version + with(sharedPreferences.edit()) { + putString("declined_version", latestVersion) + apply() + } + } + setCancelable(false) + show() + } + } + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + private fun downloadApk(apkUrl: String) { + val request = DownloadManager.Request(Uri.parse(apkUrl)).apply { + setTitle("Downloading update") + setDescription("Your app is downloading the latest update") + setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "${Constants.PACKAGE_NAME}.apk") + } + + val manager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val downloadId = manager.enqueue(request) + + // Register a BroadcastReceiver to listen for completion of the download + val onComplete = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.action + if (DownloadManager.ACTION_DOWNLOAD_COMPLETE == action) { + val query = DownloadManager.Query().setFilterById(downloadId) + val cursor = manager.query(query) + if (cursor != null && cursor.moveToFirst()) { + val statusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS) + if (statusIndex != -1) { + val status = cursor.getInt(statusIndex) + if (status == DownloadManager.STATUS_SUCCESSFUL) { + // Download completed successfully, now call installApk() + requestInstallPermission() + } + } + } + cursor?.close() + } + } + } + + context.registerReceiver(onComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_NOT_EXPORTED) + } + + private fun requestInstallPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (!context.packageManager.canRequestPackageInstalls()) { + val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).apply { + data = Uri.parse("package:${context.packageName}") + } + @Suppress("DEPRECATION") + fragment.startActivityForResult(intent, Constants.REQUEST_INSTALL_PERMISSION) + } else { + // Permission already granted, proceed with installation + installApk() + } + } else { + // For devices below Android Oreo, installation permission is granted by default + installApk() + } + } + + private fun installApk() { + val apkFile = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "${Constants.PACKAGE_NAME}.apk") + val apkUri = FileProvider.getUriForFile(context.applicationContext, "${context.packageName}.provider", apkFile) + + val intent = Intent(Intent.ACTION_VIEW).apply { + setDataAndType(apkUri, "application/vnd.android.package-archive") + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + context.startActivity(intent) + } + + @Deprecated("Deprecated in Java") + fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == Constants.REQUEST_INSTALL_PERMISSION) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val canInstallPackages = context.packageManager.canRequestPackageInstalls() + if (canInstallPackages) { + // Permission granted, proceed with installation + installApk() + } else { + // Permission still not granted, handle accordingly + context.applicationContext.showLongToast("Please allow install permission to install.") + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt index ebeb4ae..adca0c5 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt @@ -2,24 +2,17 @@ package com.github.droidworksstudio.launcher.ui.activities import android.Manifest import android.annotation.SuppressLint -import android.app.DownloadManager -import android.content.BroadcastReceiver import android.content.Context -import android.content.Intent -import android.content.IntentFilter import android.content.SharedPreferences import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.location.Location import android.location.LocationListener import android.location.LocationManager -import android.net.Uri import android.os.Build import android.os.Bundle -import android.os.Environment import android.os.Handler import android.os.Looper -import android.provider.Settings import android.view.Menu import android.view.MenuItem import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS @@ -29,7 +22,6 @@ import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat -import androidx.core.content.FileProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.findNavController @@ -38,8 +30,6 @@ import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.navigateUp import com.github.droidworksstudio.common.hasInternetPermission import com.github.droidworksstudio.common.isTablet -import com.github.droidworksstudio.common.showLongToast -import com.github.droidworksstudio.launcher.BuildConfig import com.github.droidworksstudio.launcher.R import com.github.droidworksstudio.launcher.databinding.ActivityMainBinding import com.github.droidworksstudio.launcher.helper.AppHelper @@ -47,17 +37,8 @@ import com.github.droidworksstudio.launcher.helper.PreferenceHelper import com.github.droidworksstudio.launcher.utils.Constants import com.github.droidworksstudio.launcher.viewmodel.AppViewModel import com.github.droidworksstudio.launcher.viewmodel.PreferenceViewModel -import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch -import okhttp3.Call -import okhttp3.Callback -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.json.JSONObject -import java.io.File -import java.io.IOException import javax.inject.Inject @@ -130,7 +111,6 @@ class MainActivity : AppCompatActivity(), LocationListener { } handler.removeCallbacks(saveLocationRunnable) } - } override fun onDestroy() { @@ -282,7 +262,6 @@ class MainActivity : AppCompatActivity(), LocationListener { backToHomeScreen() if (applicationContext.hasInternetPermission()) { checkLocationPermission() - checkForUpdates() } setupDataBase() observeUI() @@ -311,147 +290,4 @@ class MainActivity : AppCompatActivity(), LocationListener { if (navController.currentDestination?.id != R.id.HomeFragment) navController.navigate(R.id.HomeFragment) } - - private fun checkForUpdates() { - val currentVersion = BuildConfig.VERSION_NAME - val url = "https://api.github.com/repos/DroidWorksStudio/EasyLauncher/releases/latest" - - val request = Request.Builder().url(url).build() - OkHttpClient().newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - // Handle the error - } - - @SuppressLint("NewApi") - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - val json = response.body()?.string() - val jsonObject = JSONObject(json.toString()) - val tagName = jsonObject.getString("tag_name") - val latestVersion = tagName.replace("v", "") - val assets = jsonObject.getJSONArray("assets") - val apkUrl = (assets.get(1) as JSONObject).getString("browser_download_url") - - if (latestVersion > currentVersion) { - val sharedPreferences = getSharedPreferences("update_prefs", Context.MODE_PRIVATE) - val declinedVersion = sharedPreferences.getString("declined_version", "") - - if (latestVersion != declinedVersion) { - // Ask the user if they want to update - runOnUiThread { - showUpdateDialog(latestVersion, apkUrl) - } - } - } - } - } - }) - } - - @SuppressLint("NewApi") - fun showUpdateDialog(latestVersion: String, apkUrl: String) { - MaterialAlertDialogBuilder(this).apply { - setTitle("Update Available") - setMessage("A new version of the app is available. Do you want to update?") - setPositiveButton("Update") { _, _ -> - downloadApk(apkUrl) - } - setNegativeButton("Later") { _, _ -> - // Save the declined version - val sharedPreferences = getSharedPreferences("update_prefs", Context.MODE_PRIVATE) - with(sharedPreferences.edit()) { - putString("declined_version", latestVersion) - apply() - } - } - setCancelable(false) - show() - } - } - - @SuppressLint("NewApi") - private fun downloadApk(apkUrl: String) { - val request = DownloadManager.Request(Uri.parse(apkUrl)).apply { - setTitle("Downloading update") - setDescription("Your app is downloading the latest update") - setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) - setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "${Constants.PACKAGE_NAME}.apk") - } - - val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - val downloadId = manager.enqueue(request) - - // Register a BroadcastReceiver to listen for completion of the download - val onComplete = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val action = intent.action - if (DownloadManager.ACTION_DOWNLOAD_COMPLETE == action) { - val query = DownloadManager.Query().setFilterById(downloadId) - val cursor = manager.query(query) - if (cursor != null && cursor.moveToFirst()) { - val statusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS) - if (statusIndex != -1) { - val status = cursor.getInt(statusIndex) - if (status == DownloadManager.STATUS_SUCCESSFUL) { - // Download completed successfully, now call installApk() - requestInstallPermission() - } - } - } - cursor?.close() - } - } - } - - registerReceiver(onComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), RECEIVER_EXPORTED) - } - - private fun requestInstallPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (!packageManager.canRequestPackageInstalls()) { - val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).apply { - data = Uri.parse("package:$packageName") - } - @Suppress("DEPRECATION") - startActivityForResult(intent, Constants.REQUEST_INSTALL_PERMISSION) - return - } else { - // Permission already granted, proceed with installation - installApk() - } - } else { - // For devices below Android Oreo, installation permission is granted by default - installApk() - } - } - - private fun installApk() { - val apkFile = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "${Constants.PACKAGE_NAME}.apk") - val apkUri = FileProvider.getUriForFile(applicationContext, "$packageName.provider", apkFile) - - val intent = Intent(Intent.ACTION_VIEW).apply { - setDataAndType(apkUri, "application/vnd.android.package-archive") - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - - startActivity(intent) - } - - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == Constants.REQUEST_INSTALL_PERMISSION) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val canInstallPackages = packageManager.canRequestPackageInstalls() - if (canInstallPackages) { - // Permission granted, proceed with installation - installApk() - } else { - // Permission still not granted, handle accordingly - applicationContext.showLongToast("Please allow install permission to install.") - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/home/HomeFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/home/HomeFragment.kt index ce0d9c2..af52952 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/home/HomeFragment.kt @@ -37,6 +37,7 @@ import com.github.droidworksstudio.launcher.databinding.FragmentHomeBinding import com.github.droidworksstudio.launcher.helper.AppHelper import com.github.droidworksstudio.launcher.helper.FingerprintHelper import com.github.droidworksstudio.launcher.helper.PreferenceHelper +import com.github.droidworksstudio.launcher.helper.UpdateManagerHelper import com.github.droidworksstudio.launcher.listener.OnItemClickedListener import com.github.droidworksstudio.launcher.listener.OnSwipeTouchListener import com.github.droidworksstudio.launcher.listener.ScrollEventListener @@ -73,6 +74,7 @@ class HomeFragment : Fragment(), @Inject lateinit var appHelper: AppHelper + @Inject lateinit var fingerHelper: FingerprintHelper @@ -83,6 +85,7 @@ class HomeFragment : Fragment(), private lateinit var batteryReceiver: BroadcastReceiver private lateinit var biometricPrompt: BiometricPrompt + private lateinit var updateManager: UpdateManagerHelper private lateinit var context: Context @@ -106,6 +109,9 @@ class HomeFragment : Fragment(), setupRecyclerView() observeSwipeTouchListener() observeUserInterfaceSettings() + + updateManager = UpdateManagerHelper(this) + updateManager.checkForUpdates() } @SuppressLint("ClickableViewAccessibility") @@ -479,6 +485,8 @@ class HomeFragment : Fragment(), binding.nestScrollView.hideKeyboard() observeUserInterfaceSettings() observeFavoriteAppList() + + updateManager.checkForUpdates() } override fun onAppClicked(appInfo: AppInfo) {