﻿
# Migrating to Libsoftphone 26.1 { #android-migrating-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](../libsoftphone-android-ios/demophone.md#android-register-a-license) 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:

```kotlin
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:

```xml
<!-- 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](permissions.md) 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:

    ```kotlin
    Permission.request(owner.permissionRequestLauncher, permission, callback::onResult)
    ```

4. Pass the owner as the first argument when forwarding Android permission results:

    ```java
    // 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](permissions.md#handling-permission-requests) 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:

```java
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](managed-telecom.md) 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:

```java
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](managed-telecom.md#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.

```java
// 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](termination.md) 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:

```java
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` |

