> For the complete documentation index, see [llms.txt](https://docs.etelecom.vn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.etelecom.vn/api-etelecom-english/api-integration/vpbx/voip-sdk/android.md).

# Android

**I. Setting up Android Studio project**

***Step 1:*** Download portsipSDK[ here](https://www.portsip.com/download-portsip-voip-sdk/) .

***Step 2*****&#x20;:** Unzip the downloaded .zip file, copy the fileAndroid project`AndroidSample/SIPSample_AndroidStudio/SIPSample/libs/portsipvoipsdk.jar`folder`app/libs`

![](https://docs.etelecom.vn/~gitbook/image?url=https%3A%2F%2F4211996220-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F9CUTEBoYNqJaZqxNp3Eh%252Fuploads%252FeK6Zt7DKESBpwXOMtHNn%252Fimage.png%3Falt%3Dmedia%26token%3Df7348a3c-23e3-4613-8297-14c03f5f82b6\&width=768\&dpr=4\&quality=100\&sign=85ed9174\&sv=1)

***Step 3*****&#x20;:** Compile Libs: Right click on the file`portsipvoipsdk.jar`and select`Add as Library...`

***Step 4*****&#x20;:** Declare the necessary permissions in the file`AndroidManifest.xml`

```
<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" />
  <!-- Allows the application to unlock the phone when there is an incoming call. -->
  <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
  <!-- Allows the application to open fullscreen interface when receiving incoming call notification
when the phone is in inactive state (lock screen, kill app, not in application screen) -->
  <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
  <!-- Allows users to operate when the device is in locked state (press to accept/reject calls). -->
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <!-- Allow use of microphone. -->
  <uses-permission android:name="android.permission.CAMERA" />
  <!-- Allow use of camera. -->
  <uses-permission android:name="android.permission.VIBRATE" />
  <!-- Allows the application to control the phone to vibrate when there is an incoming call. -->

</manifest>
```

**Reference** : <https://www.portsip.com/docs/sdk/manual/portsip-voip-sdk-user-manual-android.pdf>

The examples from section II onwards apply to two files: **MainActivity.kt** and **PortsipService.kt**

**II. Initialize SDK**

**In file`PortsipService.kt`**

* Declare global variable `val portsipSDK: PortSipSdk = PortSipSdk()`. Used to call PortsipSDK APIs.
* Class `PortsipService` phải implement interface: `**OnPortSIPEvent` .\*\*

```
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")

}
```

This function should be called together with extension login.

**III. Extension login**

First, in the file ***`MainActivity.kt`***, we have to launch it `PortsipService`first.

```
// need to save data about extensions (extension number, password, domain tenant, ...) to the device's storage.
// use SharedPreferences
// Because when killing the app, the data will be lost, so this information must be saved to storage.

// Explanation:
// extension number: (eg: 1234, 5678, ...)
// extension password: (eg: abcDefg123, xzyqerTT11, ...)
// domain tenant: (eg: 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)
}
```

In the file ***`PortsipService.kt`***, after `startService`step 1 `onStartCommand`is `PortsipService`run, the extension login can be called at this time, implemented as the following example

```
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
 // example: turn off screen, kill app, ...
  // will be presented in detail in section 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)
  
}
```

There should be a variable `portsipRegistered`to save the status of whether the portip has been registered successfully or not. If the registration is successful, just call the API `refreshRegistration()`, no need to go through the 2 steps `initialSDK()`above `registerPortsip()`.

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

If you want to re-register from the beginning, you must **initialize the SDK** again as in **section II** .

**There are 2 events that will occur when logging in to an extension:**

```
// Extension login successful
fun onRegisterSuccess(reason: String?, code: Int, sipMessage: String?) {}

// Extension login failed
fun onRegisterFailure(reason: String?, code: Int, sipMessage: String?) {}
```

**IV. Log out of extension**

In fil&#x65;***`PortsipService.kt`***

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

**V. Making an outgoing call**

In file`PortsipService.kt`

* ***To make a call*** :

```
_sessionID = portsipSDK.call(callee: String, sendSdp: Boolean, videoCall: Boolean)
// _sessionID is a global variable, when any call is created, there will be a session, this _sessionID variable will save the id of that session.

// callee is the phone number the user wants to call.
/ // sendSdp: whether to transmit the Session Description Protocol or not. Usually set to true
// videoCall: whether to make a video call or not.
```

* ***When the call recipient accepts the call:***

Event `onInviteAnswered()`occurs.

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

When the call recipient rejects the call/the call is not answered/or for some reason the call is unsuccessful:

event `onInviteFailure()`occurs

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

Actively end the call:

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

**VI. Receive call (incoming call)**

Mobile Push (push notification voip) needs to be applied so that the application can receive calls in inactive cases:

* User not on application screen
* Lock screen
* Kill app

**6.1. Set up mobile push**

To use mobile push, you need to declare your Android application with the portsip system through the following steps:

* Get 2 information: 1 Server Key and 1 Sender ID on Firebase of Project.
* `applicationId`located in the `build.gradle (Module: android.app)`example file: vn.etelecom.app
* Send this information to eTelecom 3 to create additional Mobile Push.

**6.2. Reality**

* ***When there is a call to the extension*** :

Event `onInviteIncoming()`occurs.

```
onInviteIncoming(
  sessionId: Long,
  callerDisplayName: String,
  caller: String,
  calleeDisplayName: String,
  callee: String,
  audioCodecs: String,
  videoCodecs: String,
  existsAudio: Boolean,
  existsVideo: Boolean,
  sipMessage: String
) {
// do something...
// Example:
// start 1 Activity that displays incoming call information
}
```

* Create a service called `FirebaseService`, and declare it in`AndroidManifest.xml`

FirebaseService.kt

```
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()
  }

  // When eTelecom sends a notification about a call to your extension.
  override fun onMessageReceived(remoteMessage: RemoteMessage) {
    // This data variable will include information about the caller, callee, whether there is a video call or not, ...
    val data: Map<String, String> = remoteMessage.data

    if ***app is in inactive state (kill app, screen off, ...)*** {

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

    }
  }

  // When Firebase token is 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)
    }
  }

}
```

AndroidManifest.xml

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

* `build.gradle(Project: android)`, add the following classpath:

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

* `build.gradle(Module: android.app)`, add dependencies as follows:

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

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

***Create notification channel:*** to use for notification of incoming calls. *We should call this function at the same time as logging in to the extension.*

```
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)  
}
```

When `onStartCommand`the operation `PortsipService`is run, it is detailed in section III.

```
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
  if (intent != null) {
// there are 2 more situations to handle below (besides the situation presented in section III.)
// 1. When the Firebase token is refreshed
// 2. When the app is killed, or the phone screen is turned off, the incoming call will be sent via Firebase.
  }
    
  return super.onStartCommand(intent, flags, startId)
}
```

* When the Firebase token is refreshed:

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

* When you kill the app, or turn off the phone screen, incoming calls will be fired through Firebase and will be processed here:

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

   // Log in to the extension again, because when you kill the app, the login status will be lost.
    registerPortsip()
   // Then display popup notification of incoming call.
    showPendingCallNotification()

}
```

* When starting the application, the first time we start `PortsipService`, we have to listen for the Firebase token and then log in to the extension.

```
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)
   // In case Firebase init is slow, which happens after successful extension login, we will need to refreshRegistration again.
    portsipSDK.refreshRegistration(0)
  })

  registerPortsip()
}
```

* The function `refreshPushToken`can be implemented as follows:

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

   // save pushMessage to device storage
    val sharedPref = getSharedPreferences("CallManager", MODE_PRIVATE)
    with(sharedPref.edit()) {
      putString(getString(R.string.call_manager_push_message), pushMessage)
      apply()
    }
  }
}
```

* The function `showPendingNotification`can be implemented as follows:

```
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)
  
}
```

Some actions the user can perform when receiving a call: Accept (pick up the phone), Reject, Hang up (after accepting), etc...

```
//1. Accept the call
portsipSDK.answerCall(sessionId: Long, videoCall: Boolean) {}

//2. Reject the call
// code should be 486
portsipSDK.rejectCall(sessionId: Long, code: Int) {}

//3. Hang up after the call has been accepted and is in progress
portsipSDK.hangUp(sessionId: Long) {}
```

**VII. Video call**

There needs to be an *Activity* that represents the Video call, for example:

```
class VideoCallActivity: Activity(), View.OnClickListener {
   // user's video
  var localVideo
  // video of the person talking to the user
  var remoteVideo

 // when video call is initiated (1 of the caller or callee accepts call from the other)
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
  
    // the interface of this Activity, implemented in the 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)
    
   // if the user turns off the cam, the send parameter is false, otherwise it is true
    portsipSDK.sendVideo(sessionId: Long, send: Boolean)
  }

  // when the call ends, the Activity is dismissed, then these videos need to be released.
  override fun onDestroy() {
    super.onDestroy()

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

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

You should use the *portsipSDK* variable in the file `PortsipService.kt`to avoid creating multiple instances of a variable, which will cause many unwanted bugs.

The interface of the above Activity can be implemented in a file `video_call_view.xml`as shown in the following example:

```
<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 of person talking to 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: user's video -->
  <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>
```

***Make and receive calls*** :

```
// 1. Call out, with videoCall equal to true
portsipSDK.call(callee: String, true, videoCall: Boolean)

// 2. Receive incoming calls
onInviteIncoming(
  sessionId: Long,
  callerDisplayName: String,
  caller: String,
  calleeDisplayName: String,
  callee: String,
  audioCodecs: String,
  videoCodecs: String,
  existsAudio: Boolean,
  existsVideo: Boolean,
  sipMessage: String
) {
// existsVideo to indicate whether the current call is a videoCall or not.
}
```
