DevCheck-lib/mydev/src/main/java/com/xyzshell/mydev/utils/DeviceInfo.kt
2025-10-20 09:54:56 +08:00

743 lines
26 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.xyzshell.mydev.utils
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.pm.Signature
import android.hardware.Sensor
import android.hardware.SensorManager
import android.media.AudioManager
import android.media.MediaCodecInfo
import android.media.MediaCodecList
import android.net.ConnectivityManager
import android.os.Build
import android.os.SystemClock
import android.os.ext.SdkExtensions
import android.provider.Settings
import android.telephony.TelephonyManager
import android.webkit.WebSettings
import androidx.core.app.ActivityCompat
import com.xyzshell.mydev.InfoItem
import org.json.JSONObject
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileInputStream
import java.io.RandomAccessFile
import java.security.MessageDigest
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.regex.Matcher
import java.util.regex.Pattern
import javax.security.auth.x500.X500Principal
typealias Callback = () -> Unit
class DeviceInfo {
private var advertisingTrackingId:String = ""
private var limitedAdTracking:Boolean = false
private var adInit = false
private var adCallback: Callback? = null
private var context:Context? = null
private var apk_digest_timeout:Long = 0
private var apk_digest_size:Long = 0
private var apk_digest:String = ""
private var apk_digest_init:Boolean = false
private val DEBUG_CERT = X500Principal("CN=Android Debug,O=Android,C=US")
private object SingletonHolder {
@SuppressLint("StaticFieldLeak")
val holder = DeviceInfo()
}
companion object {
@SuppressLint("StaticFieldLeak")
val instance = SingletonHolder.holder
}
val AdvertisingTrackingId get() = advertisingTrackingId
val LimitedAdTracking get() = limitedAdTracking
val ApiLevel get() = Build.VERSION.SDK_INT
val ApkDigest get() = apk_digest
val ApkDigestSize get() = apk_digest_size
val ApkDigestTimeout get() = apk_digest_timeout
fun init(ctx:Context) {
this.context = ctx
Executors.newSingleThreadExecutor().execute {
try {
AdvertisingId.init(ctx)
advertisingTrackingId = AdvertisingId.getAdvertisingTrackingId()
limitedAdTracking = AdvertisingId.getLimitedAdTracking()
adInit = true
adCallback?.let { it() }
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun onAdInit(callback: Callback) {
if(adInit) {
callback()
}
this.adCallback = callback
}
fun getApkDigest() {
if(this.context == null) {
return
}
try {
val startTime = System.nanoTime()
val path: String = this.context!!.packageCodePath
val file = File(path)
apk_digest_size = file.length()
val fileInputStream = FileInputStream(file)
apk_digest = Utilities.sha256(fileInputStream)
fileInputStream.close()
val l4 = System.nanoTime() - startTime
val timeUnit: TimeUnit = TimeUnit.NANOSECONDS
apk_digest_timeout= timeUnit.toMillis(l4)
apk_digest_init = true
} catch (e:Throwable) {
e.printStackTrace()
}
}
fun getAuid(): String {
if(this.context == null) {
return ""
}
val sharedPreferences = this.context!!.getSharedPreferences("supersonic_shared_preferen", Context.MODE_PRIVATE)
return sharedPreferences.getString("auid", "") ?: ""
}
fun getBatteryStatus():Int {
val intentFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
val intent = context!!.registerReceiver(null, intentFilter)
if (intent != null) {
return intent.getIntExtra("status", -1)
}
return -1
}
fun getBoard(): String {
return Build.BOARD
}
fun getBootloader(): String {
return Build.BOOTLOADER
}
fun getBrand(): String {
return Build.BRAND
}
fun getBuildId(): String {
return Build.ID
}
fun getBuildVersionIncremental(): String {
return Build.VERSION.INCREMENTAL
}
fun getCPUCount(): Long {
return Runtime.getRuntime().availableProcessors().toLong()
}
fun getCertificateFingerprint(): String {
val packageManager = this.context?.packageManager
val packageName = this.context?.packageName ?: return ""
try {
val packageInfo = packageManager?.getPackageInfo(packageName, 64)
val arrayOfSignature: Array<Signature>? = packageInfo!!.signatures
if (!arrayOfSignature.isNullOrEmpty()) {
val certificateFactory = CertificateFactory.getInstance("X.509")
val byteArrayInputStream = ByteArrayInputStream(arrayOfSignature[0].toByteArray())
val certificate = certificateFactory.generateCertificate(byteArrayInputStream)
val messageDigest = MessageDigest.getInstance("SHA-1")
var arrayOfByte2 = certificate.encoded
arrayOfByte2 = messageDigest.digest(arrayOfByte2)
return Utilities.toHexString(arrayOfByte2)
}
} catch (e:Throwable) {
e.printStackTrace()
}
return ""
}
fun getDevice(): String {
return Build.DEVICE
}
fun getFingerprint(): String {
return Build.FINGERPRINT
}
fun getModel(): String {
return Build.MODEL
}
fun getOsVersion(): String {
return Build.VERSION.RELEASE
}
fun getAppName() : String{
return context?.packageName ?: ""
}
fun getProduct(): String {
return Build.PRODUCT
}
fun getManufacturer(): String {
return Build.MANUFACTURER
}
fun getProcessInfo():Map<Any,Any> {
val map = mutableMapOf<Any,Any>()
val randomAccessFile1 = RandomAccessFile("/proc/self/stat","r")
randomAccessFile1.use {
map.put("stat", randomAccessFile1.readLine())
}
return map.toMap()
}
fun getRingerMode():Int {
if(this.context == null) {
return -1
}
val audioManager = this.context!!.getSystemService(Context.AUDIO_SERVICE) as android.media.AudioManager
return audioManager.ringerMode
}
fun getScreenBrightness():Int {
if(this.context == null) {
return -1
}
return Settings.System.getInt(this.context!!.contentResolver, "screen_brightness", -1)
}
fun getScreenDensity():Int {
return context?.resources?.displayMetrics?.densityDpi ?: -1
}
fun getScreenHeight():Int {
return context?.resources?.displayMetrics?.heightPixels ?: -1
}
fun getScreenWidth():Int {
return context?.resources?.displayMetrics?.widthPixels ?: -1
}
fun getScreenLayout():Int {
return context?.resources?.configuration?.screenLayout ?: -1
}
fun getSensorList(): List<Sensor> {
if(this.context == null) {
return listOf()
}
val sensorManager = this.context!!.getSystemService(Context.SENSOR_SERVICE) as SensorManager
return sensorManager.getSensorList(Sensor.TYPE_ALL)
}
fun getStreamMaxVolume(paramInt:Int):Int {
if(this.context == null) {
return -1
}
val audioManager = this.context!!.getSystemService(Context.AUDIO_SERVICE) as android.media.AudioManager
return audioManager.getStreamMaxVolume(paramInt)
}
fun getStreamVolume(paramInt:Int):Int {
if(this.context == null) {
return -1
}
val audioManager = this.context!!.getSystemService(Context.AUDIO_SERVICE) as android.media.AudioManager
return audioManager.getStreamVolume(paramInt)
}
fun getSupportedAbis():Array<String> {
if (ApiLevel < 21) {
return getOldAbiList()
}
return getNewAbiList()
}
fun getSystemProperty(paramString1: String, paramString2: String?): String {
return if ((paramString2 != null)) System.getProperty(
paramString1,
paramString2
) else System.getProperty(paramString1)
}
fun getTotalMemory(): Long {
return getMemoryInfo(MemoryInfoType.TOTAL_MEMORY)
}
fun getFreeSpace(paramFile: File?): Long {
if (paramFile != null) {
val bool = paramFile.exists()
if (bool) return Math.round((paramFile.freeSpace / 1024L).toFloat())
.toLong()
}
return -1L
}
fun getTotalSpace(file: File):Long {
if(!file.exists()) {
return -1
}
return Math.round(file.totalSpace / 1024.0)
}
fun getUptime():Long {
return SystemClock.uptimeMillis()
}
fun hasAV1Decoder():Boolean {
return selectAllDecodeCodecs("video/av01").isNotEmpty()
}
fun hasX264Decoder():Boolean {
return selectAllDecodeCodecs("video/avc").isNotEmpty()
}
fun hasX265Decoder():Boolean {
return selectAllDecodeCodecs("video/hevc").isNotEmpty()
}
fun isAdbEnabled():Boolean {
if(ApiLevel < 17) {
return oldAdbStatus()
}
return newAdbStatus()
}
fun oldAdbStatus():Boolean {
val contentResolver = this.context?.contentResolver
val j = Settings.Secure.getInt(contentResolver, Settings.Global.ADB_ENABLED, 0)
return j == 1
}
fun newAdbStatus():Boolean {
val contentResolver = this.context?.contentResolver
val j = Settings.Global.getInt(contentResolver, Settings.Global.ADB_ENABLED, 0)
return j == 1
}
fun selectAllDecodeCodecs(paramString:String):List<MediaCodecInfo> {
val res = mutableListOf<MediaCodecInfo>()
val count = MediaCodecList.getCodecCount()
for (i in 0 until count) {
val mediaCodecInfo: MediaCodecInfo = MediaCodecList.getCodecInfoAt(i)
if (!mediaCodecInfo.isEncoder) {
val arrayOfString: Array<String> = mediaCodecInfo.supportedTypes
for (j in arrayOfString.indices) {
if (!arrayOfString[j].equals(paramString, ignoreCase = true)) {
continue
}
if(isHardwareAccelerated(mediaCodecInfo, paramString)) {
res.add(mediaCodecInfo)
}
}
}
}
return res
}
fun isHardwareAccelerated(paramMediaCodecInfo:MediaCodecInfo, paramString:String):Boolean {
if (ApiLevel >= 29) {
return isHardwareAcceleratedV29(paramMediaCodecInfo)
}
return isSoftwareOnly(paramMediaCodecInfo, paramString)
}
fun isHardwareAcceleratedV29(paramMediaCodecInfo:MediaCodecInfo):Boolean {
return paramMediaCodecInfo.isHardwareAccelerated
}
fun isSoftwareOnly( paramMediaCodecInfo:MediaCodecInfo, paramString:String):Boolean {
if (ApiLevel >= 29) {
return isSoftwareOnlyV29(paramMediaCodecInfo)
}
val codecName = paramMediaCodecInfo.name.lowercase()
var isSoftware = false
// 如果以"arc."开头返回false不是软件编解码器
if (codecName.startsWith("arc.")) {
return false
}
// 检查是否为已知的软件编解码器
when {
codecName.startsWith("omx.google.") -> isSoftware = true
codecName.startsWith("omx.ffmpeg.") -> isSoftware = true
codecName.startsWith("omx.sec.") && codecName.contains(".sw.") -> isSoftware = true
codecName == "omx.qcom.video.decoder.hevcswvdec" -> isSoftware = true
codecName.startsWith("c2.android.") -> isSoftware = true
codecName.startsWith("c2.google.") -> isSoftware = true
// 如果不以"omx."或"c2."开头,也认为是软件编解码器
!codecName.startsWith("omx.") && !codecName.startsWith("c2.") -> isSoftware = true
}
return isSoftware
}
fun isSoftwareOnlyV29(paramMediaCodecInfo:MediaCodecInfo):Boolean {
return paramMediaCodecInfo.isSoftwareOnly
}
fun getPackageInfo(paramPackageManager: PackageManager): JSONObject {
val str1: String = getAppName()
val packageInfo = paramPackageManager.getPackageInfo(str1, 0)
val jSONObject = JSONObject()
var str2 = paramPackageManager.getInstallerPackageName(str1)
jSONObject.put("installer", str2)
var l = packageInfo.firstInstallTime
jSONObject.put("firstInstallTime", l)
l = packageInfo.lastUpdateTime
jSONObject.put("lastUpdateTime", l)
val i = packageInfo.versionCode
jSONObject.put("versionCode", i)
str2 = packageInfo.versionName
jSONObject.put("versionName", str2)
str2 = packageInfo.packageName
jSONObject.put("packageName", str2)
return jSONObject
}
fun getConnectionType():String {
if (isUsingWifi()) {
return "wifi"
}
if (isActiveNetworkConnected()) {
return "cellular"
}
return "none"
}
fun isUsingWifi():Boolean {
if (this.context == null) {
return false
}
val connectivityManager =
this.context!!.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (connectivityManager == null) {
return false
}
val telephonyManager =
this.context!!.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val networkInfo = connectivityManager.getActiveNetworkInfo()
if (networkInfo == null) {
return false
}
if (!connectivityManager.backgroundDataSetting) {
return false
}
val networkInfo1 = connectivityManager.activeNetworkInfo
if (networkInfo1 == null) {
return false
}
if (networkInfo1.isConnected && telephonyManager != null) {
if (networkInfo.type == 1) {
return networkInfo.isConnected
}
return false
}
return false
}
fun isActiveNetworkConnected():Boolean {
if (this.context == null) {
return false
}
val connectivityManager =
this.context!!.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (connectivityManager == null) {
return false
}
val networkInfo = connectivityManager.activeNetworkInfo
return networkInfo != null && networkInfo.isConnected
}
fun getDisplayMetricDensity() : Float {
if(this.context == null) {
return -1.0f
}
return this.context!!.resources.displayMetrics.density
}
fun getElapsedRealtime() : Long {
return SystemClock.elapsedRealtime();
}
fun getExtensionVersion():Int {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R)
}
return -1
}
fun getMemoryInfo(type:MemoryInfoType) : Long {
try {
val randomAccessFile = RandomAccessFile("/proc/meminfo","r")
var i = 1
randomAccessFile.use {
/**
* 1 total
* 2 free
*/
while (true) {
val memoryInfo = randomAccessFile.readLine()
if(memoryInfo.isNullOrEmpty()) {
break
}
if(i == 1 && type == MemoryInfoType.TOTAL_MEMORY) {
return getMemoryValueFromString(memoryInfo)
}
if(i == 2 && type == MemoryInfoType.FREE_MEMORY) {
return getMemoryValueFromString(memoryInfo)
}
if(i > 2) {
break
}
i++
}
}
} catch (e:Throwable) {
e.printStackTrace()
}
return 0
}
fun getNetworkCountryISO() : String{
if(context == null) {
return ""
}
val telephonyManager = context!!.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
return telephonyManager.networkCountryIso
}
fun getNetworkMetered() : Boolean {
if(context == null) {
return false
}
val connectivityManager = context!!.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
return connectivityManager.isActiveNetworkMetered
}
fun getNetworkOperator():String {
if(context == null) {
return ""
}
val telephonyManager = context!!.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
return telephonyManager.networkOperator ?: ""
}
fun getNetworkOperatorName():String {
if(context == null) {
return ""
}
val telephonyManager = context!!.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
return telephonyManager.networkOperatorName ?: ""
}
fun getNetworkType(): Int {
if(context == null) {
return -1
}
val telephonyManager = context!!.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.READ_PHONE_STATE
) != PackageManager.PERMISSION_GRANTED
) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return -1
}
return telephonyManager.getNetworkType() ?: -1
}
fun getNewAbiList(): Array<String> {
return Build.SUPPORTED_ABIS
}
fun getOldAbiList() : Array<String>{
val arrayList = mutableListOf<String>()
arrayList.add(Build.CPU_ABI)
arrayList.add(Build.CPU_ABI2)
return arrayList.toTypedArray()
}
fun isRooted():Boolean {
return searchPathForBinary("su")
}
fun isAppDebuggable():Boolean {
val packageManager = this.context?.packageManager
val packageName = this.context?.packageName
val applicationInfo = packageName?.let { packageManager?.getApplicationInfo(it, 0) }
val j = applicationInfo!!.flags and 0x2
if(j==0) {
return false
}
val packageInfo = packageManager?.getPackageInfo(packageName, 64)
val arrayOfSignature = packageInfo?.signatures
if (arrayOfSignature == null) {
return false
}
for (signature in arrayOfSignature) {
val certificateFactory = CertificateFactory.getInstance("X.509")
val byteArrayInputStream = ByteArrayInputStream(signature.toByteArray())
val certificate: Certificate = certificateFactory.generateCertificate(byteArrayInputStream)
if (certificate is X509Certificate) {
val x500Principal1 = certificate.getSubjectX500Principal()
if (x500Principal1.equals(DEBUG_CERT)) {
return true
}
}
}
return false
}
fun getLanguage() :String {
return Locale.getDefault().toString()
}
fun getAppVersion(): String? {
val str: String = this.context?.packageName ?: ""
val packageManager: PackageManager? = this.context?.packageManager
try {
val packageInfo1 = packageManager?.getPackageInfo(str, 0)
if (packageInfo1 != null) {
return packageInfo1.versionName
}
} catch (e: Throwable) {
e.printStackTrace()
}
return ""
}
fun getTimeZone() :String{
val timeZone1 = TimeZone.getDefault()
val locale = Locale.US
return timeZone1.getDisplayName(false, 0, locale)
}
fun getTimeOffset() : Int{
val timeZone = TimeZone.getDefault()
val l1 = System.currentTimeMillis()
return timeZone.getOffset(l1) / 1000
}
fun getWebViewUa() : String {
return WebSettings.getDefaultUserAgent(this.context)
}
fun isWiredHeadsetOn(): Boolean {
val audioManager = context?.getSystemService(Context.AUDIO_SERVICE) as AudioManager
return audioManager.isWiredHeadsetOn
}
fun isUSBConnected():Boolean {
val intentFilter = IntentFilter("android.hardware.usb.action.USB_STATE")
val intent = context?.registerReceiver(null, intentFilter);
if (intent != null) {
return intent.getBooleanExtra("connected", false)
}
return false
}
fun getBatteryLevel(): Float {
if (context != null) {
val intentFilter = IntentFilter("android.intent.action.BATTERY_CHANGED")
var i = 0
val intent = context!!.registerReceiver(null, intentFilter)
if (intent != null) {
i = -1
val j = intent.getIntExtra("level", i)
i = intent.getIntExtra("scale", i)
val f1 = j.toFloat()
val f2 = i.toFloat()
return f1 / f2
}
}
return -1.0f
}
private fun searchPathForBinary(exe:String):Boolean {
val str1 = System.getenv("PATH") ?: return false
val paths = str1.split(":")
for (path in paths) {
val file = File(path)
if(!file.exists()) {
continue
}
if(!file.isDirectory) {
continue
}
val arrayOfFile = file.listFiles()
if(arrayOfFile == null || arrayOfFile.isNotEmpty()) {
continue
}
for (it in arrayOfFile) {
if(it.name.equals(exe, ignoreCase = true)) {
return true
}
}
}
return false
}
private fun getMemoryValueFromString(paramString: String?): Long {
if (paramString != null) {
val pattern: Pattern = Pattern.compile("(\\d+)")
val matcher: Matcher = pattern.matcher(paramString)
if (!matcher.find()) {
return -1L
}
val value: String = matcher.group(1) ?: return -1L
try {
return value.toLong()
} catch (e: NumberFormatException) {
e.printStackTrace()
}
}
return -1L
}
fun getDeviceInfoData():List<InfoItem> {
val res = mutableListOf<InfoItem>()
res.add(InfoItem("bundleId", this.getAppName()))
res.add(InfoItem("encrypted", this.isAppDebuggable().toString()))
res.add(InfoItem("rooted", "", callback = { this.isRooted().toString() }))
res.add(InfoItem("osVersion", this.getOsVersion()))
res.add(InfoItem("deviceModel", this.getModel()))
res.add(InfoItem("language", this.getLanguage()))
res.add(InfoItem("connectionType", this.getConnectionType()))
res.add(InfoItem("screenHeight", this.getScreenHeight().toString()))
res.add(InfoItem("screenWidth", this.getScreenWidth().toString()))
res.add(InfoItem("deviceMake", this.getManufacturer()))
res.add(InfoItem("screenDensity", this.getScreenDensity().toString()))
res.add(InfoItem("screenSize", this.getScreenLayout().toString()))
res.add(InfoItem("networkOperator", this.getNetworkOperator()))
res.add(InfoItem("networkOperatorName", this.getNetworkOperatorName()))
res.add(InfoItem("wiredHeadset", this.isWiredHeadsetOn().toString()))
res.add(InfoItem("volume", this.getStreamVolume(1).toString()))
res.add(InfoItem("deviceFreeSpace", this.getFreeSpace(this.context?.cacheDir).toString()))
res.add(InfoItem("apiLevel", this.ApiLevel.toString()))
res.add(InfoItem("networkType", this.getNetworkType().toString()))
res.add(InfoItem("networkMetered", this.getNetworkMetered().toString()))
res.add(InfoItem("bundleVersion", this.getAppVersion().toString()))
res.add(InfoItem("timeZone", this.getTimeZone()))
res.add(InfoItem("cpuCount", this.getCPUCount().toString()))
res.add(InfoItem("usbConnected", this.isUSBConnected().toString()))
res.add(InfoItem("timeZoneOffset", this.getTimeOffset().toString()))
res.add(InfoItem("webviewUa", this.getWebViewUa()))
res.add(InfoItem("apkDeveloperSigningCertificateHash", this.getCertificateFingerprint()))
res.add(InfoItem("deviceUpTime", this.getUptime().toString()))
res.add(InfoItem("deviceElapsedRealtime", this.getElapsedRealtime().toString()))
res.add(InfoItem("adbEnabled", this.isAdbEnabled().toString()))
res.add(InfoItem("androidFingerprint", this.getFingerprint()))
res.add(InfoItem("batteryStatus", this.getBatteryStatus().toString()))
res.add(InfoItem("batteryLevel", this.getBatteryLevel().toString()))
res.add(InfoItem("limitAdTracking","", callback = { this.LimitedAdTracking.toString() }))
return res.toList()
}
}