Migrating to Libsoftphone 26.1¶
Libsoftphone 26.1 changes how Android apps provide contexts, request permissions, select audio routes, and shut down the SDK.
This guide describes the changes needed when upgrading from 25.3.
The Android Demophone sample demonstrates the migration in a working app.
Update Android build settings¶
The minimum supported Android API level is now 26. Update your app's Gradle configuration and compile against API 26 or later:
Devices below API 26 are no longer supported by this SDK version.
Update your SDK dependency to the 26.1 release you intend to ship, and review any product flavors that override these Android settings.
Pass contexts explicitly¶
AndroidUtil.getContext() and utility methods that depend on an implicit global context are deprecated.
Pass the context required by the operation through your constructors or method arguments.
Use the application context for components that outlive an activity, and an activity context for dialogs and permission prompts.
Use the new context-taking overloads where available, or call the corresponding Android API directly:
| Previous code | Replacement |
|---|---|
AndroidUtil.getResources() |
context.getResources() |
AndroidUtil.getApplicationId() |
context.getPackageName() |
AndroidUtil.getColor(resId) |
ContextCompat.getColor(context, resId) |
AndroidUtil.getDimension(resId) |
AndroidUtil.getDimension(context, resId) |
AndroidUtil.checkPermission(permission) for a permission string |
Permission.check(context, permission) |
Keep activity references scoped to their lifecycle. For an SDK observer that lives longer than an activity, obtain the current activity or permission launcher owner from your app's lifecycle management when needed.
Remove the lifecycle initializer¶
LifecycleTrackerInitializer has been removed.
Delete its provider declaration from your application's manifest if you added it for an earlier SDK version:
<!-- Remove this provider. -->
<provider
android:name="cz.acrobits.libsoftphone.support.lifecycle.LifecycleTrackerInitializer"
android:authorities="${applicationId}.libsoftphone.lifecycle"
android:exported="false" />
Review the merged manifest when removing other declarations that duplicate those supplied by the SDK. Keep permissions and features required by your own app and its optional SDK features.
Update permission handling¶
Review the merged manifest¶
The 26.1 SDK manifest declares BLUETOOTH_CONNECT and POST_NOTIFICATIONS, as well as the legacy BLUETOOTH permission limited to API 30 and below.
It also declares the optional Bluetooth hardware feature, FOREGROUND_SERVICE_CAMERA, and ServiceBundle$CameraService with android:foregroundServiceType="camera".
You no longer need to add the Bluetooth permission and hardware feature solely for the SDK.
Manifest declarations do not grant runtime access.
Handle Bluetooth permission on Android 12 and later and notification permission on Android 13 and later as needed by your app's features.
For outgoing video, your app must still declare and request CAMERA; the camera foreground-service declaration does not replace it.
Review your merged manifest after the upgrade and keep declarations required by your own app.
See Android permissions for the SDK-provided declarations and app-provided optional permissions.
Forward runtime permission requests¶
Permission checks now take an explicit Context, and permission requests take a PermissionRequestLauncher.
Your app must select an activity that can display permission dialogs and forward results using its PermissionRequestLauncherOwner.
- Have your activity implement
PermissionRequestLauncherOwner. - Create its launcher with
PermissionRequestLauncher.of(activity)and return it fromgetPermissionRequestLauncher(). -
In
Observer.onPermissionRequested(...), obtain the current owner and forward the request: -
Pass the owner as the first argument when forwarding Android permission results:
In Demophone, DemoPermissionManager queues requests while no activity is available.
MainActivity supplies its owner in onResume() and removes it in onStop().
DemoListeners.onPermissionRequested(...) forwards requests to that manager.
These are sample app components; implement the equivalent ownership and pending-request behavior for your app.
After a successful runtime grant, notify the SDK on the main thread:
| Permission | Notification |
|---|---|
BLUETOOTH_CONNECT |
Instance.Audio.reloadAudioEndpoints() |
RECORD_AUDIO |
Instance.Audio.reloadAudioRecorderPermissions() |
Both notifications can be sent while calls are active.
See Android permissions for complete activity and observer examples, including the legacy 25.3 integration.
Migrate Telecom and audio routing¶
Use the SDK-managed Telecom integration¶
The SDK now manages its own ConnectionService and PhoneAccount registration.
If you had a custom SDK ConnectionService implementation inheriting from cz.acrobits.libsoftphone.telecom.ConnectionService, remove it and its service declaration from your manifest.
The SDK automatically registers and unregisters its PhoneAccount when the app is running.
Use the defaultTelecomStrategy preference to select the preferred strategy for new calls:
| Value | Behavior |
|---|---|
system (default) |
Integrates calls with Android Telecom. |
compat |
Routes audio through AudioManager without a ConnectionService. |
The SDK can fall back to compatibility routing during a call session if system Telecom is unavailable or fails to handle a call. The temporary fallback lasts until all calls end; it does not change the configured default. Changing the preference does not immediately switch active calls.
To move the current call session to compatibility routing explicitly, call:
You can observe the routing mode with Instance.Telecom.isUsingCompatibilityAudioRouting().
For device diagnostics, Instance.Telecom.testSystemAudioRoutingOnCurrentDevice(...) tests simulated incoming and outgoing calls when no calls are active.
See Managed Telecom integration for fallback behavior and test result handling.
Replace audio route enums with endpoints¶
The AudioRoute enum has been removed.
Use SDK-provided cz.acrobits.libsoftphone.telecom.AudioEndpoint objects, which distinguish individual devices even when they share a type.
For example, your route picker can show separate entries for multiple Bluetooth endpoints.
| Previous API | Replacement |
|---|---|
Instance.Audio.getAvailableCallAudioRoutes() |
Instance.Audio.getAvailableCallAudioEndpoints() returning a StateFlow<List<AudioEndpoint>> |
Instance.Audio.getCallAudioRoute() |
Instance.Audio.getCurrentAudioEndpoint() returning a StateFlow whose value can be null |
Instance.Audio.setCallAudioRoute(route) |
Instance.Audio.setCallAudioEndpoint(endpoint) |
Listeners.OnAudioRouteChanged / onAudioRouteChanged(...) |
Listeners.OnAudioEndpointChanged / onAudioEndpointChanged(...) |
Replace route cycling based on enum constants with selection from the available endpoint list. For example, the following Java code builds a simple picker inside an activity:
List<AudioEndpoint> endpoints = Instance.Audio.getAvailableCallAudioEndpoints().getValue();
if (endpoints.isEmpty())
return;
String[] labels = new String[endpoints.size()];
for (int i = 0; i < endpoints.size(); ++i) {
AudioEndpoint endpoint = endpoints.get(i);
labels[i] = endpoint.getName() != null
? endpoint.getName()
: endpoint.getType().name();
}
new AlertDialog.Builder(this)
.setTitle("Audio output")
.setItems(labels, (dialog, which) ->
Instance.Audio.setCallAudioEndpoint(endpoints.get(which)))
.show();
Use localized fallback labels in your app, and refresh the picker when the available endpoint list changes.
Update your displayed selection from onAudioEndpointChanged(...) or the current endpoint flow.
See Audio endpoints for endpoint properties and observer callbacks.
Replace termination tasks¶
TerminateTask and Instance.State.terminate() have been removed.
Use Instance.State.requestTermination(...) and wait for its callback before completing your shutdown flow.
// Before
new TerminateTask().execute();
finish();
// After
if (!Instance.State.isTerminating()) {
Instance.State.requestTermination(() -> {
// Termination complete
});
}
Termination ends active calls and unregisters SIP accounts asynchronously. Unregistering an account during shutdown does not require deleting its saved configuration. Demophone now finishes the activity from the termination callback.
requestTermination(...) returns a handle that can cancel an in-progress termination.
If you wrap termination in a cancellable coroutine, retain the handle and call dispose() or close() when the coroutine is cancelled, as shown by Demophone's DemoStateManager.
See SDK termination for timeouts and cancellation behavior.
Migrate contact sources¶
Contact-source APIs now use cz.acrobits.libsoftphone.contacts.ContactSourceType.
Replace ContactSource.ADDRESS_BOOK with ContactSourceType.AddressBook, and update source variable types, imports, comparisons, and switch branches throughout your contacts integration.
This change affects method signatures and observer implementations as well as enum constants.
| API surface | Required update |
|---|---|
Instance.Contacts.getSources() |
Handle a ContactSourceType[] result. |
Source-taking Instance.Contacts methods |
Pass ContactSourceType to getCount, get, getStorageChecksum, getBlockBitCount, iterateUnordered, iterateAlphabetically, getSections, setAuxiliary, ensureValidState, and getContactSourceState. |
ContactId |
Use ContactSourceType for its source field and source constructor argument. |
Observer contact callbacks and corresponding Listeners interfaces |
Change the source parameter to ContactSourceType in onContactsChanged, onContactSourceLoginStateChanged, onContactSourceStateChanged, and onContactSourceIndexChanged. |
For example, update a callback implementation in your Observer or Listeners.OnContactsChanged listener:
import cz.acrobits.libsoftphone.contacts.ContactSourceType;
@Override
public void onContactsChanged(@NonNull ContactSourceType source) {
if (source == ContactSourceType.AddressBook) {
// Refresh your app's contacts view.
}
}
Update other affected APIs¶
Pass an Android Color to the factory instead of constructing a request from a string.
| Previous code | Replacement |
|---|---|
new ColorRequest.HexColorRequest("#FFFFFF") |
ColorRequest.Companion.hex(Color.valueOf(Color.parseColor("#FFFFFF"))) in Java, using android.graphics.Color |