Registration (Android)¶
How Registration Works¶
The Surfmeter Mobile Quality SDK uses a license-based registration system to manage access. Before your app can run any quality measurements, the device must be registered against the Surfmeter server using a registration key provided by AVEQ.
A registration key controls:
- How many devices can register with it (a finite counter, or unlimited)
- How long each device may use the SDK after registering (optional usage limit in days)
- When the key is valid (“valid until” – a time window) – this controls when new registrations are accepted, and once it passes, devices already registered with the key are refused as well
- Which measurement types and subjects the device is allowed to run (capabilities)
When a device registers, the server decrements the key's registration counter, creates a client record, and returns a unique device identity (UUID + secret) along with any configured capabilities and expiration details. These credentials are stored encrypted on the device and used for all subsequent API calls.
The server has the last word from then on, not the device. Whenever the SDK reaches the server it asks whether this device is still accepted, and a device the server will not have any more loses its credentials and stops measuring. See When the Server Stops Accepting a Device.
Registration Lifecycle¶
The following diagram shows the full lifecycle from registration to expiration or unregistration. Understanding this lifecycle is important for integrators, because registered devices hold a registration slot. For keys with a per-device usage limit, the server returns the slot automatically when the limit elapses; for keys without one, devices keep their slot until explicitly unregistered.
flowchart TD
A[App starts] --> B{Device registered?}
B -- No --> W{Has this install<br>ever registered?}
W -- Yes --> X["Server dropped this device.<br>Register again only if its license<br>ran out; otherwise that takes<br>a second slot"]
W -- No --> D["Integrator calls registerClientAsync()"]
B -- Yes --> C[Use stored credentials]
D --> E{Server validates key}
E -- Invalid or expired key --> F[Registration failed]
E -- No registrations left --> F
E -- Valid --> G["Server creates client,<br>decrements registration counter;<br>SDK stores UUID, secret,<br>capabilities, expiration locally"]
G --> C
C --> T["Integrator creates QualityTest"]
T --> J{SDK checks:<br>registration expired?}
J -- Yes --> L["License returned automatically:<br>SDK calls returnLicense(), or the server<br>returns it when the usage limit elapses;<br>client disabled, device must re-register"]
J -- No --> U{SDK checks:<br>type and subject allowed<br>by capabilities?}
U -- No --> V["SDK throws IllegalStateException"]
U -- Yes --> K[Test runs]
K --> P{SDK asks the server:<br>do you still accept<br>this device?}
P -- "No (disabled, or license run out)" --> Q["SDK clears the credentials<br>and the measurement queue;<br>no further test can be built"]
P -- Yes --> M{App lifecycle event}
P -- "Server unreachable" --> M
Q --> X
M -- Continue --> T
M -- Device decommissioned --> N["Integrator calls unregisterClientAsync();<br>server disables client and<br>increments registration counter"]
Billing Implications¶
Registered devices are counted toward your license usage. How a registration slot is freed depends on whether the key has a per-device usage limit:
- Keys with a usage limit (
usage_limit_days): the server automatically returns the slot to the pool when the device's usage window elapses (registration time + usage_limit_days). This happens even if the device was uninstalled and the SDK never runs again. You do not need to take any action to free these slots. - Keys without a usage limit: the device stays registered and keeps holding its slot indefinitely. The server does not free it automatically, so you must actively unregister the device when it is no longer in use.
Unregister devices on keys without a usage limit
For registration keys without a usage limit, the slot is only freed when you act. When such a device is decommissioned, replaced, or no longer running measurements, call unregisterClientAsync() to return the license. This frees the registration slot so it can be reused by another device.
Calling unregisterClientAsync() is also useful for keys with a usage limit if you want to return the slot early, before the usage window elapses.
If you are unable to do so, you can manually disable the client in our Surfmeter Dashboard, which also stops that device measuring, see When the Server Stops Accepting a Device. Or, please let us know the device UUIDs so we can manually free the slots on our end.
Backup, restore, and device transfer
A device that is restored from an Android cloud backup or transferred to a new phone will register again and receive a fresh UUID, because the credentials are encrypted with a Keystore-bound master key that does not move between devices. The SDK ships backup-rules resources that exclude the credentials file from Auto Backup so that the original device keeps its UUID across normal backups. See Backup and Restore Behavior for details and for the conflict-resolution path if your app declares its own backup rules.
Registration Example¶
Use the provided Registry class and call registerClientAsync with your license key. Registration is a one-time process per device – the credentials are persisted in the app's encrypted preferences.
import com.aveq.qualitytestlib.Registry;
Registry registry = Registry.Builder(this).forSurfmeterQualitySdk().build();
if (registry.isClientRegistered()) {
return;
}
// hasRegisteredBefore is your own state, see the notes below
if (hasRegisteredBefore) {
// the server dropped this device; see "When the Server Stops Accepting a Device" below
return;
}
registry.registerClientAsync(
"REPLACE_WITH_YOUR_REGISTRATION_KEY",
"your-client-label", // optional: a label to identify the client (must be unique across all your clients)
null, // optional: a List<String> of tags for the client (e.g. Arrays.asList("production", "eu-west")), or null for none
new Registry.OnClientRegisteredListener() {
@Override
public void onClientRegistered() {
// store the state that the client is registered
hasRegisteredBefore = true;
}
@Override
public void onClientRegistrationFailed(RuntimeException e) {
// log or display an error
}
}
);
Notes:
- Your implementing application has to store the state that the client is registered, and register only when that state says it never has.
isClientRegistered()is not enough on its own: the SDK clears the credentials of a device the server has dropped, and registering such a device again takes a second slot of your key. See When the Server Stops Accepting a Device. - The registration key is provided by AVEQ. If it does not work, please contact us.
- Registration should be done asynchronously to avoid blocking the main thread. While a synchronous registration method exists, it is not recommended for production code.
Unregistration¶
When a device is no longer needed, call unregisterClientAsync() to return the license to the pool. This disables the client on the server and increments the registration counter, making the slot available for another device.
registry.unregisterClientAsync(new Registry.OnClientUnregisteredListener() {
@Override
public void onClientUnregistered() {
// client has been unregistered, clear local state
}
@Override
public void onClientUnregistrationFailed(RuntimeException e) {
// handle error
}
});
You should call this when:
- A device is being decommissioned or removed from your fleet
- A device is being replaced by a new one
- You are cleaning up test or development devices
- The app is being uninstalled (if you can intercept this event)
When the Server Stops Accepting a Device¶
A device the server will not have any more stops measuring. The SDK finds out the next time it reaches the server, which happens when a test starts and whenever a measurement is uploaded, and then clears the device's credentials along with everything still waiting in the measurement queue. From that point isClientRegistered() returns false and building a QualityTest throws an IllegalStateException.
There are three reasons the server says so:
- The device has been disabled in the Surfmeter Dashboard. Its slot stays taken.
- Its license has run out, either the per-device usage limit or the registration key's own validity. The server has already returned the slot to the pool.
- Its credentials are not ones the server issued, for example because the client was deleted server-side.
The difference matters for what your app should do next, because only the second one wants a new registration. It also is not the same as calling unregisterClientAsync(): that is your app returning the license on purpose, whereas these are the server's decision, which the device only learns about afterwards.
Registering again is your app's decision, and after a device has been disabled it is the one thing that should not happen: a device that registers again becomes a new client, enabled, holding a second slot of your registration key. So keep track of whether this install has ever registered, and use that rather than isClientRegistered() to decide:
Registry registry = new Registry.Builder(context).forSurfmeterQualitySdk().build();
if (registry.isClientRegistered()) {
// measuring normally
} else if (hasRegisteredBefore) { // your own state, not the SDK's
// the server dropped this device, most likely disabled; do not register again
} else {
registry.registerClientAsync(registrationKey, label, tags, listener);
}
The demo app in the release archive does exactly this, in MeasureFragment.
You can also ask the server yourself, for example at launch, before you offer to measure anything:
registry.checkRegistrationAsync(result -> {
switch (result) {
case ACCEPTED:
// the server still accepts this device, and its capabilities are now up to date
break;
case REFUSED:
// the credentials and the queue have just been cleared; do not register again
break;
case UNKNOWN:
// the server could not be reached, so nothing has changed
break;
}
});
The listener is called on a background thread, so dispatch back to the main thread before touching your UI. A blocking checkRegistration() is also available for code that already runs in the background.
Only a clear answer from the server changes anything
A device is only dropped when the server actually says so. If it cannot be reached, or answers with a server error, the registration is left exactly as it was. That matters for your licence count: a device whose registration was discarded over a temporary network problem would have to register again and would take a second slot of your key.
Disabling is not immediate
A device only learns that it has been disabled when it next reaches the server. One that is offline, or that is not running any tests, keeps its credentials until then. A test that is already running is also allowed to finish; it is the next one that cannot be built.
Checking whether the device is still accepted¶
Whenever the SDK reaches the server, it asks whether this device is still accepted and what it is licensed to measure, in one request. Two things follow from that:
- A capability that AVEQ changes takes effect without the device registering again.
- A device that has been disabled finds out.
You do not have to do anything for this; it happens on its own.
Remote Device Registration via ADB¶
For automated deployments or testing scenarios where you cannot manually enter a registration key, the SDK demo app supports auto-registration via an Android intent. This allows you to register the app on remote devices via ADB without any manual interaction.
First, grant the required permissions:
adb shell pm grant com.aveq.qualitytestlibdemo android.permission.ACCESS_FINE_LOCATION
adb shell pm grant com.aveq.qualitytestlibdemo android.permission.ACCESS_COARSE_LOCATION
adb shell pm grant com.aveq.qualitytestlibdemo android.permission.READ_PHONE_STATE
adb shell pm grant com.aveq.qualitytestlibdemo android.permission.POST_NOTIFICATIONS
Then send the registration intent:
adb shell am start -n com.aveq.qualitytestlibdemo/.MainActivity \
-a com.aveq.qualitytestlibdemo.action.REGISTER \
--es registration_key "YOUR_REGISTRATION_KEY" \
--es label "Device-001" \
--esa tags "production,eu-west"
Intent Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
registration_key |
String | Yes | The license key provided by AVEQ |
label |
String | No | A unique identifier for this device/client |
tags |
String Array | No | Comma-separated tags for categorization |
On successful registration, the client UUID will be logged to logcat under the Surfmeter.MainActivity tag.
Capabilities¶
Capabilities define which measurement types and subjects a device is allowed to run. They are configured server-side, associated with a registration key, and transmitted to the device during registration. They are also refreshed every time the SDK reaches the server, so a capability that AVEQ changes takes effect without the device having to register again.
If no capability is assigned to a registration key, all test types and subjects are allowed by default.
How Capabilities Work¶
A capability consists of lists that control access:
allowed_measurement_types– measurement types the client may run. An empty list means all types are allowed.allowed_measurement_subjects– specific services or platforms the client may measure. An empty list means all subjects are allowed.
The Mobile Quality SDK currently supports two measurement types:
video– video streaming quality tests (VideoQualityTest, ExoPlayerQualityTest)web– web page loading performance tests (WebQualityTest)
The video type supports the following subjects: youtube, netflix_trailer, facebook, instagram, tiktok, hotstar, exoplayer.
The web type only supports website.
Note
The server-side capability system supports additional measurement types (e.g., network, speedtest, conferencing) and their corresponding subjects. These are used by other Surfmeter products but are not available in the Mobile Quality SDK. If a capability restricts your key to types or subjects not supported by this SDK, the affected tests simply cannot be created.
Querying Capabilities¶
After registration, you can inspect the client's capabilities using Registry.getCapability():
Registry registry = new Registry.Builder(context).forSurfmeterQualitySdk().build();
HashMap<String, ArrayList<String>> capabilities = registry.getCapability();
if (capabilities == null) {
// No restrictions — all test types and subjects are allowed
} else {
ArrayList<String> allowedTypes = capabilities.get("allowed_measurement_types");
ArrayList<String> allowedSubjects = capabilities.get("allowed_measurement_subjects");
// These extra fields exists but are typically not set
ArrayList<String> forbiddenTypes = capabilities.get("forbidden_measurement_types");
ArrayList<String> forbiddenSubjects = capabilities.get("forbidden_measurement_subjects");
}
Querying capabilities is useful if you want to adapt your UI – for example, to only show measurement options that the client is permitted to use.
Automatic Enforcement¶
You do not need to check capabilities manually before running a test. The SDK automatically validates the requested test type and subject when a QualityTest is instantiated. If the test is not permitted, the constructor throws an IllegalStateException:
try {
VideoQualityTest test = new VideoQualityTest.Builder(context)
.setUrl("https://www.youtube.com/watch?v=...")
.setSubject("youtube")
.build();
} catch (IllegalStateException e) {
// "Test type or subject not allowed by capability"
}
Registration Expiration¶
There are two independent expiry mechanisms that can cause a device's registration to end. Both are configured server-side and associated with the registration key.
Per-device usage limit¶
A registration key can define a usage limit in days. When a device registers, the server computes an expiration timestamp (registration time + usage_limit_days) and transmits it to the device. The SDK stores this timestamp locally.
The slot is returned by two complementary mechanisms:
- Client-side (SDK): the SDK checks for expiration whenever the stored credentials are accessed – for example, when instantiating a
QualityTest. If the expiration has passed at that point, the SDK callsreturnLicense()on the server, clears the local credentials, and throws anIllegalStateException. This check happens lazily on the next SDK operation that requires credentials. - Server-side: independently of the SDK, the server automatically returns the slot to the pool when the usage window elapses. This means the slot is reclaimed even if the app is never opened again or was uninstalled before the limit was reached.
These two mechanisms are idempotent: whichever happens first frees the slot, and the slot is never double-counted. The client-side check is an optimization that can return the slot a bit sooner; for usage-limited keys you do not need to rely on it.
This is a hard client-side expiration: once it triggers on the device, no new tests can be created until the device re-registers with a valid key.
Registration key "Valid until" date¶
Each registration key has an optional "Valid until" date, visible in the Surfmeter Dashboard. This date controls when new devices can register with the key. After it passes, new registrations are rejected.
For already-registered devices, the date is enforced by the server rather than counted down on the device. Once it passes, the server refuses the device the next time it authenticates, and returns its slot to the pool. The SDK reads that refusal the same way it reads any other, so the credentials and anything still queued are discarded and the device stops measuring. See When the Server Stops Accepting a Device.
Because the slot has gone back to the pool, this is the one case where registering again is the right thing to do, with a key that is still valid.
Checking remaining time¶
You can check the remaining time for the per-device usage limit using Registry.getExpirationDetails():
Registry registry = new Registry.Builder(context).forSurfmeterQualitySdk().build();
Registry.ExpirationInfo expiration = registry.getExpirationDetails();
if (expiration == null) {
// No expiration set, or client is not registered
} else {
long remaining = expiration.value;
TimeUnit unit = expiration.unit;
// e.g. "Registration expires in 25 DAYS"
}
This method returns null when no expiration is configured (the registration does not expire), when the expiration has already passed, or when the client is not registered.
Note
getExpirationDetails() only reflects the per-device usage limit. It does not account for the registration key's "Valid until" date, which is only enforced server-side.