All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.com.wisetrack.sdk.DeviceInfo.kt Maven / Gradle / Ivy

There is a newer version: 1.5.3
Show newest version
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 playAdId: String? = null
    var playAdIdSource: String? = 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 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
        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 reloadPlayIds(wisetrackConfig: WiseTrackConfig) {
        if (isSamsungCloudEnvironment != null && isSamsungCloudEnvironment as Boolean) {
            return
        }
        if (!Util.canReadPlayIds(wisetrackConfig)) {
            return
        }
        if (playIdsReadOnce && wisetrackConfig.readDeviceInfoOnceEnabledI) {
            return
        }
        val context: Context = wisetrackConfig.context!!
        val previousPlayAdId = playAdId
        val previousIsTrackingEnabled = isTrackingEnabled
        playAdId = null
        isTrackingEnabled = null
        playAdIdSource = null
        playAdIdAttempt = -1

        // attempt connecting to Google Play Service by own
        var serviceAttempt = 1
        while (serviceAttempt <= 3) {
            try {
                // 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) {
                    playAdIdSource = "service"
                    playAdIdAttempt = serviceAttempt
                    return
                }
            } catch (e: Exception) {

            }
            serviceAttempt += 1
        }

        // as fallback attempt connecting to Google Play Service using library
        var libAttempt = 1
        while (libAttempt <= 3) {

            // timeout inside library is 10 seconds, so 10 + 1 seconds are given
            val advertisingInfoObject = Util.getAdvertisingInfoObject(
                context, Constants.ONE_SECOND * 11
            )
            if (advertisingInfoObject == null) {
                libAttempt += 1
                continue
            }
            if (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) {
                // 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) {
                playAdIdSource = "library"
                playAdIdAttempt = libAttempt
                return
            }
            libAttempt += 1
        }

        // if both weren't found, use previous values
        if (playAdId == null) {
            playAdId = previousPlayAdId
            playIdsReadOnce = true
        }
        if (isTrackingEnabled == null) {
            isTrackingEnabled = previousIsTrackingEnabled
        }
    }

    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) {
            null
        }
    }

    private fun getAppVersionI(packageInfo: PackageInfo?): String? {
        return try {
            packageInfo!!.versionName
        } catch (e: Exception) {
            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) {
            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) {
            null
        }
    }

    private fun getAppUpdateTime(packageInfo: PackageInfo?): String? {
        return try {
            Util.dateFormatter.format(Date(packageInfo!!.lastUpdateTime))
        } catch (ex: Exception) {
            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