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 directly to your Layout.xml file, or via code.

Layout

To include the TireTreadScanView in 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 in a Layout, 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).

Code

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

val ttrScanViewConfig = TireTreadScanViewConfig.Builder()
    // .withMeasurementSystem(MeasurementSystem.Metric)
    // .withCustomDefaultUiConfig(customUiConfg)
    // .disableDefaultHaptic()
    // .disableDefaultUi()
    // .addAdditionalContext(additionalContext)
    .build()

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

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

When initializing the TireTreadScanView via code, you have the possibility to set the behavior, appearance, and metadata of the scanning process. For that, you can use the following options:

  • withMeasurementSystem - MeasurementSystem.Metric or MeasurementSystem.Imperial

  • withCustomDefaultUiConfig - allows the customization of the Default UI

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

  • disableDefaultUi - if set, the scan view will not display the default UI elements (except the Camera Preview).

  • addAdditionalContext - provides additional context to a scan. Check below for more information.

Additional Context

The AdditionalContext is an optional property which allows you to provide more context to a scan. This makes sense in a workflow, where a scan is connected to other TireTread scans or other information in a larger context. Its initialization requires a TirePosition.

The TirePosition identifies the position where the tire is mounted on the vehicle, considering the primary direction of travel. E.g. the front left tire of a passenger car would be specified as axle=1, side=TireSide.Left, position_on_axle=1 and the rear right tire of a passenger car as axle=2, side=TireSide.Right, position_on_axle=1

The TirePosition's parameters are as follows:

  • axle: Number of the axle, whereas 1 is the front axle (in direction of travel). Its value must be at least 1.

  • side: Enum, representing the side where the Tire is located (Left, Center, Right).

  • positionOnAxle: Position on the axle, whereas 1 is the outermost tire and higher values represent the following inner tires. If the side is set to center, 1 is the leftmost tire (in direction of travel), if there are multiple tires on the center axle. Its value must be at least 1.

If the AdditionalContext is provided, it will also be returned with the results in the TreadDepthResult object, in the measurement property.

TirePosition initialization will throw an IllegalArgumentException if axle or positionOnAxle are 0.

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 TireTreadScanner.instance also provides the captureDistanceStatus property, with which your application can check if the user is positioning the device at the correct distance from the tire. This property can be used to prevent scans from initiating at an incorrect distance, e.g.:

if (TireTreadScanner.instance.captureDistanceStatus == DistanceStatus.OK)
    TireTreadScanner.instance.startScanning()
else {
    Toast.makeText(
        this,
        "Move the phone to the correct position before starting.",
        Toast.LENGTH_SHORT
    ).show()
}

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.