main.com.wisetrack.sdk.DeviceInfo.kt Maven / Gradle / Ivy
package com.wisetrack.sdk
import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.Signature
import android.content.res.Configuration
import android.content.res.Configuration.*
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.telephony.TelephonyManager
import android.text.TextUtils
import android.util.DisplayMetrics
import android.util.Log
import java.util.*
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
/**
* @author hamed (@hamed-hsb)
* @since 29th April 2024
*/
class DeviceInfo(wisetrackConfig: WiseTrackConfig) {
companion object {
const val ANDROID_OS_NAME = "android"
const val SMALL = "small"
const val NORMAL = "normal"
const val UNDEFINED = "undefined"
const val LONG = "long"
const val LARGE = "large"
const val XLARGE = "xlarge"
const val LOW = "low"
const val MEDIUM = "medium"
const val HIGH = "high"
private const val OFFICIAL_FACEBOOK_SIGNATURE =
"30820268308201d102044a9c4610300d06092a864886f70d0101040500307a310b3009060355040613" +
"025553310b3009060355040813024341311230100603550407130950616c6f20416c746f31" +
"183016060355040a130f46616365626f6f6b204d6f62696c653111300f060355040b130846" +
"616365626f6f6b311d301b0603550403131446616365626f6f6b20436f72706f726174696f" +
"6e3020170d3039303833313231353231365a180f32303530303932353231353231365a307a" +
"310b3009060355040613025553310b30090603550408130243413112301006035504071309" +
"50616c6f20416c746f31183016060355040a130f46616365626f6f6b204d6f62696c653111" +
"300f060355040b130846616365626f6f6b311d301b0603550403131446616365626f6f6b20" +
"436f72706f726174696f6e30819f300d06092a864886f70d010101050003818d0030818902" +
"818100c207d51df8eb8c97d93ba0c8c1002c928fab00dc1b42fca5e66e99cc3023ed2d214d" +
"822bc59e8e35ddcf5f44c7ae8ade50d7e0c434f500e6c131f4a2834f987fc46406115de201" +
"8ebbb0d5a3c261bd97581ccfef76afc7135a6d59e8855ecd7eacc8f8737e794c60a761c536" +
"b72b11fac8e603f5da1a2d54aa103b8a13c0dbc10203010001300d06092a864886f70d0101" +
"040500038181005ee9be8bcbb250648d3b741290a82a1c9dc2e76a0af2f2228f1d9f9c4007" +
"529c446a70175c5a900d5141812866db46be6559e2141616483998211f4a673149fb2232a1" +
"0d247663b26a9031e15f84bc1c74d141ff98a02d76f85b2c8ab2571b6469b232d8e768a7f7" +
"ca04f7abe4a775615916c07940656b58717457b42bd928a2"
}
var wisetrackConfig: WiseTrackConfig? = null
var ads: AdsIdentifier
var playAdId: String? = null
var playAdIdSource: String? = null
var isGooglePlayServicesAvailable: Boolean? = null
var playAdIdAttempt = -1
var isTrackingEnabled: Boolean? = null
private var nonGoogleIdsReadOnce = false
private var playIdsReadOnce = false
private var otherDeviceInfoParamsReadOnce = false
var androidId: String? = null
var fbAttributionId: String? = null
var clientSdk: String? = null
var packageName: String? = null
var packageInfo: String? = null
var appVersion: String? = null
var deviceType: String? = null
var deviceName: String? = null
var deviceManufacturer: String? = null
var osName: String? = null
var osVersion: String? = null
var apiLevel: String? = null
var language: String? = null
var country: String? = null
var screenSize: String? = null
var screenFormat: String? = null
var screenDensity: String? = null
var displayWidth: String? = null
var displayHeight: String? = null
var hardwareName: String? = null
var abi: String? = null
var buildName: String? = null
var appInstallTime: String? = null
var appUpdateTime: String? = null
var uiMode = 0
var serviceExceptionAttempt = 0
var appSetId: String? = null
var isGooglePlayGamesForPC = false
var isSamsungCloudEnvironment: Boolean? = null
var imeiParameters: Map? = null
var oaidParameters: Map? = null
var fireAdId: String? = null
var fireTrackingEnabled: Boolean? = null
var connectivityType = 0
var mcc: String? = null
var mnc: String? = null
init {
val context: Context = wisetrackConfig.context!!
val resources = context.resources
val displayMetrics = resources.displayMetrics
val configuration = resources.configuration
val locale = Util.getLocale(configuration)
val packageInfo = getPackageInfoI(context)
val screenLayout = configuration.screenLayout
ads = wisetrackConfig.context?.let { AdsIdentifier(wisetrackConfig) }!!
isGooglePlayGamesForPC = Util.isGooglePlayGamesForPC(context)
packageName = getPackageNameI(context)
appVersion = getAppVersionI(packageInfo)
deviceType = getDeviceTypeI(configuration)
deviceName = getDeviceNameI()
deviceManufacturer = getDeviceManufacturerI()
osName = getOsNameI()
osVersion = getOsVersionI()
apiLevel = getApiLevelI()
language = getLanguageI(locale)
country = getCountryI(locale)
screenSize = getScreenSizeI(screenLayout)
screenFormat = getScreenFormatI(screenLayout)
screenDensity = getScreenDensityI(displayMetrics)
displayWidth = getDisplayWidth(displayMetrics)
displayHeight = getDisplayHeight(displayMetrics)
clientSdk = getClientSdk(wisetrackConfig.sdkPrefix)
fbAttributionId = getFacebookAttributionId(context)
hardwareName = getHardwareNameI()
abi = getABI()
buildName = getBuildNameI()
appInstallTime = getAppInstallTime(packageInfo)
appUpdateTime = getAppUpdateTime(packageInfo)
uiMode = getDeviceUiMode(configuration)
if (Reflection.isAppRunningInSamsungCloudEnvironment(context, wisetrackConfig.logger)) {
isSamsungCloudEnvironment = true
playAdId = Reflection.getSamsungCloudDevGoogleAdId(context, wisetrackConfig.logger)
playAdIdSource = "samsung_cloud_sdk"
}
if (Util.canReadPlayIds(wisetrackConfig)) {
appSetId = Reflection.getAppSetId(context)
}
}
fun isValidAdId(adId: String?): Boolean {
val invalidPattern = "00000000-0000-0000-0000-000000000000"
return adId != null && adId != invalidPattern
}
fun reloadPlayIds(wisetrackConfig: WiseTrackConfig) {
isGooglePlayServicesAvailable = Util.isGooglePlayServicesAvailable(wisetrackConfig.context!!)
println("load reload play ids")
val context: Context = wisetrackConfig.context!!
val previousPlayAdId = playAdId
val previousIsTrackingEnabled = isTrackingEnabled
playAdId = null
isTrackingEnabled = null
playAdIdSource = null
playAdIdAttempt = -1
reloadAdsId(1)
if (isValidPlayIds()) {
println(" success adid from reload ads id 1")
return
}
if (isSamsungCloudEnvironment != null && isSamsungCloudEnvironment as Boolean) {
return
}
if (!Util.canReadPlayIds(wisetrackConfig)) {
return
}
if (playIdsReadOnce && wisetrackConfig.readDeviceInfoOnceEnabledI) {
return
}
reloadAdsId(2)
if (isValidPlayIds()) {
println(" success adid from reload ads id 2")
return
}
// attempt connecting to Google Play Service by own
var serviceAttempt = 1
while (serviceAttempt <= 3) {
try {
println("load loop ${serviceAttempt}")
// timeout is a multiplier of the attempt number with 3 seconds
// so first 3 seconds, second 6 seconds and third and last 9 seconds
val timeoutServiceMilli = Constants.ONE_SECOND * 3 * serviceAttempt
val gpsInfo: GooglePlayServicesClient.Companion.GooglePlayServicesInfo? =
GooglePlayServicesClient.getGooglePlayServicesInfo(
context,
timeoutServiceMilli
)
if (playAdId == null) {
playAdId = gpsInfo!!.gpsAdid!!
playIdsReadOnce = true
}
if (isTrackingEnabled == null) {
isTrackingEnabled = gpsInfo!!.isTrackingEnabled
}
if (playAdId != null && isTrackingEnabled != null) {
println("load condition")
if (isValidPlayIds()) {
playAdIdSource = "service"
playAdIdAttempt = serviceAttempt
return
} else {
println("load condition else")
reloadAdsId(serviceAttempt)
if (isValidPlayIds()) {
println(" success google service from reload ads id ${serviceAttempt}")
return
}
}
}
} catch (e: Exception) {
println("function reload exception:${e.message}")
serviceExceptionAttempt++
wisetrackConfig.logger.report("GooglePlayServicesClient, ${e.message}")
if (serviceExceptionAttempt <= 3) {
println("function reload exceptionserviceExceptionAttempt ${serviceExceptionAttempt}:${e.message}")
if (playAdId == null) {
println("function reload exceptionserviceExceptionAttempt ${serviceExceptionAttempt} null :${e.message}")
reloadPlayIds(wisetrackConfig)
}
}
}
serviceAttempt += 1
}
// as fallback attempt connecting to Google Play Service using library
var libAttempt = 1
while (libAttempt <= 3) {
println("load loop lib ${libAttempt}")
// timeout inside library is 10 seconds, so 10 + 1 seconds are given
val advertisingInfoObject = Util.getAdvertisingInfoObject(
context, Constants.ONE_SECOND * 11, wisetrackConfig
)
if (advertisingInfoObject == null) {
println("load loop lib advertisingInfoObject null")
libAttempt += 1
continue
}
if (playAdId == null) {
println("load loop lib playAdId null")
// just needs a short timeout since it should be just accessing a POJO
playAdId = Util.getPlayAdId(
context, advertisingInfoObject, Constants.ONE_SECOND
)
playIdsReadOnce = true
}
if (isTrackingEnabled == null) {
println("load loop lib isTrackingEnabled null")
// just needs a short timeout since it should be just accessing a POJO
isTrackingEnabled = Util.isPlayTrackingEnabled(
context, advertisingInfoObject, Constants.ONE_SECOND
)
}
if (playAdId != null && isTrackingEnabled != null) {
if (isValidPlayIds()) {
println("load loop lib playAdId != null && isTrackingEnabled != null ${playAdId} , ${isTrackingEnabled}")
playAdIdSource = "library"
playAdIdAttempt = libAttempt
return
}else{
reloadAdsId(libAttempt)
if (isValidPlayIds()) {
println(" success google service from libattempt reload ads id ${libAttempt}")
return
}
}
}
libAttempt += 1
}
// if both weren't found, use previous values
if (playAdId == null) {
println("load loop lib playAdId = previousPlayAdId")
playAdId = previousPlayAdId
playIdsReadOnce = true
}
if (isTrackingEnabled == null) {
println("load loop lib isTrackingEnabled == null")
isTrackingEnabled = previousIsTrackingEnabled
}
}
private fun reloadAdsId(attempt: Int) {
println("load ads id ${attempt}")
if (ads == null)
return
ads.start()
playAdId = AdsIdentifier.adId
// Alternatively, if you need to explicitly set it
if (AdsIdentifier.isLimited != null) {
isTrackingEnabled = if (!AdsIdentifier.isLimited!!) {
true
} else {
false
}
}
playAdIdSource = "adservice"
playAdIdAttempt = attempt
}
private fun isValidPlayIds(): Boolean {
return playAdId != null && isTrackingEnabled != null && isValidAdId(playAdId)
}
fun reloadNonPlayIds(wisetrackConfig: WiseTrackConfig) {
if (!Util.canReadNonPlayIds(wisetrackConfig)) {
return
}
if (nonGoogleIdsReadOnce) {
return
}
androidId = Util.getAndroidId(wisetrackConfig.context)
nonGoogleIdsReadOnce = true
}
fun reloadOtherDeviceInfoParams(wisetrackConfig: WiseTrackConfig, logger: ILogger) {
if (wisetrackConfig.readDeviceInfoOnceEnabledI && otherDeviceInfoParamsReadOnce) {
return
}
imeiParameters = UtilDeviceIds.getImeiParameters(wisetrackConfig, logger)
oaidParameters = UtilDeviceIds.getOaidParameters(wisetrackConfig, logger)
fireAdId = UtilDeviceIds.getFireAdvertisingId(wisetrackConfig)
fireTrackingEnabled = UtilDeviceIds.getFireTrackingEnabled(wisetrackConfig)
connectivityType = UtilDeviceIds.getConnectivityType(wisetrackConfig.context!!, logger)
mcc = UtilDeviceIds.getMcc(wisetrackConfig.context!!, logger)
mnc = UtilDeviceIds.getMnc(wisetrackConfig.context!!, logger)
otherDeviceInfoParamsReadOnce = true
}
fun getFireAdvertisingIdBypassConditions(contentResolver: ContentResolver?): String? {
return UtilDeviceIds.getFireAdvertisingId(contentResolver)
}
private fun getPackageNameI(context: Context): String? {
return context.packageName
}
private fun getPackageInfoI(context: Context): PackageInfo? {
return try {
val packageManager = context.packageManager
val name = context.packageName
packageManager.getPackageInfo(name, PackageManager.GET_PERMISSIONS)
} catch (e: Exception) {
wisetrackConfig?.logger?.report(e.message!!)
null
}
}
private fun getAppVersionI(packageInfo: PackageInfo?): String? {
return try {
packageInfo!!.versionName
} catch (e: Exception) {
wisetrackConfig?.logger?.report(e.message!!)
null
}
}
private fun getDeviceTypeI(configuration: Configuration): String? {
if (isGooglePlayGamesForPC) {
return "pc"
}
val uiMode = configuration.uiMode and UI_MODE_TYPE_MASK
if (uiMode == UI_MODE_TYPE_TELEVISION) {
return "tv"
}
val screenSize = configuration.screenLayout and SCREENLAYOUT_SIZE_MASK
return when (screenSize) {
SCREENLAYOUT_SIZE_SMALL, SCREENLAYOUT_SIZE_NORMAL -> "phone"
SCREENLAYOUT_SIZE_LARGE, 4 -> "tablet"
else -> null
}
}
private fun getDeviceUiMode(configuration: Configuration): Int {
return configuration.uiMode and UI_MODE_TYPE_MASK
}
private fun getDeviceNameI(): String? {
return if (isGooglePlayGamesForPC) {
null
} else Build.MODEL
}
private fun getDeviceManufacturerI(): String? {
return Build.MANUFACTURER
}
private fun getOsNameI(): String? {
return if (isGooglePlayGamesForPC) {
"windows"
} else "android"
}
private fun getOsVersionI(): String? {
return if (isGooglePlayGamesForPC) {
null
} else Build.VERSION.RELEASE
}
private fun getApiLevelI(): String? {
return "" + Build.VERSION.SDK_INT
}
private fun getLanguageI(locale: Locale?): String? {
return locale!!.language
}
private fun getCountryI(locale: Locale?): String? {
return locale!!.country
}
private fun getBuildNameI(): String? {
return Build.ID
}
private fun getHardwareNameI(): String? {
return Build.DISPLAY
}
private fun getScreenSizeI(screenLayout: Int): String? {
val screenSize = screenLayout and SCREENLAYOUT_SIZE_MASK
return when (screenSize) {
SCREENLAYOUT_SIZE_SMALL -> SMALL
SCREENLAYOUT_SIZE_NORMAL -> NORMAL
SCREENLAYOUT_SIZE_LARGE -> LARGE
4 -> XLARGE
else -> null
}
}
private fun getScreenFormatI(screenLayout: Int): String? {
val screenFormat = screenLayout and SCREENLAYOUT_LONG_MASK
return when (screenFormat) {
SCREENLAYOUT_LONG_YES -> LONG
SCREENLAYOUT_LONG_NO -> NORMAL
else -> null
}
}
private fun getScreenDensityI(displayMetrics: DisplayMetrics): String? {
val density = displayMetrics.densityDpi
val low = (DisplayMetrics.DENSITY_MEDIUM + DisplayMetrics.DENSITY_LOW) / 2
val high = (DisplayMetrics.DENSITY_MEDIUM + DisplayMetrics.DENSITY_HIGH) / 2
if (density == 0) {
return null
} else if (density < low) {
return LOW
} else if (density > high) {
return HIGH
}
return MEDIUM
}
private fun getDisplayWidth(displayMetrics: DisplayMetrics): String? {
return displayMetrics.widthPixels.toString()
}
private fun getDisplayHeight(displayMetrics: DisplayMetrics): String? {
return displayMetrics.heightPixels.toString()
}
private fun getClientSdk(sdkPrefix: String?): String? {
return if (sdkPrefix == null) {
Constants.CLIENT_SDK
} else {
Util.formatString("%s@%s", sdkPrefix, Constants.CLIENT_SDK)
}
}
@SuppressLint("Range")
@Suppress("deprecation")
private fun getFacebookAttributionId(context: Context): String? {
return try {
@SuppressLint("PackageManagerGetSignatures") var signatures: Array? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val signingInfo = context.packageManager.getPackageInfo(
"com.facebook.katana",
PackageManager.GET_SIGNING_CERTIFICATES
).signingInfo
if (signingInfo != null) {
signatures = signingInfo.apkContentsSigners
}
} else {
signatures = context.packageManager.getPackageInfo(
"com.facebook.katana",
PackageManager.GET_SIGNATURES
).signatures
}
if (signatures == null || signatures.size != 1) {
// Unable to find the correct signatures for this APK
return null
}
val facebookApkSignature = signatures[0]
if (OFFICIAL_FACEBOOK_SIGNATURE != facebookApkSignature.toCharsString()) {
// not the official Facebook application
return null
}
val contentResolver = context.contentResolver
val uri = Uri.parse("content://com.facebook.katana.provider.AttributionIdProvider")
val columnName = "aid"
val projection = arrayOf(columnName)
val cursor = contentResolver.query(uri, projection, null, null, null) ?: return null
if (!cursor.moveToFirst()) {
cursor.close()
return null
}
val attributionId = cursor.getString(cursor.getColumnIndex(columnName))
cursor.close()
attributionId
} catch (e: Exception) {
wisetrackConfig?.logger?.report(e.message!!)
null
}
}
private fun getABI(): String? {
val SupportedABIS: Array? = Util.getSupportedAbis()
// SUPPORTED_ABIS is only supported in API level 21
// get CPU_ABI instead
return if (SupportedABIS == null || SupportedABIS.size == 0) {
Util.getCpuAbi()
} else SupportedABIS[0]
}
private fun getAppInstallTime(packageInfo: PackageInfo?): String? {
return try {
Util.dateFormatter.format(Date(packageInfo!!.firstInstallTime))
} catch (ex: Exception) {
wisetrackConfig?.logger?.report(ex.message!!)
null
}
}
private fun getAppUpdateTime(packageInfo: PackageInfo?): String? {
return try {
Util.dateFormatter.format(Date(packageInfo!!.lastUpdateTime))
} catch (ex: Exception) {
wisetrackConfig?.logger?.report(ex.message!!)
null
}
}
private object UtilDeviceIds {
fun getImeiParameters(
wisetrackConfig: WiseTrackConfig,
logger: ILogger
): Map? {
return if (wisetrackConfig.coppaCompliantEnabled == true) {
null
} else Reflection.getImeiParameters(wisetrackConfig.context, logger)
}
fun getOaidParameters(
wisetrackConfig: WiseTrackConfig,
logger: ILogger
): Map? {
return if (wisetrackConfig.coppaCompliantEnabled == true) {
null
} else {
Reflection.getOaidParameters(wisetrackConfig.context, logger)
}
}
fun getFireAdvertisingId(wisetrackConfig: WiseTrackConfig): String? {
return if (wisetrackConfig.coppaCompliantEnabled == true) {
null
} else getFireAdvertisingId(wisetrackConfig.context!!.getContentResolver())
}
fun getFireAdvertisingId(contentResolver: ContentResolver?): String? {
if (contentResolver == null) {
return null
}
try {
// get advertising
return Settings.Secure.getString(contentResolver, "advertising_id")
} catch (ex: Exception) {
// not supported
}
return null
}
fun getFireTrackingEnabled(wisetrackConfig: WiseTrackConfig): Boolean? {
return if (wisetrackConfig.coppaCompliantEnabled == true) {
null
} else getFireTrackingEnabled(wisetrackConfig.context!!.getContentResolver())
}
fun getFireTrackingEnabled(contentResolver: ContentResolver): Boolean? {
try {
// get user's tracking preference
return Settings.Secure.getInt(contentResolver, "limit_ad_tracking") == 0
} catch (ex: Exception) {
// not supported
}
return null
}
@Suppress("deprecation")
fun getConnectivityType(context: Context, logger: ILogger): Int {
try {
val cm =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
?: return -1
// for api 22 or lower, still need to get raw type
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
val activeNetwork = cm.activeNetworkInfo
return activeNetwork!!.type
}
// .getActiveNetwork() is only available from api 23
val activeNetwork = cm.activeNetwork ?: return -1
val activeNetworkCapabilities =
cm.getNetworkCapabilities(activeNetwork) ?: return -1
// check each network capability available from api 23
if (activeNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return NetworkCapabilities.TRANSPORT_WIFI
}
if (activeNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return NetworkCapabilities.TRANSPORT_CELLULAR
}
if (activeNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
return NetworkCapabilities.TRANSPORT_ETHERNET
}
if (activeNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
return NetworkCapabilities.TRANSPORT_VPN
}
if (activeNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) {
return NetworkCapabilities.TRANSPORT_BLUETOOTH
}
// only after api 26, that more transport capabilities were added
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return -1
}
if (activeNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)) {
return NetworkCapabilities.TRANSPORT_WIFI_AWARE
}
// and then after api 27, that more transport capabilities were added
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
return -1
}
if (activeNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_LOWPAN)) {
return NetworkCapabilities.TRANSPORT_LOWPAN
}
} catch (e: Exception) {
logger.warn("Couldn't read connectivity type (%s)", e.message!!)
}
return -1
}
fun getMcc(context: Context, logger: ILogger?): String? {
return try {
val tel = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val networkOperator = tel.networkOperator
if (TextUtils.isEmpty(networkOperator)) {
WiseTrackFactory.getLogger()
.warn("Couldn't receive networkOperator string to read MCC")
return null
}
networkOperator.substring(0, 3)
} catch (ex: Exception) {
WiseTrackFactory.getLogger().warn("Couldn't return mcc")
null
}
}
fun getMnc(context: Context, logger: ILogger): String? {
return try {
val tel = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val networkOperator = tel.networkOperator
if (TextUtils.isEmpty(networkOperator)) {
WiseTrackFactory.getLogger()
.warn("Couldn't receive networkOperator string to read MNC")
return null
}
networkOperator.substring(3)
} catch (ex: Exception) {
logger.warn("Couldn't return mnc")
null
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy