Skip to content

Android SDK Behavior Changes and Migration (v3.16 )

ciscoRankush edited this page Nov 25, 2025 · 2 revisions

Android SDK Behavior Changes and Migration (v3.16+)

We have recently release 3.16.0v for Mobile SDK. And with this release there are few behaviour changes. This guide explains two behavior updates introduced in the Android SDK and how to migrate apps:

  • Support for 16 KB memory page size (Google-recommended platform change)
  • App-owned runtime permission handling (SDK no longer prompts)

Both are intentional behavior changes. The 16 KB work is non-breaking for apps; the permission model is a breaking change that requires app updates.

1. 16 KB memory page size (what, why, and what changed)

What is the 16 KB page-size recommendation?

Newer Android devices and toolchains can use a default memory page size of 16 KB on arm64. Google has announced a Play compatibility requirement: starting November 1, 2025, all new apps and updates targeting Android 15+ devices must support 16 KB page sizes on 64-bit devices. See guidance: Google: Support 16 KB page sizes.

What we changed in the SDK

  • Updated native components and packaging to be compatible with 16 KB memory pages (e.g., proper ELF alignment, toolchain flags, and mapping behavior).
  • Ensured the SDK runs correctly on devices using 16 KB pages without app-side changes.

Migration impact for apps

  • No app code changes required. Updating to this SDK version is sufficient.
  • Validation tip: on a test device/emulator, adb shell getconf PAGE_SIZE should return 16384 when running with 16 KB pages (see Google doc linked above).

2. App-owned permission flow (breaking change)

What changed

  • SDK does not include dangerous permissions in its library manifest; applications must declare them in their own manifests.
  • SDK does not prompt for system permissions; applications must request them at runtime.
  • SDK performs preflight checks and reports missing permissions via WebexError.ErrorCode.PERMISSION_REQUIRED (with the missing permission list in error.data).
  • There is a change in how user consent for screen sharing is obtained.

Why we changed it

  • Flexibility: Apps can adopt the latest Android UX patterns for permission prompts and custom UI flows.
  • Independence: Reduces app coupling to SDK releases when Google updates permission policies.
  • Store compliance: Apps can control which permissions are declared per flavor, minimizing Play Console warnings for dangerous/sensitive permissions.

Why it’s a breaking change

  • Apps that previously relied on SDK-owned prompts will now receive PERMISSION_REQUIRED errors instead of dialog popups.
  • Migration is required to add manifest declarations, request permissions at runtime, and retry SDK calls after grants.

Migration Guide (step by step)

A. Manifest preparation (app-side)

  • Declare only the permissions your app actually needs per feature/flavor. Examples:
    • RECORD_AUDIO (audio)
    • CAMERA (video)
    • MODIFY_AUDIO_SETTINGS (required for audio routing - speaker, Bluetooth, earpiece)
    • BLUETOOTH_CONNECT on API 31+ (Bluetooth route toggle; legacy BLUETOOTH on older)
    • READ_PHONE_STATE (optional optimization)
    • No runtime string for screen share consent (MediaProjection); overlays require SYSTEM_ALERT_WINDOW if your UX needs it

B. Common runtime request utility

Request permissions before calling SDK APIs. Keep a small helper for checking and requesting.

val required = listOf(Manifest.permission.RECORD_AUDIO)
val missing = required.filter { ContextCompat.checkSelfPermission(ctx, it) != PackageManager.PERMISSION_GRANTED }
if (missing.isNotEmpty()) {
    // requestPermissions(missing.toTypedArray(), REQ_CODE)
    return
}

If a call returns PERMISSION_REQUIRED, read error.data for the list of missing permissions, request them, and retry.

C. Use-case recipes

Permission behavior changes sequence

1. Audio-only calling (Webex Call/meetings)

  • Permissions: RECORD_AUDIO
  • Flow:
    1. Check/request mic
    2. Call webex.phone.dial(...) (or answer(...))
    3. On PERMISSION_REQUIRED, prompt and retry
val mic = Manifest.permission.RECORD_AUDIO
if (ContextCompat.checkSelfPermission(ctx, mic) != PackageManager.PERMISSION_GRANTED) {
    // requestPermissions(arrayOf(mic), REQ_MIC)
    return
}
webex.phone.dial(address, MediaOption.audioOnly()) { result -> /* handle */ }

Dangerous permissions: RECORD_AUDIO is runtime (dangerous). Always prompt at runtime on Android 6+.

Manifest entries:

<uses-permission android:name="android.permission.RECORD_AUDIO" />

2. Audio + Video calling

  • Permissions: RECORD_AUDIO, CAMERA (plus optional READ_PHONE_STATE if enabled in your app)
  • Flow: request both, then dial/answer
val needed = arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
// request if missing ... then
webex.phone.dial(address, MediaOption.audioVideo(/* views */)) { result -> /* handle */ }

Manifest entries:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />

3. Screen sharing (MediaProjection consent handoff)

  • No runtime permission string; must obtain the consent intent
  • Flow:
    1. Launch MediaProjectionManager#createScreenCaptureIntent()
    2. On success, pass data: Intent to call.startSharing(consentData, ...) and proceed to startSharing(...)
    3. If overlays needed (e.g., annotations), check Settings.canDrawOverlays(...) and guide user to Settings
val mgr = ctx.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
val consentIntent = mgr.createScreenCaptureIntent()
launcher.launch(consentIntent)
// onActivityResult / ActivityResult callback:
call.startSharing(notification, notifId, data /* consent */, callback, shareConfig)

Manifest entries:

<!-- No runtime permission string for MediaProjection consent -->
<!-- If your UX includes overlays (e.g., annotations), and you actually present them: -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

4. Audio routing (speaker, Bluetooth, earpiece)

  • Permissions:
    • MODIFY_AUDIO_SETTINGS (required for all audio routing - normal permission)
    • BLUETOOTH_CONNECT on API 31+ for Bluetooth toggle (dangerous permission)
    • Legacy BLUETOOTH on older Android versions
  • Flow: declare MODIFY_AUDIO_SETTINGS in manifest, request Bluetooth permission at runtime if needed, then call call.switchAudioOutput(...)

Important: Without MODIFY_AUDIO_SETTINGS, toggling to speaker mode or any audio route will fail or produce no audio during calls.

// Switch to speaker (only needs MODIFY_AUDIO_SETTINGS in manifest)
call.switchAudioOutput(Call.AudioOutputMode.SPEAKER) { result -> 
    if (result.isSuccessful) {
        // Audio routed to speaker
    }
}

// Switch to Bluetooth (needs MODIFY_AUDIO_SETTINGS + runtime Bluetooth permission)
val bt = if (Build.VERSION.SDK_INT >= 31) Manifest.permission.BLUETOOTH_CONNECT else Manifest.permission.BLUETOOTH
if (ContextCompat.checkSelfPermission(ctx, bt) == PackageManager.PERMISSION_GRANTED) {
    call.switchAudioOutput(Call.AudioOutputMode.BLUETOOTH_HEADSET) { /* handle */ }
} else {
    // Request Bluetooth permission at runtime
}

// Switch to earpiece/phone
call.switchAudioOutput(Call.AudioOutputMode.PHONE) { /* handle */ }

Manifest entries:

<!-- Required for ALL audio routing operations (normal permission - no runtime request needed) -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

<!-- Only if your app uses Bluetooth audio routing -->
<!-- API 31+ -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Legacy (if supporting older targets) -->
<uses-permission android:name="android.permission.BLUETOOTH" />

Note: MODIFY_AUDIO_SETTINGS is a normal permission (not dangerous), so it only needs manifest declaration. However, BLUETOOTH_CONNECT is a dangerous permission on API 31+ and requires runtime request.

5. Optional: READ_PHONE_STATE

  • Some apps use READ_PHONE_STATE to improve the call experience on certain devices or carriers. Keep it optional—request it only if your app truly benefits.

Example (only request if you need it):

val phoneState = Manifest.permission.READ_PHONE_STATE
val isGranted = ContextCompat.checkSelfPermission(ctx, phoneState) == PackageManager.PERMISSION_GRANTED
if (!isGranted) {
    // requestPermissions(arrayOf(phoneState), REQ_READ_PHONE_STATE)
}

SDK toggle:

  • Phone.enableAskingReadPhoneStatePermission(enable: Boolean)
  • Default is true. When your target SDK ≥ 30, this permission is used to check network state so the SDK can auto‑tune performance during calls.
// If your app does not benefit from READ_PHONE_STATE, you can opt out
webex.phone.enableAskingReadPhoneStatePermission(false)

Manifest entries:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />

D. Handling SDK errors consistently

  • If a preflight finds missing permissions, you’ll get PERMISSION_REQUIRED with a list in error.data.
  • Request those permissions via your UI, then retry the exact API call.
  • Avoid scenarios where the app displays a “missing permission” toast while the SDK proceeds—ensure your UI and SDK preflight are aligned.

E. Flavor-level control for Play Store compliance

  • Split permissions by product flavor/module so you only declare what each build truly needs.
  • This reduces Play Console warnings for unused dangerous permissions.

FAQ

Does the SDK still show permission dialogs?

No. All prompts are app-owned. The SDK only validates and reports PERMISSION_REQUIRED.

Do I need to change anything for 16 KB page size?

No. Updating to this SDK is sufficient.

Can the SDK return an error and still connect?

No for the same operation. If a preflight fails, the call is not executed. Ensure your app UI doesn’t show an unrelated permission toast while a separate flow proceeds.


Clone this wiki locally