224 lines
8.9 KiB
Kotlin
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)
|
|
}
|
|
} |