Skip to content

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:

android {
    defaultConfig {
        minSdk = 26
    }
}

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.

  1. Have your activity implement PermissionRequestLauncherOwner.
  2. Create its launcher with PermissionRequestLauncher.of(activity) and return it from getPermissionRequestLauncher().
  3. In Observer.onPermissionRequested(...), obtain the current owner and forward the request:

    Permission.request(owner.permissionRequestLauncher, permission, callback::onResult)
    
  4. Pass the owner as the first argument when forwarding Android permission results:

    // Before (25.3)
    Permission.onRequestPermissionResult(requestCode, permissions, grantResults);
    
    // After (26.1), in an activity implementing PermissionRequestLauncherOwner
    Permission.onRequestPermissionResult(this, requestCode, permissions, grantResults);
    

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:

Instance.Telecom.switchReportedCallsToCompatibilityAudioRouting();

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