Skip to content

Instantly share code, notes, and snippets.

@Oleg-Beloy
Created February 2, 2026 16:00
Show Gist options
  • Select an option

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

Select an option

Save Oleg-Beloy/93bf066995e3da0a1375156dbab7c394 to your computer and use it in GitHub Desktop.
Documentation Android AV SDK 2.2.1
# PointWild Antivirus Android SDK
[![Build Antivirus](https://github.com/AnchorFree/android-av-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/AnchorFree/android-av-sdk/actions/workflows/ci.yml)
# SDK Quick Start Guide
This guide explains how to integrate and use the SDK in an Android project.
## 0. API
* [Modern SDK](ScannerSDK/src/main/java/com/pointwild/avsdk/contract/AntivirusSdk.kt) and recommended api with
coroutines support, a preferable way to use in a kotlin app
* [Compat api](ScannerSDK/src/main/java/com/pointwild/avsdk/contract/AntivirusSdkCompat.kt) wrapper for java
and kotlin apps, which do not support coroutines
* See [scan modes](ScannerSDK/src/main/java/com/pointwild/avsdk/contract/ScanMode.kt) for details
about scan modes
## 1. Installation
Add the SDK to your `build.gradle`:
```gradle
dependencies {
implementation("com.pointwild:avsdk:{RELEASE_VERSION}")
}
```
### Aura Jfrog remote repository
For those, who have private repository credentials, add the following to the end of `repositories`
section:
```gradle
pluginManagement {
repositories {
...
maven {
url = uri("https://aura.jfrog.io/artifactory/MaxSecureAndroid")
credentials {
username = "username"
password = "XXXXXXXX"
}
}
}
}
```
### Local maven repository
* Get avsdk-x.x.x.zip
* Unpack to project root. The resulting folders should have `build/avsdk/com/pointwild/..` structure
* add the following to the end of `repositories` section:
Add
```gradle
pluginManagement {
repositories {
...
maven {
url = uri("${rootDir}/build/avsdk")
}
}
}
```
## 1.1 Dependency Requirements
The SDK has the following dependency constraints:
- **Min SDK**: 23 (Android 6.0)
- **OkHttp**: 4.12.0
- **AGP (Android Gradle Plugin)**: 8.13.2
- **Kotlin**: 2.2.21
Ensure your project meets these requirements for compatibility.
If you need changes here, let us know.
## 2. Initialization and authentication
### 2.1 Company license
Pass company license as param to the init api:
```kotlin
scanner.initialiseSdk("YOUR_COMPANY_LICENSE")
```
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.
``` groovy
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
### 2.2 Pango auth
To enable user authentication, configure Pango Auth first.
Create a `PangoAuthConfiguration` with your credentials and pass it to the SDK:
```kotlin
val authConfig = PangoAuthConfiguration(
apiKey = "YOUR_API_KEY",
productName = "YOUR_PRODUCT_NAME"
)
scanner.initialiseSdk(authConfig)
```
After successful initialization, the SDK is ready to authenticate users.
Use the signIn method to authenticate a user with their credentials:
```kotlin
scanner.signIn(
username = "username",
password = "password"
)
```
To sign out the currently authenticated user, call:
```kotlin
scanner.signOut()
```
## 3. Permissions
Handle permissions and add them to your AndroidManifest.xml:
```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.
#### 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
#### Basic Setup
```kotlin
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
```kotlin
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()
}
}
```
#### 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.
```kotlin
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:
```kotlin
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:
```kotlin
val result = scanner.scanInstalledApps { progress ->
_scanProgress.value = progress
}
// other possible usages
val apps: List<ApplicationInfo> = listOf(...)
scanner.scanApps(apps)
scanner.scanApp(apps.first())
```
### Using AppUninstaller
The SDK provides optional `AppUninstaller` to handle and simplify uninstallation of malicious apps.
It uses the system
uninstall dialog with user consent.
When it is setup, a client can call `appUninstaller.uninstall(maliciousApp)` or
`appUninstaller.uninstall(packageName)`
#### Key Features
- **User Consent**: Opens system uninstall dialog requiring user confirmation
- **Result Callbacks**: Detects successful uninstall, user cancellation, and failures
- **No Special Permissions**: Works without `REQUEST_DELETE_PACKAGES` permission
- **Automatic Lifecycle Management**: Automatically releases resources when activity/fragment is
destroyed
#### Basic Setup (Can be also in a fragment)
```kotlin
import com.pointwild.avsdk.uninstaller.AppUninstaller
import com.pointwild.avsdk.uninstaller.UninstallCallback
class YourActivity : ComponentActivity(), UninstallCallback {
private val appUninstaller = AppUninstaller.create()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Attach uninstaller to activity
appUninstaller.attachTo(this, this)
}
override fun onDestroy() {
super.onDestroy()
appUninstaller.release()
}
// Implement UninstallCallback
override fun onSuccess(packageName: String) {
Toast.makeText(this, "App uninstalled: $packageName", Toast.LENGTH_SHORT).show()
}
override fun onUserCanceled(packageName: String) {
Toast.makeText(this, "Uninstall cancelled: $packageName", Toast.LENGTH_SHORT).show()
}
override fun onFailure(packageName: String, status: Int, message: String?) {
Toast.makeText(this, "Uninstall failed: $message", Toast.LENGTH_SHORT).show()
}
private fun uninstallApp(maliciousApp: DetectedThreat.MaliciousApp) {
appUninstaller.uninstall(maliciousApp)
}
private fun uninstallByPackageName(packageName: String) {
appUninstaller.uninstall(packageName)
}
}
```
#### Important Notes
1. **Attachment Required**: Call `attachTo()` in `onCreate()` before `super.onCreate()` to register
the activity result launcher before the lifecycle reaches STARTED state.
2. **User Consent**: The uninstall dialog is handled by the Android system. The user must confirm
the uninstallation.
3. **Google Play Compliance**: This feature requires having
`android.permission.REQUEST_DELETE_PACKAGES`.
It must be then disclosed on google play
## 6. Scan all
```kotlin
scanner.scanAll { progress ->
_scanProgress.value = progress
}
```
## 7. Scan Progress Tracking
The SDK provides real-time progress updates through the `ScanProgress` data class, which offers
comprehensive information about the current scanning state. This enables you to build rich user
interfaces that show exactly what's being scanned and the overall progress.
### Key Features
- **Real-time Entry Tracking**: See which entries are currently being scanned, not just completed
ones
- **Progress Percentage**: Accurate percentage calculation based on total entries including online
scan overhead
- **Threat Detection Count**: Track how many infected entries have been detected on a go
### ScanProgress Properties
```kotlin
data class ScanProgress(
val scannedEntry: ScanEntry? = null, // Last completed entry
val scanningEntries: Set<ScanEntry> = emptySet(), // Currently scanning entries
val scanCount: Int, // Number of entries scanned so far
val infectedEntryCount: Int, // Number of infected entries found
val totalScanSize: Int, // Total number of entries to scan
val percent: Int // Progress percentage (0-100)
)
```
### Understanding ScanEntry
`ScanEntry` represents an entry being scanned and can be either:
- **`ScanEntry.AppEntry`**: An installed application being scanned
- **`ScanEntry.FileEntry`**: A file being scanned
Both types provide a `title` property for display purposes:
- `AppEntry.title`: Returns the package name
- `FileEntry.title`: Returns the file name
### Progress Update Behavior
The SDK emits progress updates in 3 scenarios:
1. **When Starting to Scan an Entry**: Progress is emitted immediately when scanning begins, showing
the entry in `scanningEntries`. This is especially useful for large entries (like multidex apps)
that take time to scan, as users see activity right away.
2. **When Finishing an Entry**: Progress is emitted when an entry completes, moving it from
`scanningEntries` to `scannedEntry`.
3. **When Online Scan is Done**: Progress is emitted when a chunk of entries is scanned online
### Best Practices
1. **Display Scanning Entries**: Show `scanningEntries` to users so they know what's currently being
processed, especially for long-running scans
2. **Update UI Frequently**: Progress callbacks are called frequently; debounce UI updates if needed
for performance
3. **Show Both States**: Display both completed (`scannedEntry`) and in-progress (`scanningEntries`)
entries for best user experience
## 8. Signatures update
Signatures database is downloaded on a first scan or if `requestUpdateSignatures` is called.
It is used to compare signatures of apps & files under scan with known malicious signatures
database.
For reaching maximum protection reasons a scan makes an incremental signatures update, if new
signatures are available.
If server signatures version exceeds local version on **30** versions or more, then signatures
database
is redownloaded.
For performance reasons in the same logic we introduced a threshold that signatures update could be
done only once a day.
On the other hand, `requestUpdateSignatures` is needed in case a client app wants to preload
signatures, silently on an app launch, for example. To speed up a next scan, which will wait for an
ongoing update.
If `requestUpdateSignatures` is called, SDK compares `getVdfLocalVersion` and `getVdfServerVersion`
on its own, and an incremental update will be made only if new signatures are available
### 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.
#### 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
#### Basic Setup
```kotlin
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
```kotlin
// 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...
}
```
## 9. [Optional] SDK configuration
```kotlin
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. [Optional] Realtime Scan Service
The SDK provides a realtime scanning service that monitors app installations and file system
changes. This feature requires a **foreground service** with a persistent notification (Android
system requirement).
### Scanning Modes
- **App Install Monitoring**: Scans newly installed apps in real-time, event-based approach
- **File System Monitoring**: Global observer that recursively scans all changed files every 4 hours
The scanning mode is configurable via `RealtimeScanConfig`.
### Enabling Realtime Scan
Use `RealtimeScanSdk` to activate and configure the feature:
```kotlin
import com.pointwild.avsdk.contract.RealtimeScanSdk
import com.pointwild.avsdk.contract.RealtimeScanConfig
import com.pointwild.avsdk.contract.RealtimeScanMode
// Get SDK instance (implements RealtimeScanSdk)
val scanner = AntivirusSdkCompat.build()
// Optional: Configure notification and scan mode before enabling
scanner.configureRealtimeScan(
RealtimeScanConfig(
scanMode = RealtimeScanMode.APPS_ONLY, // or RealtimeScanMode.ALL for apps + files
notificationTitleRes = R.string.your_notification_title,
notificationTextRes = R.string.your_notification_text,
smallIconRes = R.drawable.your_notification_icon,
notificationClickAction = "com.example.app.ACTION_OPEN_SCAN_STATUS" // optional
)
)
// Enable realtime scanning
scanner.enableRealtimeScan(true)
// Check if enabled
val isEnabled = scanner.isRealtimeScanEnabled()
// Disable realtime scanning
scanner.enableRealtimeScan(false)
```
### Receiving Scan Results
Create a receiver extending `RealtimeScanResultBroadcastReceiver`:
```kotlin
class MyRealtimeScanReceiver : RealtimeScanResultBroadcastReceiver() {
override fun onRealtimeScanResult(
context: Context,
entry: ScanEntry,
result: ScanResult
) {
when (entry) {
is ScanEntry.AppRealtimeEntry -> {
// Handle app scan result
val packageName = entry.applicationInfo.packageName
}
is ScanEntry.FileRealtimeEntry -> {
// Handle file scan result
val filePath = entry.path
}
}
when (result) {
is ScanResult.Success -> {
if (result.detectedThreats.isNotEmpty()) {
// Threat detected!
}
}
is ScanResult.Error -> {
// Handle error
}
}
}
}
```
Register it in your `AndroidManifest.xml`:
```xml
<receiver android:name=".MyRealtimeScanReceiver" android:enabled="true" android:exported="false">
<intent-filter>
<action android:name="com.pointwild.avsdk.REALTIME_SCAN_RESULT" />
</intent-filter>
</receiver>
```
### Manifest Configuration
Since the realtime scan components are not included in the SDK manifest by default, you must add the
following entries to your app's `AndroidManifest.xml`:
```xml
<!-- Boot receiver to restart realtime scanning service after system reboot -->
<receiver
android:name="com.pointwild.avsdk.realtime.RealtimeScanBootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
<!-- Foreground service for realtime app/file scanning -->
<service
android:name="com.pointwild.avsdk.realtime.RealtimeScanService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="realtime-device-security-app-install-filesystem-change-monitor" />
</service>
```
### Important Notes
1. **Foreground Service Requirement**: The realtime scanning logic must have the foreground service
running and display a notification to the user — this is an Android system requirement. Covered
by SDK
2. **Boot Receiver**: Ensures the realtime scanning service automatically restarts after device
reboot or app update.
3. **Performance**: Deep performance testing for file system monitoring has not been conducted. For
lighter resource usage, consider using app install scanning mode only.
## 11. Demo app
See [demo app](demoApp) 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