diff --git a/platformtools/appmanager/src/androidMain/AndroidManifest.xml b/platformtools/appmanager/src/androidMain/AndroidManifest.xml index 0e482ce..00622ed 100644 --- a/platformtools/appmanager/src/androidMain/AndroidManifest.xml +++ b/platformtools/appmanager/src/androidMain/AndroidManifest.xml @@ -5,7 +5,9 @@ - + Unit, @@ -60,47 +68,119 @@ class ApkInstallerAndroid : AppInstaller { if (!canRequestInstallPackages()) { // 2) Request permission if necessary requestInstallPackagesPermission() + + // Optional: Wait for the user to grant permission + // You can implement logic to wait or inform the user + // and return or suspend until the permission is granted. + // For simplicity, we continue here. } - // 3) Recheck after the request: if still not allowed, exit + // 3) Re-check after the request: if still not allowed, exit if (!canRequestInstallPackages()) { onResult(false, "Installation from unknown sources is not allowed.") return } - // 4) If permission is granted, proceed with the installation try { - val intent = Intent(Intent.ACTION_VIEW).apply { - // Open the APK installer in a new task - flags = Intent.FLAG_ACTIVITY_NEW_TASK + // Use a suspended coroutine to wait for the installation result + suspendCancellableCoroutine { continuation -> + val packageManager = context.packageManager - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - // From Nougat (API 24), a FileProvider must be used - // and permissions must be granted to read the URI - val apkUri: Uri = FileProvider.getUriForFile( - context, "${context.packageName}.fileprovider", appFile - ) - setDataAndType(apkUri, "application/vnd.android.package-archive") - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } else { - // Before Nougat, Uri.fromFile can still be used - setDataAndType(Uri.fromFile(appFile), "application/vnd.android.package-archive") + // Extract the package name from the APK file + val packageInfo = packageManager.getPackageArchiveInfo(appFile.absolutePath, 0) + val targetPackageName = packageInfo?.packageName + + if (targetPackageName == null) { + onResult(false, "Failed to extract package name from APK file.") + continuation.resume(Unit) + return@suspendCancellableCoroutine } - } - // Launch the intent - context.startActivity(intent) + // Retrieve current package information for the target package before installation + val currentPackageInfo = try { + packageManager.getPackageInfo(targetPackageName, 0) + } catch (e: PackageManager.NameNotFoundException) { + null + } + + // Retrieve the current versionCode or installation date + val currentVersionCode = currentPackageInfo?.versionCode ?: -1 + val currentInstallTime = currentPackageInfo?.firstInstallTime ?: -1 + + // Create the intent to launch the APK installation + val installIntent = Intent(Intent.ACTION_VIEW).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + // From Nougat (API 24) onwards, use FileProvider + val apkUri: Uri = FileProvider.getUriForFile( + context, "${context.packageName}.fileprovider", appFile + ) + setDataAndType(apkUri, "application/vnd.android.package-archive") + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } else { + // Before Nougat, use Uri.fromFile + setDataAndType(Uri.fromFile(appFile), "application/vnd.android.package-archive") + } + } + + try { + // Launch the installation intent + context.startActivity(installIntent) - // Inform the caller that the installation has started - onResult(true, "Installation started.") + // Wait for a short moment to allow the installation to start + Handler(Looper.getMainLooper()).postDelayed({ + // Check if the app has been installed or updated + val updatedPackageInfo = try { + packageManager.getPackageInfo(targetPackageName, 0) + } catch (e: PackageManager.NameNotFoundException) { + null + } + + if (updatedPackageInfo != null) { + // Compare the versionCode or installation date + val isUpdated = when { + // If the versionCode has increased, it's a successful update + updatedPackageInfo.versionCode > currentVersionCode -> true + // If the installation date has changed, it's a successful update + updatedPackageInfo.firstInstallTime > currentInstallTime -> true + // Otherwise, the installation/update failed + else -> false + } + + if (isUpdated) { + onResult(true, "Installation/update successful.") + } else { + onResult(false, "The application was not updated.") + } + } else { + onResult(false, "The application was not installed.") + } + + // Resume the coroutine + continuation.resume(Unit) + }, 5000) // Wait 5 seconds before checking + + } catch (e: Exception) { + // Handle errors during the installation intent launch + onResult(false, "Failed to start installation: ${e.message}") + continuation.resumeWithException(e) + } + + // Handle coroutine cancellation + continuation.invokeOnCancellation { + // Clean up if necessary + } + } } catch (e: Exception) { - // In case of an unexpected error e.printStackTrace() - onResult(false, "Failed to start installation: ${e.message}") + onResult(false, "Installation failed: ${e.message}") } } + + /** * Installs an APK file silently without user interaction. *