> For the complete documentation index, see [llms.txt](https://popin.gitbook.io/popin-developer-hub/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://popin.gitbook.io/popin-developer-hub/popin-android-sdk-v2-integration-documentation.md).

# Popin Android SDK v2 – Integration Documentation

A video calling SDK for Android that enables seamless video communication with queue management, scheduling, and real-time notifications.

[![](https://jitpack.io/v/Springr-Creatives/PopinAndroidSDK.svg)](https://jitpack.io/#Springr-Creatives/PopinAndroidSDK)

### Requirements

* **Min SDK:** 24 (Android 7.0)
* **Target SDK:** 35 (Android 15)
* **Java:** 17

***

## Integration

### Step 1: Add JitPack Repository

#### settings.gradle.kts

```kotlin
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven {
            url = uri("https://jitpack.io")
            credentials { username = "YOUR_JITPACK_TOKEN" }
        }
    }
}
```

#### settings.gradle (Groovy)

```groovy
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven {
            url 'https://jitpack.io'
            credentials { username authToken }
        }
    }
}
```

Store the token securely in `local.properties` (do not commit this file):

```properties
authToken=YOUR_JITPACK_TOKEN
```

Then load it in your root `build.gradle`:

```groovy
Properties localProps = new Properties()
localProps.load(new FileInputStream(rootProject.file("local.properties")))
ext.authToken = localProps['authToken'] ?: ""
```

### Step 2: Add the Dependency

```groovy
dependencies {
    // Replace 'Tag' with the latest release version (e.g. 2.0.41)
    implementation 'com.github.Springr-Creatives.PopinAndroidSDK:PopinCustomerSDK:Tag'
}
```

### Step 3: Configure AndroidManifest.xml

```xml
<application ...>
    <meta-data
        android:name="to.popin.androidsdk.POPIN_TOKEN"
        android:value="YOUR_ACCESS_TOKEN" />
</application>
```

### Step 4: Initialize and Use

```java
import to.popin.androidsdk.Popin;
import to.popin.androidsdk.PopinConfig;
import to.popin.androidsdk.listeners.PopinInitListener;
import to.popin.androidsdk.listeners.PopinEventsListener;
import to.popin.androidsdk.models.Product;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initPopin();
    }

    private void initPopin() {
        PopinConfig config = new PopinConfig.Builder()
            .userName("John Doe")
            .contactInfo("9876543210")
            .sandboxMode(true)
            .initListener(new PopinInitListener() {
                @Override
                public void onInitComplete(int userId) {
                    Log.d("Popin", "Ready, userId=" + userId);
                }

                @Override
                public void onInitFailed(String reason) {
                    Log.e("Popin", "Init failed: " + reason);
                }
            })
            .build();

        Popin.init(this, config);
    }

    public void onCallButtonClick(View view) {
        Popin.getInstance().startCall();
    }
}
```

***

## Setup

### Configure AndroidManifest.xml

Add the `POPIN_TOKEN` metadata inside the `<application>` tag. You will receive this token from the Popin Dashboard or your administrator.

```xml
<application ...>

    <meta-data
        android:name="to.popin.androidsdk.POPIN_TOKEN"
        android:value="YOUR_ACCESS_TOKEN" />

</application>
```

> **Note:** The SDK manages runtime permissions automatically. Make sure your app has internet access enabled.

***

## Initialization

Initialize the SDK using `PopinConfig`. This is typically done in your `Application` class or the `onCreate()` method of your main Activity.

### Import Statements

```java
import to.popin.androidsdk.Popin;
import to.popin.androidsdk.PopinConfig;
import to.popin.androidsdk.listeners.PopinInitListener;
import to.popin.androidsdk.listeners.PopinEventsListener;
import to.popin.androidsdk.models.Product;

import java.util.HashMap;
import java.util.Map;
```

### Step 1: Prepare Meta Data (Optional)

```java
Map<String, String> meta = new HashMap<>();
meta.put("businessUnit", "BUY");
meta.put("tenantId", "INDIA_VIDEO_PLATFORM");
```

### Step 2: Prepare Product Data (Optional)

```java
Product product = new Product(
    "product_id_123",
    "Product Name",
    "https://example.com/image.png",
    "https://example.com/product_page",
    "Additional Info",
    "Description or Specs"
);
```

### Step 3: Build the Configuration

```java
PopinConfig config = new PopinConfig.Builder()
        .userName("User Name")
        .contactInfo("9876543210")
        .identifier("your_user_id")
        .sandboxMode(true)
        .persistenceMode(true)
        .debugMode(false)
        .enableIncomingCalls(false)
        .startWithVideoDisabled(false)
        .hideDisconnectButton(false)
        .hideScreenShareButton(true)
        .hideFlipCameraButton(false)
        .hideMuteVideoButton(false)
        .hideMuteAudioButton(false)
        .hideBackButton(false)
        .callerId("your_caller_id")
        .expertDesignation("Expert")
        .secondaryProductText("Additional product info")
        .product(product)
        .meta(meta)
        .initListener(new PopinInitListener() {

            @Override
            public void onInitComplete(int userId) {
                // SDK initialized successfully
            }

            @Override
            public void onInitFailed(String reason) {
                // Initialization failed
            }
        })
        .build();
```

### Step 4: Initialize the SDK

```java
Popin.init(this, config);
```

***

## Configuration Options

### User Information

| Method                | Description                                                                                                          |
| --------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `userName(String)`    | User's display name                                                                                                  |
| `contactInfo(String)` | User's phone number or email                                                                                         |
| `callerId(String)`    | Custom caller identifier                                                                                             |
| `identifier(String)`  | Unique identifier for the user in your system (e.g. user ID or email). Can also be set later using `setIdentifier()` |

### Environment

| Method                            | Description                                           | Default |
| --------------------------------- | ----------------------------------------------------- | ------- |
| `sandboxMode(boolean)`            | Use sandbox server for testing                        | `false` |
| `persistenceMode(boolean)`        | Reuse existing SDK instance across re-initializations | `true`  |
| `debugMode(boolean)`              | Enable verbose SDK logging                            | `false` |
| `enableIncomingCalls(boolean)`    | Enable incoming calls via FCM                         | `false` |
| `startWithVideoDisabled(boolean)` | Start calls with camera disabled                      | `false` |

### UI Button Visibility

| Method                           | Description              | Default |
| -------------------------------- | ------------------------ | ------- |
| `hideDisconnectButton(boolean)`  | Hide disconnect button   | `false` |
| `hideScreenShareButton(boolean)` | Hide screen share button | `false` |
| `hideFlipCameraButton(boolean)`  | Hide camera flip button  | `false` |
| `hideMuteVideoButton(boolean)`   | Hide video mute button   | `false` |
| `hideMuteAudioButton(boolean)`   | Hide audio mute button   | `false` |
| `hideBackButton(boolean)`        | Hide back button         | `false` |

### Product, Metadata & Labels

| Method                            | Description                      |
| --------------------------------- | -------------------------------- |
| `product(Product)`                | Product associated with the call |
| `meta(Map<String, String>)`       | Custom metadata                  |
| `expertDesignation(String)`       | Expert role label                |
| `secondaryProductText(String)`    | Additional product card text     |
| `initListener(PopinInitListener)` | Initialization callbacks         |

### Runtime Configuration Updates

You can update `product`, `callerId`, and `meta` before starting a call without reinitializing the SDK.

```java
PopinConfig config = Popin.getInstance().getConfig();

config.setProduct(new Product(
    "NEW-SKU",
    "New Product",
    "https://example.com/new.jpg",
    "https://example.com/new",
    "Updated product",
    "$199.99"
));

config.setCallerId("new_caller_id");

Map<String, String> newMeta = new HashMap<>();
newMeta.put("source", "android_app");
newMeta.put("campaign", "winter_sale");

config.setMeta(newMeta);

Popin.getInstance().startCall();
```

#### Updating User Name and Contact Info

```java
Popin.getInstance().updateUserInfo(
    "New Name",
    "new@email.com"
);
```

#### Setting User Identifier After Init

```java
Popin.getInstance().setIdentifier("user_12345");
```

***

## Usage

### Setting the Events Listener

```java
Popin.getInstance().setPopinEventsListener(new PopinEventsListener() {

    @Override
    public void onPermissionGiven() {
    }

    @Override
    public void onPermissionDenied() {
    }

    @Override
    public void onCallStart(int callId) {
    }

    @Override
    public void onCallCancel() {
    }

    @Override
    public void onQueuePositionChanged(int position) {
    }

    @Override
    public void onCallMissed() {
    }

    @Override
    public void onCallNetworkFailure(String participant) {
    }

    @Override
    public void onCallConnected() {
    }

    @Override
    public void onCallFailed() {
    }

    @Override
    public void onCallEnd() {
    }

    @Override
    public void onPipStateChanged(boolean isInPipMode) {
    }
});
```

### Starting a Video Call

```java
Popin.getInstance().startCall();
```

### Cancelling a Call

```java
Popin.getInstance().cancelCall();
```

### Logging Out / Deinitializing the SDK

```java
Popin.deinit();
```

> **Note:** After calling `deinit()`, `Popin.getInstance()` will throw until `Popin.init()` is called again.

***

## Scheduling a Call

### Get Available Schedule Slots

```java
import to.popin.androidsdk.listeners.PopinScheduleListener;

Popin.getInstance().getAvailableScheduleSlots(
    new PopinScheduleListener() {

        @Override
        public void onAvailableScheduleLoaded(
                List<ScheduleSlotsModel.ScheduleSlot> slots) {
        }

        @Override
        public void onScheduleLoadError() {
        }
    }
);
```

### Create a Scheduled Call

```java
import to.popin.androidsdk.listeners.PopinCreateScheduleListener;

Popin.getInstance().createSchedule(
    "2024-01-15T10:00:00Z",
    new PopinCreateScheduleListener() {

        @Override
        public void onScheduleCreated() {
        }

        @Override
        public void onScheduleLoadError() {
        }
    }
);
```

***

## Setting a User Group

```java
import to.popin.androidsdk.listeners.PopinSetGroupListener;

Popin.getInstance().setGroup(
    "your_group_identifier",
    new PopinSetGroupListener() {

        @Override
        public void onSuccess() {
        }

        @Override
        public void onFailed(String reason) {
        }
    }
);
```

***

## Inviting Participants

```java
import to.popin.androidsdk.listeners.PopinInviteListener;

Popin.getInstance().inviteParticipant(
    callId,
    new PopinInviteListener() {

        @Override
        public void onInviteSuccess(String inviteUrl) {
        }

        @Override
        public void onInviteFailed(String reason) {
        }
    }
);
```

***

## Receiving Incoming Calls via FCM

> **Important:** For incoming calls to work, `Popin.init()` must be called in your `Application` class.

> **Required Permission**

```xml
<uses-permission
    android:name="android.permission.USE_FULL_SCREEN_INTENT" />
```

### Application Initialization

```java
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        PopinConfig config = new PopinConfig.Builder()
            .userName("User Name")
            .contactInfo("9876543210")
            .enableIncomingCalls(true)
            .build();

        Popin.init(this, config);
    }
}
```

Register in `AndroidManifest.xml`:

```xml
<application
    android:name=".MyApplication"
    ... >
```

Enable incoming calls:

```java
PopinConfig config = new PopinConfig.Builder()
    .enableIncomingCalls(true)
    .build();
```

### Forward the FCM Token

```java
public class MyFirebaseMessagingService
        extends FirebaseMessagingService {

    @Override
    public void onNewToken(String token) {
        super.onNewToken(token);
        Popin.setFcmToken(getApplicationContext(), token);
    }
}
```

### Forward Incoming Call Messages

```java
public class MyFirebaseMessagingService
        extends FirebaseMessagingService {

    @Override
    public void onNewToken(String token) {
        super.onNewToken(token);
        Popin.setFcmToken(getApplicationContext(), token);
    }

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);

        if (Popin.onFcmMessageReceived(
                remoteMessage.getData())) {
            return;
        }

        // Handle your own messages here
    }
}
```

> **Note:** Popin messages contain `"source": "popin"` in the data payload.

### Call Accepted Callback (Optional)

```java
import to.popin.androidsdk.listeners.PopinCallAcceptedListener;

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    super.onMessageReceived(remoteMessage);

    boolean handled =
            Popin.onFcmMessageReceived(
                    remoteMessage.getData(),
                    callId -> Log.d(
                            "Popin",
                            "Call " + callId + " accepted"
                    )
            );

    if (handled) return;

    // Handle your own messages here
}
```

| Method                       | Description                              |
| ---------------------------- | ---------------------------------------- |
| `onCallAccepted(int callId)` | Called when an incoming call is accepted |

#### FCM Flow

```md
![FCM Flow Diagram](docs/fcm_flow.png)
```

***

## Event Listeners Reference

### PopinInitListener

| Method                        | Description               |
| ----------------------------- | ------------------------- |
| `onInitComplete(int userId)`  | SDK initialized and ready |
| `onInitFailed(String reason)` | Initialization failed     |

### PopinEventsListener

| Method                                     | Description             |
| ------------------------------------------ | ----------------------- |
| `onPermissionGiven()`                      | Permissions granted     |
| `onPermissionDenied()`                     | Permissions denied      |
| `onCallStart(int callId)`                  | Call started and queued |
| `onCallCancel()`                           | Call cancelled          |
| `onQueuePositionChanged(int position)`     | Queue position updated  |
| `onCallMissed()`                           | Call missed             |
| `onCallNetworkFailure(String participant)` | Network issue detected  |
| `onCallConnected()`                        | Call connected          |
| `onCallFailed()`                           | Call failed             |
| `onCallEnd()`                              | Call ended              |
| `onPipStateChanged(boolean isInPipMode)`   | PiP mode changed        |

### PopinScheduleListener

| Method                                          | Description           |
| ----------------------------------------------- | --------------------- |
| `onAvailableScheduleLoaded(List<ScheduleSlot>)` | Schedule slots loaded |
| `onScheduleLoadError()`                         | Failed to load slots  |

### PopinCreateScheduleListener

| Method                  | Description               |
| ----------------------- | ------------------------- |
| `onScheduleCreated()`   | Schedule created          |
| `onScheduleLoadError()` | Failed to create schedule |

### PopinInviteListener

| Method                              | Description              |
| ----------------------------------- | ------------------------ |
| `onInviteSuccess(String inviteUrl)` | Invite URL generated     |
| `onInviteFailed(String reason)`     | Invite generation failed |

### PopinSetGroupListener

| Method                    | Description                 |
| ------------------------- | --------------------------- |
| `onSuccess()`             | Group assigned successfully |
| `onFailed(String reason)` | Failed to assign group      |

***

## Permissions

The SDK automatically requests the following permissions at runtime:

| Permission              | Purpose                       |
| ----------------------- | ----------------------------- |
| `INTERNET`              | Network communication         |
| `CAMERA`                | Video capture                 |
| `RECORD_AUDIO`          | Audio capture                 |
| `MODIFY_AUDIO_SETTINGS` | Audio routing                 |
| `ACCESS_NETWORK_STATE`  | Network status                |
| `BLUETOOTH_CONNECT`     | Bluetooth audio (Android 12+) |

***

## ProGuard Configuration

The SDK ships consumer ProGuard rules in both `PopinCustomerSDK` and `PopinCommonSDK`. These rules are merged automatically into your release build when using the AAR.

No manual ProGuard configuration is required.

***

## Environment Configuration

### Production

```java
PopinConfig config = new PopinConfig.Builder()
    .sandboxMode(false)
    .build();
```

### Sandbox (Testing)

```java
PopinConfig config = new PopinConfig.Builder()
    .sandboxMode(true)
    .build();
```
