Skip to content

Instantly share code, notes, and snippets.

@Oleg-Beloy
Last active December 3, 2025 14:07
Show Gist options
  • Select an option

  • Save Oleg-Beloy/4f79bb869c3156f2a5e36930ce640a75 to your computer and use it in GitHub Desktop.

Select an option

Save Oleg-Beloy/4f79bb869c3156f2a5e36930ce640a75 to your computer and use it in GitHub Desktop.
PointWild Android SDK Readme

PointWild Antivirus Android SDK

Build Antivirus

SDK Quick Start Guide

This guide explains how to integrate and use the SDK in an Android project.

0. API

  • Modern SDK and recommended api with coroutines support, a preferable way to use in a kotlin app
  • Compat api wrapper for java and kotlin apps, which do not support coroutines
  • See scan modes for details about scan modes

1. Installation

Add the SDK to your build.gradle:

dependencies {
    implementation("com.pointwild:avsdk:{RELEASE_VERSION}")
}

add our repo in settings.gradle file:

pluginManagement {
    repositories {
        google()
        mavenCentral()
        maven {
            url =  uri("https://aura.jfrog.io/artifactory/MaxSecureAndroid")
            credentials {
                username = "username"
                password = "XXXXXXXX"
            }
        }
    }
}

2. Initialization and authentication

Pass company license as param to the init api:

scanner.initialiseSdk("eyJwYXlsb2FkIjoiZXdvZ0lDSnNhV05sYm5ObFgzUjVjR1VpT2lBaVkyOXRjR0Z1ZVNJc0NpQWdJbUpsWjJsdVgyUmhkR1ZmWjIxMElqb2dJakl3TWpVdE1EY3RNekJVTVRJNk1qVTZNakF1TnpnNE5UUTJNVGN6V2lJc0NpQWdJbVY0Y0dseVlYUnBiMjVmWkdGMFpWOW5iWFFpT2lBaU1qQXlOaTB3Tnkwek1GUXhNam95TlRveU1DNDNPRGcxTkRZek16bGFJaXdLSUNBaVlXUmthWFJwYjI1aGJGOXdZWGxzYjJGa0lqb2diblZzYkN3S0lDQWlZV1JrYVhScGIyNWhiRjlpWVhObE5qUWlPaUFpWlhsS2FtSXlVbXhZTTA1d1dqSTFabU16Vm1saGJWWnFaRU5KTmtsc1FsaFJNbFo1WkVOSmMwbHRUblppV0VKb1ltNXNabUp0Um5SYVUwazJTV3hDZG1GWE5UQldNbXh6V2tOSmMwbHRWblJaVjJ4elNXcHZhVmt5T1hWa1IwWnFaRVZDZDJJeWJIVmtTR1J3WWtkUmRWa3lPWFJKYVhkcFlsZEdORmd5VW14a2JXeHFXbGhOYVU5cVJYZE5SRUYzVEVOS2RGcFlVbWhhUjBZd1dWTkpOa2xwU2praUNuMD0iLCJzaWduYXR1cmUiOiJmaUE2elZxTFN5VmpsWjRYKzFrN3EyOTF5NXlld1IwR0lMYUorTmVpVEFlK3lCWkdXTzR6YUR2SC9iV0xhWitHam1YS3ZGb0ZFQ1FPT1ZKTWhDNGdEUT09Iiwia2V5X2lkIjoid3FyZE16d2JwQTBxa0Roa1V3VHRNMEpNNEt6S01La0x2V3NOQlByYUhpYz0ifQ==")

To get the company license, share with us SHA-256 of your app's signing certificate. You can get it, using the following command:

keytool -exportcert -keystore <yourkeystore.jks> -alias <mykey> -storepass <storepass> | openssl sha256 -binary | openssl base64

it should be your signing certificate of an app.

debug {
    signingConfig signingConfigs.debug
}
release {
    signingConfig signingConfigs.release
}

So, if you have different certificates for debug and prod versions of an app, you’ll need two keys to be generated and used correspondingly

3. Permissions

Handle permissions and add them to your AndroidManifest.xml:

...
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" /><uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" /><uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />

<application android:requestLegacyExternalStorage="true"
android:requestRawExternalStorageAccess="true"...

Using AntivirusPermissionManager

The SDK provides AntivirusPermissionManager to handle storage permissions automatically. This is the recommended approach for managing permissions in your app.

Basic Setup

import com.pointwild.avsdk.permission.AntivirusPermissionManager
import com.pointwild.avsdk.permission.PermissionCallback
import com.pointwild.avsdk.permission.PermissionRequest

class YourActivity : ComponentActivity(), PermissionCallback {

    private val permissionManager = AntivirusPermissionManager.create()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Attach permission manager to activity
        permissionManager.attachTo(this, this)
    }

    override fun onDestroy() {
        super.onDestroy()
        permissionManager.release()
    }

    // Implement PermissionCallback
    override fun onPermissionResult(requestCode: Int, isGranted: Boolean) {
        if (isGranted) {
            // Permissions granted, proceed with scanning
            when (requestCode) {
                R.id.btnScanAll -> startScanAll()
                R.id.btnScanFiles -> startScanFiles()
                // Handle other scan types
            }
        } else {
            // Show error message to user
            Toast.makeText(this, "Storage permissions required for scanning", Toast.LENGTH_SHORT)
                .show()
        }
    }

    override fun onStoragePermissionDisclosureDialogRequested(request: PermissionRequest) {
        // Show disclosure dialog explaining why permissions are needed
        MaterialAlertDialogBuilder(this)
            .setTitle("Storage Access Required")
            .setMessage("This app needs access to storage to scan files for viruses and malware.")
            .setPositiveButton("Continue") { _, _ ->
                request.proceed() // Proceed with permission request
            }
            .setNegativeButton("Cancel") { _, _ ->
                // Handle user cancellation
            }
            .show()
    }

    private fun requestScanPermissions(requestCode: Int) {
        permissionManager.requestStoragePermission(requestCode, false)
    }
}

Using in Fragment

class YourFragment : Fragment(), PermissionCallback {

    private val permissionManager = AntivirusPermissionManager.create()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Attach permission manager to fragment
        permissionManager.attachTo(this, this)
    }

    override fun onDestroy() {
        super.onDestroy()
        permissionManager.release()
    }

    // Implement PermissionCallback interface
    override fun onPermissionResult(requestCode: Int, isGranted: Boolean) {
        if (isGranted) {
            // Handle successful permission grant
            when (requestCode) {
                R.id.btnScanAll -> viewModel.startScanAll()
                R.id.btnScanFiles -> viewModel.startScanFiles()
            }
        }
    }

    override fun onStoragePermissionDisclosureDialogRequested(request: PermissionRequest) {
        // Show disclosure dialog
        MaterialAlertDialogBuilder(requireContext())
            .setTitle("Storage Access Required")
            .setMessage("This app needs access to storage to scan files for viruses and malware.")
            .setPositiveButton("Continue") { _, _ ->
                request.proceed()
            }
            .setNegativeButton("Cancel", null)
            .show()
    }
}

Key Features

  • Automatic Lifecycle Management: Automatically releases resources when activity/fragment is destroyed
  • Google Play Compliance: Handles disclosure dialog requirements for MANAGE_EXTERNAL_STORAGE permission
  • Cross-Platform Support: Works with both legacy storage permissions (API < 30) and scoped storage (API ≥ 30)
  • Request Code Support: Allows different actions to be triggered based on permission request source

Important Notes

  1. Google Play Compliance: For apps targeting API 30+, you must show a disclosure dialog before requesting MANAGE_EXTERNAL_STORAGE permission. The AntivirusPermissionManager handles this automatically through the onStoragePermissionDisclosureDialogRequested callback.

  2. Disclosure Dialog: You must implement the disclosure dialog in onStoragePermissionDisclosureDialogRequested and call request.proceed() when the user confirms. Record a video of this dialog and scan start for Google Play submission.

  3. Permission Check: Use areStoragePermissionsGranted() to check if permissions are already granted before requesting them.

if (permissionManager.areStoragePermissionsGranted()) {
    // Permissions already granted, proceed with scanning
    startScanning()
} else {
    // Request permissions first
    permissionManager.requestStoragePermission(R.id.btnScan, false)
}

4. Scan files

Use this api(s) for scanning single or multiple files:

val files = listOf<File>()
val result = scanner.scanFile(files.first())
// other possible usages
scanner.scanFiles(files)
scanner.scanFiles(File("/storage/emulated/0/Download"))

5. Scan apps

Use this api(s) for scanning single or multiple apps:

val result = scanner.scanInstalledApps { progress ->
    _scanProgress.value = progress
}
// other possible usages
val apps: List<ApplicationInfo> = listOf(...)
scanner.scanApps(apps)
scanner.scanApp(apps.first())

6. Scan all

scanner.scanAll { progress ->
    _scanProgress.value = progress
}

7. Using SignatureUpdateListener

The SDK provides SignatureUpdateListener to monitor virus signature database updates and initialization states. This is essential for tracking the status of signature downloads and ensuring your app can provide proper user feedback during updates. SDK synchronise with signatures automatically when a scan is requested.

Basic Setup

import com.pointwild.avsdk.contract.SignatureUpdateListener
import com.pointwild.avsdk.contract.SignaturesInitialisationState
import com.pointwild.avsdk.contract.SignaturesVersion

class YourActivity : ComponentActivity() {

    private lateinit var scanner: AntivirusSdkCompat

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        scanner = AntivirusSdkCompat.build()

        // Set up signature update listener
        setupSignatureUpdateListener()
    }

    private fun setupSignatureUpdateListener() {
        scanner.setSignaturesUpdateListener(object : SignatureUpdateListener {
            override fun onSignatureUpdateStateChange(state: SignaturesInitialisationState) {
                when (state) {
                    is SignaturesInitialisationState.NotInitialized -> {
                        // No signatures database exists yet
                        showMessage("No virus signatures found")
                    }

                    is SignaturesInitialisationState.Initialised -> {
                        // Signatures database exists but may need updating
                        showMessage("Signatures database ready")
                    }

                    is SignaturesInitialisationState.InProgress -> {
                        // Signature update in progress
                        val progress = (state.progress * 100).toInt()
                        showMessage("Downloading virus signatures: $progress%")
                        updateProgressBar(state.progress)
                    }

                    SignaturesInitialisationState.Paused -> {
                        // Update is paused waiting for network connection
                        showMessage("Waiting for network connection...")
                    }

                    is SignaturesInitialisationState.Ready -> {
                        // Signatures are up to date and ready
                        val version = state.vdfVersion
                        showMessage("Signatures updated: ${version.versionName}")
                        enableScanning()
                    }

                    is SignaturesInitialisationState.Error -> {
                        // Error occurred during signature update
                        showError("Failed to update signatures: ${state.error.message}")
                    }
                }
            }
        })
    }

    private fun requestSignatureUpdate() {
        scanner.requestUpdateSignatures()
    }
}

Signature States Explained

The SignaturesInitialisationState provides detailed information about the current state of virus signatures:

  • NotInitialized: No signatures database exists on the device. First-time setup required.

  • Initialised: Signatures database exists but may be outdated. Ready for scanning with existing signatures.

  • InProgress(progress: Double): Signature update is currently downloading. Progress ranges from 0.0 to 1.0.

  • Paused: Signature update is paused waiting for internet connection to be restored. The update will automatically resume when network connectivity is reestablished.

  • Ready(vdfVersion: SignaturesVersion): Signatures are up to date and ready for scanning. Contains version information.

  • Error(error: Throwable): An error occurred during signature update. Contains the error details.

Signature Version Information

// Access signature version information
when (val state = signatureUpdateStatus.value) {
    is SignaturesInitialisationState.Ready -> {
        val version = state.vdfVersion
        val versionId = version.id          // Incremental version ID
        val versionName = version.versionName // Human-readable version string

        println("Signature version: $versionName (ID: $versionId)")
    }
    // Handle other states...
}

Key Features

  • Real-time Progress Tracking: Monitor download progress with precise percentage updates
  • Error Handling: Get detailed error information when signature updates fail
  • Version Information: Access current signature database version details
  • State Management: Track the complete lifecycle of signature initialization and updates

8. [Optional] SDK configuration

scanner = AntivirusSdkCompat.build()
// configure sdk
scanner.configureSdk(
    ScannerConfiguration(
        shouldScanSystemApps = true,
        skipStrategy = SkipStrategy.NONE,
        enableZipScanning = true,
        logLevel = LogLevel.VERBOSE,
        scanApkSizeLimit = 3 * 1024 * 1024,
        zipScanSizeLimit = 50 * 1024 * 1024 // 50MB limit
    )
)

Or you can use our default settings

10. Demo app

See demo app where features of the SDK are showcased. You can build it on your own or use a compiled sample which comes with a github release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment