# Android

## I. Thiết lập Android Studio project <a href="#i-thiet-lap-android-studio-project" id="i-thiet-lap-android-studio-project"></a>

***Bước 1:*** Tải portsipSDK tại [đây](https://www.portsip.com/download-portsip-voip-sdk/).

***Bước 2*****:** Giải nén file .zip vừa mới tải về, copy file `AndroidSample/SIPSample_AndroidStudio/SIPSample/libs/portsipvoipsdk.jar` vào thư mục `app/libs` của project Android

<figure><img src="/files/Jro3tZn1RJoMbzdhkj2g" alt=""><figcaption></figcaption></figure>

***Bước 3*****:** Compile Libs : Chuột phải vào file `portsipvoipsdk.jar` chọn `Add as Library...`

***Bước 4*****:** Khai báo các permission cần thiết trong file `AndroidManifest.xml`

{% code overflow="wrap" fullWidth="false" %}

```html
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app">

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
  <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
  <!-- Các permission liên quan đến internet. -->
  
  <uses-permission android:name="android.permission.WAKE_LOCK" />
  <!-- Cho phép ứng dụng có thể unlock điện thoại khi có cuộc gọi đến. -->
  <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
  <!-- Cho phép ứng dụng có thể mở giao diện fullscreen khi nhận thông báo cuộc gọi đến 
  lúc điện thoại đang ở trạng thái không active (khoá màn hình, kill app, không ở
  màn hình ứng dụng) -->
  <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
  <!-- Cho phép người dùng có thể thao tác khi máy đã ở trạng thái lock 
  (bấm chấp nhận/từ chối cuộc gọi). -->
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <!-- Cho phép sử dụng microphone. -->
  <uses-permission android:name="android.permission.CAMERA" />
  <!-- Cho phép sử dụng camera. -->
  <uses-permission android:name="android.permission.VIBRATE" />
  <!-- Cho phép ứng dụng điều khiển điện thoại rung khi có cuộc gọi đến. -->

</manifest>
```

{% endcode %}

**Tài liệu tham khảo**: <https://www.portsip.com/docs/sdk/manual/portsip-voip-sdk-user-manual-android.pdf>

{% hint style="info" %}
Những ví dụ từ mục II trở đi áp dụng cho 2 file: **MainActivity.kt** và **PortsipService.kt**
{% endhint %}

## II. Khởi tạo SDK <a href="#ii-khoi-tao-sdk" id="ii-khoi-tao-sdk"></a>

#### Ở file `PortsipService.kt`

* Khai báo biến global `val portsipSDK: PortSipSdk = PortSipSdk()` . Dùng để gọi các API của PortsipSDK.
* Class `PortsipService` phải implement interface: `**OnPortSIPEvent` .\*\*

```javascript
fun initialSDK() {
    portsipSDK.setOnPortSIPEvent(this)
    portsipSDK.CreateCallManager(application)
    
    portsipSDK.initialize(
      PortSipEnumDefine.ENUM_TRANSPORT_TCP, "0.0.0.0", 10002,
      PortSipEnumDefine.ENUM_LOG_LEVEL_NONE, "", 8, "PortSIP SDK for Android",
      0, 0, "", "", false, "")
    
    // những cấu hình liên quan đến Audio
    portsipSDK.addAudioCodec(PortSipEnumDefine.ENUM_AUDIOCODEC_OPUS)
    portsipSDK.addAudioCodec(PortSipEnumDefine.ENUM_AUDIOCODEC_G729)
    portsipSDK.addAudioCodec(PortSipEnumDefine.ENUM_AUDIOCODEC_PCMA)
    portsipSDK.addAudioCodec(PortSipEnumDefine.ENUM_AUDIOCODEC_PCMU)

    // những cấu hình liên quan đến Video
    portsipSDK.addVideoCodec(PortSipEnumDefine.ENUM_VIDEOCODEC_H264)
    portsipSDK.addVideoCodec(PortSipEnumDefine.ENUM_VIDEOCODEC_VP8)
    portsipSDK.addAudioCodec(PortSipEnumDefine.ENUM_VIDEOCODEC_VP9)
    portsipSDK.setVideoBitrate(-1, 512)
    portsipSDK.setVideoFrameRate(-1, 20)
    portsipSDK.setVideoResolution(480, 640)
    portsipSDK.setVideoNackStatus(true)
    
    // có thể đặt InstanceId là 1 constant bất kì.
    portsipSDK.setInstanceId("PORTSIP_INSTANCE_ID")
    
    portsipSDK.setLicenseKey("PORTSIP_UC_LICENSE")

}
```

Hàm này nên được gọi chung lúc với đăng nhập máy nhánh.

## III. Đăng nhập máy nhánh (extension) <a href="#iii-dang-nhap-may-nhanh-extension" id="iii-dang-nhap-may-nhanh-extension"></a>

Đầu tiên, ở file ***`MainActivity.kt`***, ta phải khởi chạy `PortsipService` trước

<pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript"><strong>// cần lưu các data về extension (số máy nhánh, password, domain tenant, ...) vào storage của máy.
</strong>// sử dụng SharedPreferences
// Vì khi kill app, các data sẽ bị mất nên phải lưu xuống storage những thông tin này.

// Giải thích:
// số máy nhánh: (ví dụ: 1234, 5678, ...)
// password máy nhánh: (ví dụ: abcDefg123, xzyqerTT11, ...)
// domain tenant: (ví dụ: voip.example.com, ...)

val sharedPref = getSharedPreferences("CallManager", MODE_PRIVATE) ?: return 0
    with(sharedPref.edit()) {
      putString(getString(R.string.call_manager_ps_extension), số máy nhánh)
      putString(getString(R.string.call_manager_ps_password), password)
      putString(getString(R.string.call_manager_ps_domain), domain tenant)
      apply()
    }

Intent(this, PortsipService::class.java).also { intent ->
  startService(intent)
}

</code></pre>

Ở file ***`PortsipService.kt`***, sau khi `startService` như bước 1 thì `onStartCommand` của `PortsipService` được chạy, đăng nhập máy nhánh có thể được gọi lúc này, được hiện thực như ví dụ sau:

{% code overflow="wrap" %}

```javascript
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
  registerPortsip()
  return super.onStartCommand(intent, flags, startId)
}

fun registerPortsip() {
  
  val sharedPref = getSharedPreferences("CallManager", MODE_PRIVATE)
  val psExtension = sharedPref.getString(getString(R.string.call_manager_ps_extension), "")
  val psPassword = sharedPref.getString(getString(R.string.call_manager_ps_password), "")
  val psDomain = sharedPref.getString(getString(R.string.call_manager_ps_domain), "")
  val pushMessage = sharedPref.getString(getString(R.string.call_manager_push_message), "") ?: ""

  // addSipMessageHeader dùng cho việc nhận cuộc gọi khi ứng dụng đang không active
  // ví dụ: tắt màn hình, kill app, ...
  // sẽ trình bày chi tiết ở mục VI.
  if (pushMessage.isNotEmpty()) {
    portsipSDK.clearAddedSipMessageHeaders()
    portsipSDK.addSipMessageHeader(-1, "REGISTER", 1, "x-p-push", pushMessage)
  }

  portsipSDK.removeUser()
  var code = portsipSDK.setUser(
    psExtension, psExtension, psExtension, psPassword, psDomain, "sip.etelecom.vn", 5063,
    "", 0, "", 0)
  
  if (code != PortSipErrorcode.ECoreErrorNone) {
    return
  }
  
  portsipSDK.unRegisterServer()
  code = portsipSDK.registerServer(90, 0)
  
}
```

{% endcode %}

\
Nên có 1 biến `portsipRegistered` dùng để lưu trạng thái đã register với portsip thành công hay chưa. Nếu đã register thành công thì chỉ cần gọi API `refreshRegistration()`, không cần qua 2 bước `initialSDK()` và `registerPortsip()` bên trên.

```javascript
portsipSDK.refreshRegistration(0)
```

Nếu muốn register lại từ đầu thì phải **Khởi tạo SDK** lại như **mục II**.

**Có 2 event sẽ xảy ra khi đăng nhập máy nhánh:**

```javascript
// Đăng nhập máy nhánh thành công
fun onRegisterSuccess(reason: String?, code: Int, sipMessage: String?) {}

// Đăng nhập máy nhánh thất bại
fun onRegisterFailure(reason: String?, code: Int, sipMessage: String?) {}
```

## IV. Đăng xuất máy nhánh (extension) <a href="#iv-dang-xuat-may-nhanh-extension" id="iv-dang-xuat-may-nhanh-extension"></a>

Ở file ***`PortsipService.kt`***

```javascript
portsipSDK.removeUser()
portsipSDK.unRegisterServer()
```

## V. Thực hiện cuộc gọi (outgoing call) <a href="#v-thuc-hien-cuoc-goi-outgoing-call" id="v-thuc-hien-cuoc-goi-outgoing-call"></a>

Ở file `PortsipService.kt`

* ***Để thực hiện cuộc gọi***:

{% code overflow="wrap" %}

```javascript
_sessionID = portsipSDK.call(callee: String, sendSdp: Boolean, videoCall: Boolean)
// _sessionID là 1 biến global, khi một cuộc gọi bất kì được tạo ra thì sẽ có 1 session, biến _sessionID này sẽ lưu lại id của session đó.

// callee là số điện thoại user muốn gọi đến.
// sendSdp: có truyền lên Session Description Protocol hay không. Thường sẽ để bằng true
// videoCall: có thực hiện video call hay không.
```

{% endcode %}

* ***Khi người nhận cuộc gọi chấp nhận cuộc gọi:***

Sự kiện `onInviteAnswered()` xảy ra.

```javascript
fun onInviteAnswered(
  sessionId: Long,
  callerDisplayName: String,
  caller: String,
  calleeDisplayName: String,
  callee: String,
  audioCodecNames: String,
  videoCodecNames: String,
  existsAudio: Boolean,
  existsVideo: Boolean,
  sipMessage: String
) {}
```

Khi người nhận cuộc gọi từ chối cuộc gọi/cuộc gọi không được bắt máy/hay vì 1 lý do nào đó mà cuộc gọi không thành công:

sự kiện `onInviteFailure()` xảy ra.

{% code overflow="wrap" %}

```javascript
fun onInviteFailure(sessionId: Long, reason: String?, code: Int, sipMessage: String?) {}
```

{% endcode %}

Chủ động kết thúc cuộc gọi:

```javascript
portsipSDK.hangUp(sessionID: Long)
```

## VI. Nhận cuộc gọi (incoming call) <a href="#vi-nhan-cuoc-goi-incoming-call" id="vi-nhan-cuoc-goi-incoming-call"></a>

Cần áp dụng Mobile Push (push notification voip) để ứng dụng có thể nhận cuộc gọi trong các trường hợp không active:

* Người dùng không ở màn hình ứng dụng
* Khoá màn hình
* Kill app

### 6.1. Thiết lập mobile push <a href="#id-6-1-thiet-lap-mobile-push" id="id-6-1-thiet-lap-mobile-push"></a>

Để sử dụng mobile push, cần khai báo ứng dụng Android với hệ thống portsip thông qua các bước:

* Lấy 2 thông tin: 1 Server Key và 1 Sender ID trên Firebase của Project.
* `applicationId` nằm trong file `build.gradle (Module: android.app)` ví dụ: vn.etelecom.app
* Gửi cho eTelecom 3 thông tin này để tạo thêm Mobile Push.

### 6.2. Hiện thực <a href="#id-6-2-hien-thuc" id="id-6-2-hien-thuc"></a>

* ***Khi có một cuộc gọi đến máy nhánh***:

Sự kiện `onInviteIncoming()` xảy ra.

```javascript
onInviteIncoming(
  sessionId: Long,
  callerDisplayName: String,
  caller: String,
  calleeDisplayName: String,
  callee: String,
  audioCodecs: String,
  videoCodecs: String,
  existsAudio: Boolean,
  existsVideo: Boolean,
  sipMessage: String
) {
// do something...
// Ví dụ:
// start 1 Activity hiển thị thông tin cuộc gọi đến
}
```

* Tạo 1 service là `FirebaseService`, và khai báo nó ở `AndroidManifest.xml`

{% code title="FirebaseService.kt" overflow="wrap" %}

```javascript
class FirebaseService : FirebaseMessagingService() {

  companion object {
    const val ACTION_REFRESH_PUSH_TOKEN = "REFRESH_PUSH_TOKEN"
    const val ACTION_PROCESS_PUSH_PBX = "PROCESS_PUSH_PBX"
    const val PUSH_TOKEN = "PUSH_TOKEN"
  }

  override fun onCreate() {
    super.onCreate()
  }

  // Khi eTelecom bắn 1 notification về việc có cuộc gọi đến máy nhánh của bạn.
  override fun onMessageReceived(remoteMessage: RemoteMessage) {
    // trong biến data này sẽ gồm thông tin về caller, callee, có video call hay không, ...
    val data: Map<String, String> = remoteMessage.data

    if ***app đang ở trạng thái unactive (kill app, screen off, ...)*** {

      Intent(this, PortsipService::class.java).also { intent ->
        intent.action = ACTION_PROCESS_PUSH_PBX
        startService(intent)
      }

    }
  }

  // Khi token của Firebase được reset
  override fun onNewToken(newToken: String) {
    Intent(this, PortsipService::class.java).also { intent ->
      intent.action = ACTION_REFRESH_PUSH_TOKEN
      intent.putExtra(PUSH_TOKEN, newToken)
      startService(intent)
    }
  }

}
```

{% endcode %}

{% code title="AndroidManifest.xml" %}

```html
<service android:name=".FirebaseService" android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>
```

{% endcode %}

* `build.gradle(Project: android)`, thêm classpath sau đây:

```sh
buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.3.8'
    }
}
```

* `build.gradle(Module: android.app)`, thêm dependencies như sau:

```sh
apply plugin: 'com.google.gms.google-services'

dependencies {
  implementation platform('com.google.firebase:firebase-bom:28.1.0')
  implementation 'com.google.firebase:firebase-messaging'
}
```

***Tạo notification channel:*** để dùng trong việc thông báo cuộc gọi đến. *Ta nên gọi hàm này cùng lúc với đăng nhập máy nhánh.*

{% code overflow="wrap" %}

```javascript
fun initNotification() {
  notiManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

  val callChannel = NotificationChannel(BACKGROUND_INCOMING_CALL_CHANNEL_ID, "Cuộc gọi đến", NotificationManager.IMPORTANCE_HIGH)
  callChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
  
  notiManager?.createNotificationChannel(callChannel)  
}
```

{% endcode %}

Khi `onStartCommand` của `PortsipService` được chạy, đã trình bày chi tiết ở mục III.

{% code overflow="wrap" %}

```javascript
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
  if (intent != null) {
    // có thêm 2 tình huống cần xử lý bên dưới đây (bên cạnh tình huống đã được trình bày ở mục III.)
    // 1. Khi token của Firebase được refresh
    // 2. Khi kill app, hoặc tắt màn hình điện thoại, cuộc gọi đến sẽ được bắn qua Firebase.
  }
    
  return super.onStartCommand(intent, flags, startId)
}
```

{% endcode %}

* Khi token của Firebase được refresh:

```javascript
if (intent.action == FirebaseService.ACTION_REFRESH_PUSH_TOKEN) {
  firebasePushToken = intent.getStringExtra(FirebaseService.PUSH_TOKEN) ?: ""
  refreshPushToken(true)
  portsipSDK.refreshRegistration(0)
}
```

* Khi kill app, hoặc tắt màn hình điện thoại, cuộc gọi đến sẽ được bắn qua Firebase và sẽ xử lý tại đây:

```javascript
if (intent.action == FirebaseService.ACTION_PROCESS_PUSH_PBX) {

    // Đăng nhập máy nhánh lại, vì khi kill app thì trạng thái đăng nhập sẽ mất đi.
    registerPortsip()
    // Sau đó, hiển thị popup notification thông báo cuộc gọi đến.
    showPendingCallNotification()

}
```

* Khi khởi động ứng dụng, lần đầu tiên start của `PortsipService`, ta phải lắng nghe token của Firebase rồi đăng nhập máy nhánh sau.

{% code overflow="wrap" %}

```javascript
if (intent?.action == null || intent.action!!.isEmpty()) {
  FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
    if (!task.isSuccessful) {
      Log.w("FIREBASE", "Fetching FCM registration token failed", task.exception)
      return@OnCompleteListener
    }

    // Get new FCM registration token
    firebasePushToken = task.result ?: ""
    refreshPushToken(true)
    // Trường hợp Firebase init chậm, xảy ra sau khi đăng nhập máy nhánh thành công, ta sẽ cần refreshRegistration lại.
    portsipSDK.refreshRegistration(0)
  })

  registerPortsip()
}
```

{% endcode %}

* Hàm `refreshPushToken` có thể hiện thực như sau:

{% code overflow="wrap" %}

```javascript
fun refreshPushToken(willPush: Boolean) {
  if (firebasePushToken.isNotEmpty()) {
    portsipSDK.clearAddedSipMessageHeaders()
    val pushMessage = "device-os=android;device-uid=$firebasePushToken;allow-call-push=$willPush;allow-message-push=$willPush;app-id=$packageName"
    portsipSDK.addSipMessageHeader(-1, "REGISTER", 1, "x-p-push", pushMessage)

    // lưu pushMessage vào storage của máy
    val sharedPref = getSharedPreferences("CallManager", MODE_PRIVATE)
    with(sharedPref.edit()) {
      putString(getString(R.string.call_manager_push_message), pushMessage)
      apply()
    }
  }
}
```

{% endcode %}

* Hàm `showPendingNotification` có thể hiện thực như sau:

{% code overflow="wrap" %}

```javascript
fun showPendingCallNotification() {
    
  // Cần phải có 1 Activity để hiển thị view Cuộc gọi đến 
  // khi user bấm vào notification hoặc khi màn hình tắt.
  val fullScreenIntent = Intent(this, IncomingActivity::class.java)
  fullScreenIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK

  val fullScreenPendingIntent = PendingIntent.getActivity(this, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT)
  
  val notification = Notification.Builder(this, BACKGROUND_INCOMING_CALL_CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_notification)
    .setColor(Color.argb(1, 47, 204, 112))
    .setContentTitle("Cuộc gọi đến từ")
    .setContentText(Html.fromHtml("<strong>${CallManager.callerParsed}</strong>", Html.FROM_HTML_MODE_LEGACY))
    .setOngoing(true)
    .setShowWhen(true)
    .setContentIntent(fullScreenPendingIntent)
    .setFullScreenIntent(fullScreenPendingIntent, true)
    .build()
  
  notiManager?.notify(BACKGROUND_INCOMING_CALL_NOTIFICATION, notification)
  
}
```

{% endcode %}

Một số thao tác user có thể thực hiện khi có cuộc gọi: Chấp nhận (bắt máy), Từ chối, Ngắt máy (sau khi đã chấp nhận), v.v....

```javascript
//1. Chấp nhận cuộc gọi
portsipSDK.answerCall(sessionId: Long, videoCall: Boolean) {}

//2. Từ chối cuộc gọi
// code nên có giá trị là 486
portsipSDK.rejectCall(sessionId: Long, code: Int) {}

//3. Ngắt máy sau khi cuộc gọi đã được chấp nhận và đang diễn ra
portsipSDK.hangUp(sessionId: Long) {}
```

## VII. Cuộc gọi video <a href="#vii-cuoc-goi-video" id="vii-cuoc-goi-video"></a>

Cần có 1 *Activity* thể hiện cuộc gọi Video, ví dụ như sau:

{% code overflow="wrap" %}

```javascript
class VideoCallActivity: Activity(), View.OnClickListener {
  // video của user
  var localVideo
  // video của người đang đàm thoại với user
  var remoteVideo

  // khi cuộc gọi video được bắt đầu (1 trong caller hoặc callee chấp nhận cuộc gọi từ người còn lại)
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
  
    // phần giao diện của Activity này, được hiện thực trong file video_call_view.xml
    setContentView(R.layout.video_call_view)

    
    localVideo = findViewById(R.id.local_video_view)
    remoteVideo = findViewById(R.id.remote_video_view)

    portsipSDK.setRemoteVideoWindow(sessionId: Long, remoteVideo)
    portsipSDK.displayLocalVideo(true, true, localVideo)
    
    // nếu user tắt cam thì tham số send bằng false, ngược lại thì bằng true
    portsipSDK.sendVideo(sessionId: Long, send: Boolean)
  }

  // khi cuộc gọi kết thúc, Activity bị dismiss thì cần release các video này ra.
  override fun onDestroy() {
    super.onDestroy()

    portsipSDK.displayLocalVideo(false, false, null)
    localVideo.release()

    portsipSDK.setRemoteVideoWindow(sessionId: Long, null)
    remoteVideo.release()
  }
}
```

{% endcode %}

Nên dùng biến *portsipSDK* ở file `PortsipService.kt` để không xảy ra hiện tượng nhiều instance của 1 biến được tạo ra, sẽ phát sinh nhiều bug không mong muốn.

Giao diện của Activity bên trên, có thể hiện thực ở file `video_call_view.xml` như ví dụ sau:

{% code overflow="wrap" %}

```html
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:orientation="vertical"
        android:background="#EEEEEE"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

  <!-- Remote Video: video của người đang đàm thoại với user -->
  <LinearLayout
            android:id="@+id/remote_video_view_wrapper"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:background="#333"
            android:orientation="vertical">

        <com.portsip.PortSIPVideoRenderer
                android:id="@+id/remote_video_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
  </LinearLayout>

  <!-- Local Video: video của user -->
  <LinearLayout
            android:id="@+id/local_video_view_wrapper"
            android:layout_width="120dp"
            android:layout_height="166dp"
            android:layout_alignParentTop="true"
            android:layout_marginTop="70dp"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="22dp"
            android:clipChildren="true"
            android:background="@drawable/transparent_rounded_corner">

        <com.portsip.PortSIPVideoRenderer
                android:id="@+id/local_video_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
  </LinearLayout>

</FrameLayout>
```

{% endcode %}

***Gọi ra và nhận cuộc gọi đến***:

```javascript
// 1. Gọi ra, với videoCall bằng true
portsipSDK.call(callee: String, true, videoCall: Boolean)

// 2. Nhận cuộc gọi đến
onInviteIncoming(
  sessionId: Long,
  callerDisplayName: String,
  caller: String,
  calleeDisplayName: String,
  callee: String,
  audioCodecs: String,
  videoCodecs: String,
  existsAudio: Boolean,
  existsVideo: Boolean,
  sipMessage: String
) {
// existsVideo để cho biết cuộc gọi hiện tại có phải là videoCall hay không.
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.etelecom.vn/tich-hop-api/tong-dai/voip-sdk/android.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
