Measurement Sync (Android)¶
Measurement sync (available since v1.16.0) automatically uploads finished test results to the Surfmeter server so they appear in the Surfmeter Dashboard. The SDK queues each completed measurement and sends it in the background. The queue is stored in a local database, so measurements survive app restarts and are retried after network failures.
See Measurement Sync (iOS) for the corresponding iOS behavior and APIs.
Note
This feature is only available for select customers who have opted to have their measurement data hosted on AVEQ's servers. Standalone builds of the SDK do not include measurement sync. They return measurements locally through the result callbacks and send no data to AVEQ except telemetry data. Contact AVEQ support if you want to enable measurement sync for your app.
Setup¶
There is no queue-specific setup. Register the device before running a test. In a server-connected build, every finished test then hands its report to the queue automatically. Your result listener still receives the same report.
Authentication is handled internally through the registered device credentials. Your app does not need to manage authentication tokens for measurement sync.
Timing¶
A measurement is queued as soon as its test finishes, and the queue schedules it for upload. Uploads pause while any quality test is running so the queue's network traffic does not distort the measurement. The SDK manages this lifecycle for its own tests.
You can check whether a test is active when implementing custom test orchestration:
Android uses WorkManager for uploads. Pending work can continue after the app leaves the foreground and is constrained to run when the device has a network connection.
Queue Status and Manual Sync¶
You can query the current queue state:
import com.aveq.qualitytestlib.queue.MeasurementQueueManager;
// Get the number of pending measurements
int pending = MeasurementQueueManager.getPendingCount(context);
// Check whether measurements are currently being sent
boolean sending = MeasurementQueueManager.isSending(context);
You can also schedule an immediate sync attempt:
The request is still subject to the network constraint and waits while a quality test is running.
If measurements are left in the sending state after a crash, reset them to pending when the app starts:
Upload Notifications¶
The SDK broadcasts local intents when a measurement is uploaded or permanently fails. Register a BroadcastReceiver if your app needs to update its UI in response.
The following broadcast actions and extras are available:
| Constant | Value | Description |
|---|---|---|
ACTION_MEASUREMENT_SENT |
com.aveq.qualitytestlib.MEASUREMENT_SENT |
Broadcast when a measurement is successfully uploaded |
ACTION_MEASUREMENT_FAILED |
com.aveq.qualitytestlib.MEASUREMENT_FAILED |
Broadcast when a measurement permanently fails to upload |
For ACTION_MEASUREMENT_SENT, the following extras are included:
| Extra | Type | Description |
|---|---|---|
EXTRA_MEASUREMENT_ID |
int |
The server-assigned measurement ID |
EXTRA_MEASUREMENT_TYPE |
String |
The measurement type, such as video_measurement or web_measurement |
For ACTION_MEASUREMENT_FAILED, the following extra is included:
| Extra | Type | Description |
|---|---|---|
EXTRA_ERROR_MESSAGE |
String |
Description of the failure reason |
The following example registers a receiver for both outcomes:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.aveq.qualitytestlib.queue.MeasurementSendWorker;
public class MyActivity extends AppCompatActivity {
private final BroadcastReceiver measurementReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (MeasurementSendWorker.ACTION_MEASUREMENT_SENT.equals(action)) {
int measurementId = intent.getIntExtra(
MeasurementSendWorker.EXTRA_MEASUREMENT_ID, -1);
String type = intent.getStringExtra(
MeasurementSendWorker.EXTRA_MEASUREMENT_TYPE);
// Handle successful upload
Log.i("MyApp", "Measurement uploaded: id=" + measurementId);
} else if (MeasurementSendWorker.ACTION_MEASUREMENT_FAILED.equals(action)) {
String error = intent.getStringExtra(
MeasurementSendWorker.EXTRA_ERROR_MESSAGE);
// Handle permanent failure
Log.e("MyApp", "Measurement upload failed: " + error);
}
}
};
@Override
protected void onStart() {
super.onStart();
IntentFilter filter = new IntentFilter();
filter.addAction(MeasurementSendWorker.ACTION_MEASUREMENT_SENT);
filter.addAction(MeasurementSendWorker.ACTION_MEASUREMENT_FAILED);
LocalBroadcastManager.getInstance(this)
.registerReceiver(measurementReceiver, filter);
}
@Override
protected void onStop() {
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(measurementReceiver);
super.onStop();
}
}
Retries¶
A measurement that cannot be delivered stays in the queue and is retried with exponential backoff.
The outcome depends on why the upload failed:
- If the server cannot be reached because the device is offline or the server is down, the measurement keeps its place in the queue without using its retry budget. It is sent after connectivity returns.
- If the server returns a server error, or the SDK cannot classify the failure, the attempt counts toward a limit of five. After the fifth failed attempt, the measurement is marked as permanently failed.
- If the server rejects the request with a 4xx status, the measurement fails immediately because repeating the same request would not change the response.
- A 401 or 403 response first makes the SDK ask the server whether it still accepts the device. If it does, the SDK retries with a fresh token. If it does not, the registration and queue are cleared as described below.
Custom Measurements¶
The SDK calls the queue lifecycle methods for its own quality tests. If you run another measurement and want to prevent uploads from interfering with it, bracket that work with matching calls:
MeasurementQueueManager.onTestStarted(context);
// ... run your own measurement ...
MeasurementQueueManager.onTestFinished(context);
Every onTestStarted() call needs a matching onTestFinished() call. Otherwise, the queue keeps waiting and stops sending.
You can also add a compatible report to the queue yourself:
Storage and Limits¶
Pending measurements are stored in a Room database in your app's private storage. A measurement is removed as soon as the server accepts it. Permanently failed entries are retained for seven days before cleanup.
The Android queue has no fixed limit on the number of pending measurements. Deleting the app discards its queue.
When Registration Is Lost¶
A measurement is uploaded under the identity of the device that took it, so the queue is cleared when that identity is removed. This happens when a per-device usage limit expires, when your app calls unregisterClientAsync(), or when the server no longer accepts the device because it was disabled, its license expired, or its credentials are invalid.
Nothing is uploaded after that, and no further test can be built. If your app shows the pending count, expect it to drop to zero. See When the Server Stops Accepting a Device.