package dev.yashgarg.appcheck import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.NameNotFoundException import android.util.Log import androidx.annotation.NonNull import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.* import kotlin.collections.* import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES.P import android.os.Build // Import yang diperlukan untuk sertifikat dan hashing import java.security.MessageDigest import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.io.ByteArrayInputStream import javax.security.auth.x500.X500Principal /** AppcheckPlugin */ class AppcheckPlugin : FlutterPlugin, MethodCallHandler { private lateinit var channel: MethodChannel private lateinit var context: Context override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "dev.yashgarg/appcheck") channel.setMethodCallHandler(this) context = flutterPluginBinding.applicationContext } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { val uriSchema: String when (call.method) { "checkAvailability" -> { uriSchema = call.argument("uri").toString() checkAvailability(uriSchema, result) } "getInstalledApps" -> { val startTime = System.currentTimeMillis() // Memanggil fungsi yang sekarang mengembalikan Pair: List dan durasi PM val (apps, pmDuration) = getInstalledNonSystemApps() val endTime = System.currentTimeMillis() val totalDuration = endTime - startTime val resultMap = mutableMapOf() resultMap["apps"] = apps resultMap["duration_ms"] = totalDuration resultMap["package_manager_duration_ms"] = pmDuration // Tambahkan durasi PackageManager di sini result.success(resultMap) } "isAppEnabled" -> { uriSchema = call.argument("uri").toString() isAppEnabled(uriSchema, result) } "launchApp" -> { uriSchema = call.argument("uri").toString() launchApp(uriSchema, result) } else -> result.notImplemented() } } private fun checkAvailability(uri: String, result: Result) { val info = getAppPackageInfo(uri) if (info != null) { result.success(convertPackageInfoToJson(info)) return } result.error("400", "App not found $uri", null) } // Mengubah tipe return menjadi Pair>, Long> // untuk mengembalikan daftar aplikasi DAN durasi operasi PackageManager. private fun getInstalledNonSystemApps(): Pair>, Long> { val packageManager: PackageManager = context.packageManager val flags = PackageManager.GET_SIGNING_CERTIFICATES or PackageManager.GET_PERMISSIONS val pmCallStartTime = System.currentTimeMillis() val packages = packageManager.getInstalledPackages(flags) val pmCallEndTime = System.currentTimeMillis() val pmDuration = pmCallEndTime - pmCallStartTime // Durasi spesifik PackageManager.getInstalledPackages val installedApps: MutableList> = ArrayList() for (pkg in packages) { if ((pkg.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 0) { val map = convertPackageInfoToJson(pkg) installedApps.add(map) } } return Pair(installedApps, pmDuration) // Mengembalikan Pair } private fun getAppPackageInfo(uri: String): PackageInfo? { val pm = context.packageManager try { val flags = PackageManager.GET_ACTIVITIES or PackageManager.GET_SIGNING_CERTIFICATES or PackageManager.GET_PERMISSIONS return pm.getPackageInfo(uri, flags) } catch (e: NameNotFoundException) { e.message?.let { Log.e("getAppPackageInfo ($uri)", it) } } return null } private fun convertPackageInfoToJson(info: PackageInfo): Map { val app: MutableMap = HashMap() val appInfo = info.applicationInfo app["app_name"] = appInfo?.loadLabel(context.packageManager)?.toString() ?: "N/A" app["system_app"] = (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0 app["package_name"] = info.packageName app["version_name"] = info.versionName ?: "N/A" // app["version_code"] = getVersionCode(info) app["installer"] = context.packageManager.getInstallerPackageName(info.packageName) ?: "unknown" // app["source_dir"] = appInfo.sourceDir val apkFile = java.io.File(appInfo.sourceDir) // app["apk_size_bytes"] = apkFile.length() // app["permissions"] = info.requestedPermissions?.toList() ?: listOf() // try { // val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // info.signingInfo?.apkContentsSigners ?: emptyArray() // } else { // @Suppress("DEPRECATION") // info.signatures ?: emptyArray() // } // if (signatures.isNotEmpty()) { // // SHA-256 // val md = MessageDigest.getInstance("SHA-256") // val sha = md.digest(signatures[0].toByteArray()) // val hex = sha.joinToString(":") { "%02X".format(it) } // app["signature_sha256"] = hex // val certFactory = CertificateFactory.getInstance("X.509") // val cert = certFactory.generateCertificate( // ByteArrayInputStream(signatures[0].toByteArray()) // ) as X509Certificate // val x500Principal = cert.subjectX500Principal // var developerName = "unknown" // val subjectDN = x500Principal.name // val parts = subjectDN.split(",").map { it.trim() } // for (part in parts) { // if (part.startsWith("O=", ignoreCase = true)) { // developerName = part.substring(2).trim() // break // } else if (part.startsWith("CN=", ignoreCase = true)) { // developerName = part.substring(3).trim() // } // } // if (developerName == "unknown" && subjectDN.isNotEmpty()) { // developerName = subjectDN // } // app["developer"] = developerName // } else { // app["signature_sha256"] = "N/A" // app["developer"] = "unknown" // } // } catch (e: Exception) { // Log.e("AppCheckPlugin", "Error getting signature/developer info: ${e.message}") // app["signature_sha256"] = "error" // app["developer"] = "error" // } return app } @Suppress("DEPRECATION") private fun getVersionCode(packageInfo: PackageInfo): Long { return if (SDK_INT < P) packageInfo.versionCode.toLong() else packageInfo.longVersionCode } private fun isAppEnabled(packageName: String, result: Result) { val appStatus: Boolean try { val appInfo: ApplicationInfo = context.packageManager.getApplicationInfo(packageName, 0) appStatus = appInfo.enabled } catch (e: NameNotFoundException) { result.error("400", "${e.message} $packageName", e) return } result.success(appStatus) } private fun launchApp(packageName: String, result: Result) { val info = getAppPackageInfo(packageName) if (info != null) { val launchIntent: Intent? = context.packageManager.getLaunchIntentForPackage(packageName) if (launchIntent != null) { launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(launchIntent) result.success(null) return } } result.error("400", "App not found $packageName", null) } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) } }