This guide explains how to integrate and use the SDK in an Android project.
- 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
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"
}
}
}
}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
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"...The SDK provides AntivirusPermissionManager to handle storage permissions automatically. This is
the recommended approach for managing permissions in your app.
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)
}
}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()
}
}- Automatic Lifecycle Management: Automatically releases resources when activity/fragment is destroyed
- Google Play Compliance: Handles disclosure dialog requirements for
MANAGE_EXTERNAL_STORAGEpermission - 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
-
Google Play Compliance: For apps targeting API 30+, you must show a disclosure dialog before requesting
MANAGE_EXTERNAL_STORAGEpermission. TheAntivirusPermissionManagerhandles this automatically through theonStoragePermissionDisclosureDialogRequestedcallback. -
Disclosure Dialog: You must implement the disclosure dialog in
onStoragePermissionDisclosureDialogRequestedand callrequest.proceed()when the user confirms. Record a video of this dialog and scan start for Google Play submission. -
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)
}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"))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())scanner.scanAll { progress ->
_scanProgress.value = progress
}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.
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()
}
}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.
// 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...
}- 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
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
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.