Scan Process
The scanning process happens entirely through the TireTreadScanView
and should be performed in Landscape mode.
The TireTreadSDK
allows for a variety of options on how to set up the scan process. In the following sections we will show the most convenient and quick options to get started with scanning.
Setup with JSON config
The most convenient way to configure the TireTreadScanView
is via a JSON config located in your application’s asset folder. You can also use the full JSON string as an input.
The file name and the name used for the JSON config must be identical. |
-
Compose
-
XML + ViewBinding
@Composable
private fun Content() {
// You can pass the tire width as an integer
val tireWidth = 200
TireTreadScanView(context = this@ScanActivity,
config = "scan_config.json", //you can also use the full JSON string as an input!
tireWidth = tireWidth,
callback = object : TireTreadScanViewCallback {
// handle callback methods
}) {
// handle possible errors
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<io.anyline.tiretread.sdk.scanner.TireTreadScanView
android:id="@+id/tireTreadScanView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:scanConfig="scan_config.json" />
</androidx.constraintlayout.widget.ConstraintLayout>
Setup with a config object
If there is a need for a more fine grained or dynamic configuration in code the scan process can be set up via a configuration class.
-
Compose
-
ViewBinding
@Composable
private fun Content() {
// You can pass the tire width as an integer
val tireWidth = 200
TireTreadScanView(context = this@ScanActivity,
config = TireTreadScanViewConfig().apply {
defaultUiConfig = DefaultUiConfig().apply {
tireOverlayConfig = TireOverlayConfig().apply {
visible = true
}
howToScanTooltipConfig = HowToScanTooltipConfig().apply {
visible = false
}
lineProgressBarConfig = LineProgressBarConfig().apply {
visible = true
}
// ...
}
measurementSystem = MeasurementSystem.Imperial
scanSpeed = ScanSpeed.Fast
},
tireWidth = tireWidth,
callback = object : TireTreadScanViewCallback {
// handle callback methods
}) {
}
}
binding.tireTreadScanView.apply {
// You can pass the tire width as an integer
val tireWidth = 200
withScanConfig(tireTreadScanViewConfig = TireTreadScanViewConfig().apply {
defaultUiConfig = DefaultUiConfig().apply {
tireOverlayConfig = TireOverlayConfig().apply {
visible = true
}
howToScanTooltipConfig = HowToScanTooltipConfig().apply {
visible = false
}
lineProgressBarConfig = LineProgressBarConfig().apply {
visible = true
}
// ...
}
measurementSystem = MeasurementSystem.Imperial
scanSpeed = ScanSpeed.Fast
},
tireWidth = tireWidth
)
scanViewCallback = object : TireTreadScanViewCallback {
// handle callback methods
}
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnLifecycleDestroyed(
this@ScanActivity
)
)
}
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 tocenter
, 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 through 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. |
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.
Enabling volume keys to start scanning
Optionally can override the functionality of the phone’s volume buttons to start and stop the scan by adding this function to your Activity code.
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
return when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (event.action == ACTION_UP) return true
if (TireTreadScanner.instance.isScanning) {
TireTreadScanner.instance.stopScanning()
} else {
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()
}
}
true
}
else -> super.dispatchKeyEvent(event)
}
}
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)
val measurementSystem = if (PreferencesUtils.shouldUseImperialSystem(this@ScanActivity)) {
MeasurementSystem.Imperial
} else {
MeasurementSystem.Metric
}
// newDistance will be in millimeters if you choose Metric, or in inches, if you choose Imperial
val distanceString: String = if (measurementSystem == MeasurementSystem.Imperial) {
"$newDistance 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.