Scan Process

The scanning process happens entirely through the TireTreadScanView and should be performed in Landscape mode.

The TireTreadScanView

To start using the Anyline Tire Tread SDK for your measurements, you will need the TireTreadScanView. This view can be added, for example, directly to your Layout.xml file.

To include the TireTreadScanViewin your Layout.xml file, add the io.anyline.tiretread.sdk.scanner.TireTreadScanView view in the appropriated hierarchy, e.g.:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black"
    android:backgroundTint="@color/black"
    tools:context=".activities.ScanActivity">

    <io.anyline.tiretread.sdk.scanner.TireTreadScanView
        android:id="@+id/tireTreadScanView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:measurementSystem="metric" />

</androidx.constraintlayout.widget.ConstraintLayout>

To configure the TireTreadScanView, you can use the following attributes:

  • app:measurementSystem - metric or imperial

  • app:disableDefaultHaptic - if true, the SDK will not make use of the device’s haptic during the scanning process to provide feedback to the users.

  • app:disableDefaultUi - if true, the scan view will not display any UI elements (except the Camera Preview).

To instantiate the ScanView via code, you can use the following snippet:

val ttrScanViewConfig = TireTreadScanViewConfig.Builder()
    // .withMeasurementSystem(MeasurementSystem.Metric)
    // .disableDefaultHaptic()
    // .disableDefaultUi()
    .build()

TireTreadScanView(context = this, config = ttrScanViewConfig, callback = this, onInitializationFailed = { ex -> /* handle */ })

This method can only be used from the context of a @Composable function.

Audio Feedback

The Tire Tread SDK can provide audio feedback to guide users throught the scan process.

To make use of these audio feedbacks, your application needs to provide (and thus can customize) the audio files inside its Assets folder.

The audio feedbacks (with their respective files names) on Android are played on:

  • Scan Started

    • tiretread_sound_start.wav

  • Scan Stopped

    • tiretread_sound_stop.wav

  • Phone too close to the Tire

    • tiretread_sound_beep_too_close.wav

  • Phone too distant from the Tire

    • tiretread_sound_beep_too_far.wav

The SDK only supports these file names, and the .wav extension.

An example implementation, and the example audio files, can be found in our GitHub Example implementation.

To disable the audio feedback, remove these audio files from the applications' Assets folder or rename them.


If you have issues with the disposal of the TireTreadScanView, add the following lines to your Activity’s code, to define the View Composition Strategy:

override fun onCreate(savedInstanceState: Bundle?) {

    ...

    val myTireTreadScanView = findViewById<TireTreadScanView>(R.id.myTireTreadScanView)
    myTireTreadScanView.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(this))

    ...
}
Make sure that your "ScanActivity" runs in Landscape mode.

Start Scanning

In the previous example, we are not disabling the default UI (disableDefaultUi) in the TireTreadScanView. This means that the ScanView will draw and control all the required components for the scan process.

When using the default UI, your application only needs to implement behavior in a few callbacks from the SDK: onScanAbort, onUploadCompleted, onUploadFailed, and onUploadAborted. Jump to the Handling the SDK’s events section if you are using the default UI.

When not using the default UI, your application can define the UX of the entire scan process. For that, start by adding a button alongside with the TireTreadScanView. This button will be responsible for starting and stopping the scan process. Here is a Kotlin example with its functionality:

fun onClickedBtnStartScan(view: View) {
    val scannerInstance = TireTreadScanner.instance

    val btnStartScan = (view as Button)
    if (!scannerInstance.isScanning) {
        // Here we start scanning
        scannerInstance.startScanning()
        btnStartScan.text = "Stop"
    } else {
        btnStartScan.visibility = View.GONE
        btnStartScan.isEnabled = false
        btnStartScan.isClickable = false
        // Here we stop scanning
        scannerInstance.stopScanning()
    }
}

The scanning process can be stopped at any time. If not manually stopped, the scan process is automatically stopped after 10 seconds.

Besides the "Start/Stop" button, when not using the default UI, add also an "Abort" button (or e.g., an 'x' icon), through which the users can abort the Scan Process without waiting for the results. This button should call the TireTreadScanner.instance.abortScanning() function.

Handling the SDK’s events

The TireTreadScanViewCallback is the interface through which the TireTreadScanView communicates back to your application. With these callbacks, you can react on changes and implement your own workflow around the scan process. The callbacks available are:

  • onScanStart: Invoked when the scanning process begins

  • onScanStop: Invoked when the scanning process ends

  • onScanAbort: Invoked when the scanning process is aborted

    • Should be implemented also when using the default UI

  • onImageUploaded: Invoked after each frame is uploaded

  • onUploadCompleted: Invoked once all the frames were successfully uploaded for processing

    • Should be implemented also when using the default UI

  • onUploadAborted: Invoked when the upload process is aborted.

    • Should be implemented also when using the default UI

  • onUploadFailed: Invoked if any issue has occurred during upload

    • Should be implemented also when using the default UI

  • onDistanceChanged: Invoked whenever the distance between the device and the tire changes

    • This information serves as a guide to assist your app’s users in scanning correctly

The callback "onFocusFound" will not be invoked - and should be used - on Android.

Once your "myTireTreadScanView" object is available, add your callback implementation to it. In this example, we will use our own Activity as the callback implementation.

Implement the TireTreadScanViewCallback interface, and override the desired methods, e.g.:

// Implement the TireTreadScanViewCallback interface
class MyScanActivity : AppCompatActivity(), TireTreadScanViewCallback {

    private var aborted = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_scan)

        val myTireTreadScanView = findViewById<TireTreadScanView>(R.id.tireTreadScanView)
        myTireTreadScanView.scanViewCallback = this
    }

    // These callbacks should always be implemented
    override fun onScanAbort(uuid: String?) {
        super.onScanAbort(uuid)
        Log.d("MY_APP", "scan was aborted!")
        aborted = true
        // Leave the scan activity
        finish()
    }

    override fun onUploadCompleted(uuid: String?) {
        super.onUploadCompleted(uuid)
        Log.d("MY_APP", "upload is completed!")

        // redirect to the result loading screen
        if (!aborted && !uuid.isNullOrEmpty()) {
            openLoadAndResultScreen(uuid)
        }
    }

    override fun onUploadAborted(uuid: String?) {
        super.onUploadAborted(uuid)
        mainHandler.post {
            Toast.makeText(applicationContext, "Upload Aborted", Toast.LENGTH_SHORT).show()
        }
        finish()
    }

    override fun onUploadFailed(uuid: String?, exception: Exception) {
        super.onUploadFailed(uuid, exception)
        Log.e("MY_APP", "onUploadFailed: ", exception)

        Toast.makeText(applicationContext, "Upload Failed. Check your internet and try again.", Toast.LENGTH_SHORT).show()

        finish()
    }

    // When not using the 'default UI', you will need to implement (at least) the following functions to control the scan process

    fun startScanning(view: View) {
        TireTreadScanner.instance.startScanning()
    }

    fun stopScanning() {
        TireTreadScanner.instance.stopScanning()
    }

    fun abortScan() {
        TireTreadScanner.instance.abortScanning()
        finish()
    }

    // When not using the "default UI", use this callback to provide guidance to the users
    override fun onDistanceChanged(uuid: String?, previousStatus: DistanceStatus, newStatus: DistanceStatus, previousDistance: Float, newDistance: Float,
    ) {
        super.onDistanceChanged(uuid, previousStatus, newStatus, previousDistance, newDistance)

        // newDistance will be in millimiters if you choose Metric, or in inches, if you choose Imperial
        val scanView = findViewById<TireTreadScanView>(R.id.tireTreadScanView)
        val measurementSystem = scanView.measurementSystem
        val distanceString: String = if(measurementSystem == MeasurementSystem.Imperial){
            "${inchStringToTriple(inchToFractionString(newDistance.toDouble())).first} in"
        } else {
            "${(newDistance/10).toInt()} cm"
        }

        var message = ""
        message = when (newStatus) {
            DistanceStatus.TOO_CLOSE -> {
                "Increase Distance: "
            }
            DistanceStatus.TOO_FAR -> {
                "Decrease Distance: "
            }
            else -> {
                "Distance OK"
            }
        }
        message += distanceString
        findViewById<TextView>(R.id.tvDistance).text = message
    }
}
You can check out our GitHub example repository for a full implementation of the Scan Activity.

Check out the next section to learn how to handle the measurement results after the scan process is finished.