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

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

There is a newer version: 1.5.8
Show newest version
package com.wisetrack.sdk

import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import org.json.JSONObject
import java.io.File
import java.io.FileInputStream
import java.util.*

/**
@author hamed (@hamed-hsb)
 * @since 10th October 2021
 */

class PreinstallUtil {
    companion object {


        private const val SYSTEM_PROPERTY_BITMASK: Long = 1                  //00...000000001
        private const val SYSTEM_PROPERTY_REFLECTION_BITMASK: Long = 2       //00...000000010
        private const val SYSTEM_PROPERTY_PATH_BITMASK: Long = 4             //00...000000100
        private const val SYSTEM_PROPERTY_PATH_REFLECTION_BITMASK: Long = 8  //00...000001000
        private const val CONTENT_PROVIDER_BITMASK: Long = 16                //00...000010000
        private const val CONTENT_PROVIDER_INTENT_ACTION_BITMASK: Long = 32  //00...000100000
        private const val FILE_SYSTEM_BITMASK: Long = 64                     //00...001000000
        private const val CONTENT_PROVIDER_NO_PERMISSION_BITMASK: Long = 128 //00...010000000


        // bitwise OR (|) of all above locations
        private const val ALL_LOCATION_BITMASK = SYSTEM_PROPERTY_BITMASK or
                SYSTEM_PROPERTY_REFLECTION_BITMASK or
                SYSTEM_PROPERTY_PATH_BITMASK or
                SYSTEM_PROPERTY_PATH_REFLECTION_BITMASK or
                CONTENT_PROVIDER_BITMASK or
                CONTENT_PROVIDER_INTENT_ACTION_BITMASK or
                FILE_SYSTEM_BITMASK or
                CONTENT_PROVIDER_NO_PERMISSION_BITMASK //00...011111111


        fun hasAllLocationsBeenRead(status: Long): Boolean {
            // Check if the given status has none of the valid location with bit `0`, indicating it has
            // not been read
            return status and ALL_LOCATION_BITMASK == ALL_LOCATION_BITMASK
        }

        fun hasNotBeenRead(location: String?, status: Long): Boolean {
            // Check if the given status has bit '0` (not `1`) for the given location, indicating it has not been read
            when (location) {
                Constants.SYSTEM_PROPERTIES -> return status and SYSTEM_PROPERTY_BITMASK != SYSTEM_PROPERTY_BITMASK
                Constants.SYSTEM_PROPERTIES_REFLECTION -> return status and SYSTEM_PROPERTY_REFLECTION_BITMASK != SYSTEM_PROPERTY_REFLECTION_BITMASK
                Constants.SYSTEM_PROPERTIES_PATH -> return status and SYSTEM_PROPERTY_PATH_BITMASK != SYSTEM_PROPERTY_PATH_BITMASK
                Constants.SYSTEM_PROPERTIES_PATH_REFLECTION -> return status and SYSTEM_PROPERTY_PATH_REFLECTION_BITMASK != SYSTEM_PROPERTY_PATH_REFLECTION_BITMASK
                Constants.CONTENT_PROVIDER -> return status and CONTENT_PROVIDER_BITMASK != CONTENT_PROVIDER_BITMASK
                Constants.CONTENT_PROVIDER_INTENT_ACTION -> return status and CONTENT_PROVIDER_INTENT_ACTION_BITMASK != CONTENT_PROVIDER_INTENT_ACTION_BITMASK
                Constants.FILE_SYSTEM -> return status and FILE_SYSTEM_BITMASK != FILE_SYSTEM_BITMASK
                Constants.CONTENT_PROVIDER_NO_PERMISSION -> return status and CONTENT_PROVIDER_NO_PERMISSION_BITMASK != CONTENT_PROVIDER_NO_PERMISSION_BITMASK
            }
            return false
        }

        fun markAsRead(location: String?, status: Long): Long {
            // Set the bit to '1` for the given location, indicating it has been read
            when (location) {
                Constants.SYSTEM_PROPERTIES -> return status or SYSTEM_PROPERTY_BITMASK
                Constants.SYSTEM_PROPERTIES_REFLECTION -> return status or SYSTEM_PROPERTY_REFLECTION_BITMASK
                Constants.SYSTEM_PROPERTIES_PATH -> return status or SYSTEM_PROPERTY_PATH_BITMASK
                Constants.SYSTEM_PROPERTIES_PATH_REFLECTION -> return status or SYSTEM_PROPERTY_PATH_REFLECTION_BITMASK
                Constants.CONTENT_PROVIDER -> return status or CONTENT_PROVIDER_BITMASK
                Constants.CONTENT_PROVIDER_INTENT_ACTION -> return status or CONTENT_PROVIDER_INTENT_ACTION_BITMASK
                Constants.FILE_SYSTEM -> return status or FILE_SYSTEM_BITMASK
                Constants.CONTENT_PROVIDER_NO_PERMISSION -> return status or CONTENT_PROVIDER_NO_PERMISSION_BITMASK
            }
            return status
        }

        fun getPayloadFromSystemProperty(
            packageName: String,
            logger: ILogger
        ): String? {
            return readSystemProperty(
                Constants.WISETRACK_PREINSTALL_SYSTEM_PROPERTY_PREFIX + packageName,
                logger
            )
        }

        fun getPayloadFromSystemPropertyReflection(
            packageName: String,
            logger: ILogger
        ): String? {
            return readSystemPropertyReflection(
                Constants.WISETRACK_PREINSTALL_SYSTEM_PROPERTY_PREFIX + packageName,
                logger
            )
        }

        fun getPayloadFromSystemPropertyFilePath(
            packageName: String,
            logger: ILogger
        ): String? {
            val filePath: String? =
                readSystemProperty(Constants.WISETRACK_PREINSTALL_SYSTEM_PROPERTY_PATH, logger)
            if (filePath == null || filePath.isEmpty()) {
                return null
            }
            val content: String? = readFileContent(filePath, logger)
            return if (content == null || content.isEmpty()) {
                null
            } else readPayloadFromJsonString(content, packageName, logger)
        }

        fun getPayloadFromSystemPropertyFilePathReflection(
            packageName: String,
            logger: ILogger
        ): String? {
            val filePath: String? = readSystemPropertyReflection(
                Constants.WISETRACK_PREINSTALL_SYSTEM_PROPERTY_PATH,
                logger
            )
            if (filePath == null || filePath.isEmpty()) {
                return null
            }
            val content: String? = readFileContent(filePath, logger)
            return if (content == null || content.isEmpty()) {
                null
            } else readPayloadFromJsonString(content, packageName, logger)
        }

        fun getPayloadFromContentProviderDefault(
            context: Context,
            packageName: String,
            logger: ILogger
        ): String? {
            if (!Util.resolveContentProvider(
                    context,
                    Constants.WISETRACK_PREINSTALL_CONTENT_URI_AUTHORITY
                )
            ) {
                return null
            }
            val defaultContentUri: String = String.format(
                "content://%s/%s",
                Constants.WISETRACK_PREINSTALL_CONTENT_URI_AUTHORITY,
                Constants.WISETRACK_PREINSTALL_CONTENT_URI_PATH
            )
            return readContentProvider(context, defaultContentUri, packageName, logger)
        }

        fun getPayloadsFromContentProviderIntentAction(
            context: Context,
            packageName: String,
            logger: ILogger
        ): List? {
            return readContentProviderIntentAction(
                context,
                packageName,
                Manifest.permission.INSTALL_PACKAGES,
                logger
            )
        }

        fun getPayloadsFromContentProviderNoPermission(
            context: Context,
            packageName: String,
            logger: ILogger
        ): List? {
            return readContentProviderIntentAction(
                context,
                packageName,
                null,  // no permission
                logger
            )
        }

        fun getPayloadFromFileSystem(
            packageName: String,
            filePath: String?,
            logger: ILogger
        ): String? {
            var content = readFileContent(Constants.WISETRACK_PREINSTALL_FILE_SYSTEM_PATH, logger)
            if (content == null || content.isEmpty()) {
                if (filePath != null && filePath.isNotEmpty()) {
                    content = readFileContent(filePath, logger)
                }
                if (content == null || content.isEmpty()) {
                    return null
                }
            }
            return readPayloadFromJsonString(content, packageName, logger)
        }

        private fun readSystemProperty(
            propertyKey: String,
            logger: ILogger
        ): String? {
            try {
                return System.getProperty(propertyKey)
            } catch (e: Exception) {
                logger.error("Exception read system property key [$propertyKey] error [${e.message!!}]")
                logger.report("Exception read system property key [$propertyKey] error [${e.message!!}]")
            }
            return null
        }

        @SuppressLint("PrivateApi")
        private fun readSystemPropertyReflection(
            propertyKey: String,
            logger: ILogger
        ): String? {
            try {
                val classObject = Class.forName("android.os.SystemProperties")
                val methodObject = classObject.getDeclaredMethod("get", String::class.java)
                return methodObject.invoke(classObject, propertyKey) as String
            } catch (e: Exception) {
                logger.error("Exception read system property using reflection key [$propertyKey] error [${e.message}]")
                logger.report("Exception read system property using reflection key [$propertyKey] error [${e.message}]")

            }
            return null
        }

        private fun readContentProvider(
            context: Context,
            contentUri: String,
            packageName: String,
            logger: ILogger
        ): String? {
            return try {
                val contentResolver = context.contentResolver
                val uri = Uri.parse(contentUri)
                val encryptedDataColumn = "encrypted_data"
                val projection = arrayOf(encryptedDataColumn)
                val selection = "package_name=?"
                val selectionArgs = arrayOf(packageName)
                val cursor = contentResolver.query(
                    uri, projection,
                    selection, selectionArgs, null
                )
                if (cursor == null) {
                    logger.debug("Read content provider cursor null content uri [$contentUri]")
                    return null
                }
                if (!cursor.moveToFirst()) {
                    logger.debug("Read content provider cursor empty content uri [$contentUri]")
                    cursor.close()
                    return null
                }
                val payload = cursor.getString(0)
                cursor.close()
                payload
            } catch (e: Exception) {
                logger.error("Exception read content provider uri [$contentUri] error [${e.message!!}]")
                logger.report("Exception read content provider uri [$contentUri] error [${e.message!!}]")
                null
            }
        }

        private fun readContentProviderIntentAction(
            context: Context,
            packageName: String,
            permission: String?,
            logger: ILogger
        ): List? {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                val providers = context.packageManager
                    .queryIntentContentProviders(
                        Intent(Constants.WISETRACK_PREINSTALL_CONTENT_PROVIDER_INTENT_ACTION), 0
                    )
                val payloads: MutableList = ArrayList()
                for (provider in providers) {
                    var permissionGranted = true
                    if (permission != null) {
                        val result = context.packageManager.checkPermission(
                            permission, provider.providerInfo.packageName
                        )
                        if (result != PackageManager.PERMISSION_GRANTED) {
                            permissionGranted = false
                        }
                    }
                    if (permissionGranted) {
                        val authority = provider.providerInfo.authority
                        if (authority != null && authority.isNotEmpty()) {
                            val contentUri: String = String.format(
                                "content://%s/%s",
                                authority, Constants.WISETRACK_PREINSTALL_CONTENT_URI_PATH
                            )
                            val payload =
                                readContentProvider(context, contentUri, packageName, logger)
                            if (payload != null && payload.isNotEmpty()) {
                                payloads.add(payload)
                            }
                        }
                    }
                }
                if (payloads.isNotEmpty()) {
                    return payloads
                }
            }
            return null
        }

        private fun readFileContent(filePath: String, logger: ILogger): String? {
            val file = File(filePath)
            if (file.exists() && file.isFile && file.canRead()) {
                try {
                    val length = file.length().toInt()
                    if (length <= 0) {
                        logger.debug("Read file content empty file")
                        return null
                    }
                    val bytes = ByteArray(length)
                    val fileInputStream = FileInputStream(file)
                    try {
                        fileInputStream.read(bytes)
                    } catch (e: Exception) {
                        logger.error("Exception read file input stream error [${e.message!!}]")
                        logger.report("Exception read file content error [${e.message!!}]")
                        return null
                    } finally {
                        fileInputStream.close()
                    }
                    return String(bytes)
                } catch (e: Exception) {
                    logger.error("Exception read file content error [${e.message!!}]")
                    logger.report("Exception read file content error [${e.message!!}]")
                }
            }
            return null
        }

        private fun readPayloadFromJsonString(
            jsonString: String,
            packageName: String,
            logger: ILogger
        ): String? {
            try {
                val jsonObject = JSONObject(jsonString.trim { it <= ' ' })
                return jsonObject.optString(packageName)
            } catch (e: Exception) {
                logger.error("Exception read payload from json string error [${e.message!!}]")
                logger.report("Exception read payload from json string error [${e.message!!}]")
            }
            return null
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy