app_check/android/src/main/kotlin/dev/yashgarg/appcheck/AppcheckPlugin.kt

224 lines
8.9 KiB
Kotlin

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<String>("uri").toString()
checkAvailability(uriSchema, result)
}
"getInstalledApps" -> {
val startTime = System.currentTimeMillis()
// Memanggil fungsi yang sekarang mengembalikan Pair: List<Map> dan durasi PM
val (apps, pmDuration) = getInstalledNonSystemApps()
val endTime = System.currentTimeMillis()
val totalDuration = endTime - startTime
val resultMap = mutableMapOf<String, Any>()
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<String>("uri").toString()
isAppEnabled(uriSchema, result)
}
"launchApp" -> {
uriSchema = call.argument<String>("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<MutableList<Map<String, Any>>, Long>
// untuk mengembalikan daftar aplikasi DAN durasi operasi PackageManager.
private fun getInstalledNonSystemApps(): Pair<MutableList<Map<String, Any>>, 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<Map<String, Any>> = 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<String, Any> {
val app: MutableMap<String, Any> = 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<String>()
// 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)
}
}