---
title: "NCG Android SDK Guide"
description: "This document describes how to use the libraries and sample projects included in the SDK."
---
> For the complete documentation index, see [llms.txt](/llms.txt).

import YouTubeEmbed from '../../../../../components/YouTubeEmbed.astro';

DoveRunner NCG Android SDK makes it easy to apply INKA's NCG(Netsync Content Guard) DRM when developing media service apps for Android. This document describes how to use the libraries and sample project included in the SDK.

Details of DoveRunner Multi DRM service integration are provided in [License Token Guide](/content-security/multi-drm/license/license-token/) guides. For technical questions about using the SDK, please visit our [Helpdesk site](https://support.doverunner.com).

:::note 

This SDK product can be downloaded from [DoveRunner Github repository](https://github.com/doverunner/ncg-android-sdk). In the trial account, you can freely test SDK products within the trial limit on the number of licenses issued. However, in order to apply the SDK to commercial services, you must apply for a plan that includes SDK usage rights when subscribing to DoveRunner commercial plans.

:::

## Tutorial Video

This video is a tutorial for playing DRM content using the sample project included in the SDK.

<YouTubeEmbed videoId="iKzem0auGJI" />

> For optimal playback, select '1080p' as the video quality and enable subtitle (Korean or English) before starting playback.

## Development Environment Setting

> **NOTE** :
> This document is prepared in accordance with the following environment.
> - **Android 5.0 (Lollipop) or later**


To apply DoveRunner NCG Android SDK to your project, copy the SDK files to your local Maven repository and configure your Gradle build files.

**Step 1: Copy files to your local Maven repository**

- On **macOS**: Copy to `${HOME}/.m2/repository/com/doverunner/ncg/`
- On **Windows**: Copy to `%USERPROFILE%\.m2\repository\com\doverunner\ncg\`

```bash
# macOS
cp -R libs/* ${HOME}/.m2/repository/com/doverunner/ncg/
```

```cmd
:: Windows
xcopy libs\* %USERPROFILE%\.m2\repository\com\doverunner\ncg\ /E /I
```

**Step 2: Configure your Gradle repositories**

Add `mavenLocal()` as the first repository in your `build.gradle` or `settings.gradle`:

```gradle
repositories {
    mavenLocal()  // Add this as the first repository
    google()
    mavenCentral()
}
```

**Step 3: Add the SDK dependency**

```gradle
dependencies {
    implementation 'com.doverunner:ncg:x.x.x'
}

### AndroidManifest.xml Permission Setting

For the use of DoveRunner Android SDK, the below permissions are required:

|Item     | Description |
|:------- | :----- |
|READ_LOGS | Permission to read LOG |
|INTERNET     | Permission to use network |
|WRITE_EXTERNAL_STORAGE| Permission to use external storage |
|READ_PHONE_STATE | Permission to read the status of device |
|MOUNT_UNMOUNT_FILESYSTEMS | Permission to edit file system |

## Initialization

Only one NCG Core object is generated as a single tone.

```java
public class DemoLibrary {
    static Ncg2Agent g_ncgAgent = Ncg2SdkFactory.getNcgAgentInstance();
    public final static Ncg2Agent getNcgAgent() {
        return g_ncgAgent;
    }
    ...
}
```

In order to use `Ncg2Agent` object, it should be initialized by `init()` method. In general, `init()` method of `Ncg2Agent` may be called when the app starts(`onCreate` of `android.app.Application`).

|Item  | Description |
|:---- | :----- |
|NotSupportOffline | Not support offline mode |
|OfflineSupport | Support offline mode |

If you set the offline policy to `OfflineSupport` then you can limit the count of app execution.
The default limit count is `10` and the value can be changed by calling the `setCountOfExecutionLimit` method in `OfflineSupportPolicy` enum class.

```java
public class DemoApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        initializeNcgAgent();
    }
    private void initializeNcgAgent() {
        try {
            DemoLibrary.getNcgAgent().setHttpRequestCallback(mHttpRequestCallback);
            Ncg2Agent.OfflineSupportPolicy policy = Ncg2Agent.OfflineSupportPolicy.OfflineSupport;
            policy.setCountOfExecutionLimit(20);
            DemoLibrary.getNcgAgent().init(this, policy);
            DemoLibrary.getNcgAgent().enableLog();
        } catch (Ncg2Exception e) {
            e.printStackTrace();
            String errorMsg = "Exception in init() : " + e.getMessage();
            Log.e(DemoLibrary.TAG, errorMsg);
            Toast.makeText(this, errorMsg, Toast.LENGTH_LONG).show();
        }
    }
    ...
}
```

Controlling HTTP communication inside DoveRunner NCG Android SDK requires implementing `HttpRequestCallback` in App and then initializing it before setting callback. You need to call `Ncg2Agent.setHttpRequestCallback` method to set `HttpRequestCallback` object.

```java
public class DemoApplication extends Application{
    private Ncg2Agent.HttpRequestCallback mHttpRequestCallback = new Ncg2Agent.HttpRequestCallback() {

        @Override
        public String sendRequest(String url, String param){
            ...
        }
        @Override
        public byte[] sendRequestResponseBytes(String url, String param) throws NcgHttpRequestException {
            ...
        }
        @Override
        public byte[] sendRequest(String url, String param, int begin, int end) throws NcgHttpRequestException {
            ...
        }
    }
    private void initializeNcgAgent() {
        ...
        DemoLibrary.getNcgAgent().setHttpRequestCallback(mHttpRequestCallback);
        ...
    }
 }
```

## License Management

### Checking License

A DRM license is required to play DRM-protected content. Licensed data contains the rights for content including usage period and whether TV-out is permitted, etc. So the service app should acquire DRM license from license server before playing the content. The below functions are provided to check the license data:

```java
interface Ncg2Agent{
    // Confirm whether a file situated in the entered path and URL is NCG file.
    public boolean isNcgContent(String strFileOrURL) throws Ncg2Exception;

    // Confirm the license of file situated in the entered path and URL for validity.
    public boolean isLicenseValid(String strFileOrURL) throws Ncg2Exception;

    // Confirm the license of content for validity.
    public LicenseValidation checkLicenseValid(String path) throws Ncg2Exception;
}
```

The `LicenseValidation` enum type returned from `checkLicenseValid` method is as follows:

|Item   | Description |
|:----- | :----- |
|ValidLicense | The license is valid. |
|NotExistLicense  | No license or invalid license |
|ExternalDeviceDisallowed| License with external output device disallowed |
|RootedDeviceDisallowed | License disallowed the use of rooted device |
|ExpiredLicense | The license is expired. |
|DeviceTimeModified | Detected device time manipulation |
|OfflineNotSupported | Failed to access server and detected offline while initializing under the policy of not supporting offline mode |
|OfflineStatusTooLong | Online connection is needed because of too many counts of execution offline. For more information, please refer to the following note. |
|NotAuthorizedAppID | Failed to initialize since the executing App is not authenticated in server. |
|ScreenRecorderDetected | Detected a screen recording app |

In Sample Project, confirm license for validity in the following order:

```java
if( DemoLibrary.getNcgAgent().isNcgContent(path) == false ) {
    return true;
}

Ncg2Agent.LicenseValidation validation = DemoLibrary.getNcgAgent().checkLicenseValid(path);

if( validation == LicenseValidation.ValidLicense ) {
    // Routine to process license for validity
    ...
} else if( validation == LicenseValidation.NotExistLicense ) {
    // No license or invalid license
    ...
    // Request license to license server
    DemoLibrary.getNcgAgent().acquireLicenseByPath( path, DemoLibrary.getUserID(), DemoLibrary.getOrderID() );
    if( DemoLibrary.getNcgAgent().checkLicenseValid(path) == LicenseValidation.ValidLicense ) {
        // Acquire valid license from server
    } else {
        // Not acquire license from server
    }
} else if( validation == LicenseValidation.ExternalDeviceDisallowed) {
    // Not allow to play to the connection to external device such as HDMI device
    ...
} else if( validation == LicenseValidation.RootedDeviceDisallowed) {
    // License not allowed in Rooting terminal.
    ...
} else if(validation == LicenseValidation.ExpiredLicense){
    // Expired license
    ...
} else if(validation == LicenseValidation.ScreenRecorderDetected {
    // You can check the package name and the appName of screen recorder app.
    HashMap<String,String> data = validation.getExtraData();
    String appName = data.get("AppName");
    String packageName = data.get("AppPackageName");
    ...
}
```

> **NOTE** :
> To detect the device time manipulation, check if it executes more than limit value set by `setCountOfExecutionLimit` method in the internal offline mode, OfflineStatusTooLong value is returned. `OfflineStatusTooLong` value is returned only when it is set as `OfflineSupport` parameter option at `init()` method called.

### Issuance and Renewal of License

DRM license may be acquired at any time before playing the content; however, SDK Sample App Project tries to issue license at the start of playback. The timing to issue license depends on App scenario. The below functions are provided in DoveRunner NCG Android SDK to issue license:

```java
interface Ncg2Agent{
    // Acquire license via the entered path with UserID and OrderID.
    public void acquireLicenseByPath(String path, String userId, String orderId) throws Ncg2Exception;

    // Acquire license via the entered path with UserID and OrderID, there can be acquired temporary license by setting a temporary.
    public abstract void acquireLicenseByPath( String path, String userID, String orderID, boolean temporary ) throws Ncg2Exception;

    // Acquire license using Content ID.
    public void acquireLicenseByCID(String cid, String userID, String orderID, String acquisitionURL) throws Ncg2Exception;
    // Acquire license using Content ID.
    public void acquireLicenseByCID(String cid, String userID, String siteID, String orderID, String acquisitionURL) throws Ncg2Exception;

    // Acquire license using Content ID, there can be acquired temporary license by setting a temporary.
    public abstract void acquireLicenseByCID(String cid, String userID, String siteID, String orderID, String acquisitionURL, boolean temporary ) throws Ncg2Exception;
}
```

> **NOTE** :
> To support offline scenario, it is needed to acquire license when the content is downloaded online.

### Removing License

One-time license may be removed at any time after use. In the SDK Sample, license is removed when the video play is stopped. The below functions are provided in DoveRunner NCG Android SDK:

```java
interface Ncg2Agent{
    // Delete license via all CIDs of the issued licenses.
    public void removeLicenseAllCID() throws Ncg2Exception;

    // Delete temporary license.
    public abstract void removeAllTemporaryLicense() throws Ncg2Exception;

    // Delete license via CID.
    public void removeLicenseByCID(strContentsID) throws Ncg2Exception;

    // Delete license of the content in the file path.
    public void removeLicenseByPath(strFilename) throws Ncg2Exception;
}
```

## Playing Content

To play content after acquiring license, you can control the local web server by using `Ncg2LocalWebServer` interface. When you specify the path of DRM content in the playback API, the virtual URL of the local web server is returned, and then you can play the URL with MediaPlayer or third party player. Below is the virtual URL functions provided by `Ncg2LocalWebServer`.

```java
interface Ncg2LocalWebServer{
    // In case of Local File,
    public String addLocalFilePathForPlayback(Activity activity, String url, long fileSize) throws Ncg2InvalidLicenseException, Ncg2Exception;

    // In case of PD contents,
    public String addProgressiveDownloadUrlForPlayback(Activity activity, String url) throws Ncg2InvalidLicenseException, Ncg2Exception;

    // In case of HLS,
    public String addHttpLiveStreamUrlForPlayback(Activity activity, String url) throws Ncg2InvalidLicenseException, Ncg2Exception;

    // In case of HLS (live),
    public String addHttpLiveStreamUrlForPlayback(Activity activity, String url, boolean isLiveHLS) throws Ncg2InvalidLicenseException, Ncg2Exception;

    // In case of HLS,
    // unlike addHttpLiveStreamUrlForPlayback(), do not prior check key file equivalent to m3u8 path.
    public String addHttpLiveStreamUrlForPlaybackWithoutChecking(Activity activity, String url) throws Ncg2InvalidLicenseException, Ncg2Exception;

    // In case of HLS,
    // You can call this method to add HLS url without accessing the URLs of m3u8 and key file.
    public String addHttpLiveStreamUrlForPlayback(Activity activity, String url, String cid) throws Ncg2InvalidLicenseException, Ncg2Exception;

    // In case of HLS (live),
    // You can call this method to add HLS url without accessing the URLs of m3u8 and key file.
    public String addHttpLiveStreamUrlForPlayback(Activity activity, String url, String cid, boolean isLiveHLS) throws Ncg2InvalidLicenseException, Ncg2Exception;
}
```

The following code shows how to obtain virtual URL from DoveRunner contents.

```java
public class PlayerActivity extends Activity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Ncg2Agent ncgAgent = Ncg2SdkFactory.getNcgAgentInstance();
        Ncg2LocalWebServer ncgLocalWebServer = ncgAgent.getLocalWebServer();
        ncgLocalWebServer.setWebServerListener(mWebServerCallback);
        ncgLocalWebServer.clearPlaybackUrls();
        try {
            if( mNcgFilePath.contains(".m3u8") ) {
                // HLS Playback
                mPlaybackURL = ncgLocalWebServer.addHttpLiveStreamUrlForPlayback(mNcgFilePath);
            } else if(mNcgFilePath.startsWith("http://")) {
                // PD Playback
                mPlaybackURL = ncgLocalWebServer.addProgressiveDownloadUrlForPlayback(mNcgFilePath);
            } else {
                // Local Playback
                mPlaybackURL = ncgLocalWebServer.addLocalFilePathForPlayback(mNcgFilePath, mNcgFileSize);
            }
        } catch (Ncg2Exception e) {
            e.printStackTrace();
            Toast.makeText(MediaPlayerActivity.this, "[onCreate] Error Occurred. : " + e.getMessage(), Toast.LENGTH_LONG).show();
            return;
        }
    }
}
```

The following code shows how to decrypt and use virtual URL.

```java
try {
    mPlayer.setDataSource(mPlaybackURL);
    mPlayer.prepareAsync();
} catch (IllegalArgumentException e) {
    e.printStackTrace();
    errorMsg = e.getMessage();
} catch (IllegalStateException e) {
    e.printStackTrace();
    errorMsg = e.getMessage();
} catch (IOException e) {
    e.printStackTrace();
    errorMsg = e.getMessage();
}
```

### LocalWebserver Callback

DoveRunner NCG Android SDK utilizes local web server in a proxy server. The event data from the web server can be received via `WebServerListener` interface. As shown in the following codes, you can process notice or error of `Ncg2LocalWebserver`.

> **NOTE** :
> You need to check and return the status of player by using `onCheckPlayerStatus()` method.

```java
private Ncg2LocalWebServer.WebServerListener mWebServerCallback = new Ncg2LocalWebServer.WebServerListener(){
    @Override
    public void onNotification(int notificationCode) {
        //Most of notice codes can be ignored. ⧸⧸⧸
    }

    @Override
    public void onError(int errorCode, String errorMessage) {
        //Show the applicable user error message.
    }

    @Override
    public PlayerState onCheckPlayerStatus(String uri) {
        if (mIsPrepared) {
            // Return PlayerState.ReadyToPlay only when the player can start play.
            return PlayerState.ReadyToPlay;
        } else {
            // Block playing when PlayerState.Fail is returned.
            return PlayerState.Fail;
        }
    }
};
```

> **NOTE** :
> In order to prevent the case that a hacker accesses content data via virtual URL, the state of player should be monitored.
> If the content is accessed via virtual URL but it is not playing, the app should stop the process.

### File Decryption

DoveRunner NCG Android SDK provides the function to also decrypt NCG DRM files. `Ncg2Agent` can return `NcgFile` object to open and read decrypted data from NCG file. The SDK provides the following functions for NCG file decryption:

```java
interface Ncg2Agent{
    //Return NcgFile object.
    public NcgFile createNcgFile();

    interface NcgFile{
        //Open NCG file.
        public void open(String path) throws Ncg2InvalidLicenseException, Ncg2InvalidNcgFileException, Ncg2Exception;
        //Set license and open NCG file.
        public void open(String path, boolean prepare) throws Ncg2InvalidLicenseException, Ncg2InvalidNcgFileException, Ncg2Exception;
        //Call the applicable method in a status after open and make it a status to enable decrypting. 
        public void prepare() throws Ncg2InvalidLicenseException, Ncg2InvalidNcgFileException, Ncg2Exception;
        //Close the opened file.
        public void close();
        //Read content data from NCG file.
        public long read(byte[] buff, long sizeToRead) throws Ncg2Exception;
        //Move the location of file pointer to read.
        public void seek(long offset, SeekMethod seekMethod) throws Ncg2Exception;
        //Return the current location of file pointer.
        public long getCurrentFilePointer() throws Ncg2Exception;
        //Return the size of NCG file header.
        public int getHeaderSize() throws Ncg2Exception;
        //Return InputStream for NCG file.
        public InputStream getInputStream() throws Ncg2Exception;
    }
}
```

The following is a sample code to get decrypted content using `NcgFile` object.

```java
try {
    String ncgFilePath = mFilePath;
    ncgFile = DemoLibrary.getNcgAgent().createNcgFile();
    ncgFile.open(ncgFilePath);
    ncgFile.seek(0, SeekMethod.End);

    // the original file's size will be used for checking success of decryption.
    long contentFileSize = ncgFile.getCurrentFilePointer();
    ncgFile.seek(0, SeekMethod.Begin);
    int pos = mFilePath.lastIndexOf("/");

    // removes ".ncg" in the path of NCG file
    String unpackFilePath = ncgFilePath.substring(0, pos+1) + "unpackFile.mp4";
    fileOutStream = new BufferedOutputStream(new FileOutputStream(unpackFilePath));
    long totalReadBytes = 0;
    while( true ) {
        long readBytes = ncgFile.read(buffer, 1024);
        if( readBytes <= 0 ) {
            break;
        }
        fileOutStream.write(buffer, 0, (int)readBytes);
        totalReadBytes += readBytes;
    }
    if( totalReadBytes == contentFileSize ) {
        Log.d(DemoLibrary.TAG, "Decryption succeeded");
    } else {
        Log.d(DemoLibrary.TAG, "Decryption failed");
        Toast.makeText(mainActivity, "[unpackNcgFiles] decryption failed", Toast.LENGTH_LONG).show();
    }
} catch (Ncg2Exception e) {

}
```

> **NOTE** :
> It is recommended to decrypt and use the data only in memory, because there is a risk to expose original file if you save decrypted data to a file.

### ePub File Decryption (NcgEpubFile)

`NcgEpubFile` is designed for ePub content that requires reading multiple NCG files sharing the **same CID** (e.g., chapters of the same book). It reuses the internal crypto context across multiple `open()` calls for the same CID, reducing overhead significantly.

:::note
**One instance per CID.** A single `NcgEpubFile` instance is dedicated to one CID (one book). To open content with a different CID, call `release()` first or create a new instance. Calling `open()` with a different CID on the same instance without calling `release()` first may result in incorrect license validation.
:::

The following is the interface provided for `NcgEpubFile`:

```java
interface Ncg2Agent {
    // Returns a new NcgEpubFile handle.
    public NcgEpubFile createNcgEpub();

    interface NcgEpubFile {
        // Opens the NCG ePub file for the given CID and file path.
        // On the first call, performs full license validation and header parsing.
        // On subsequent calls with the same CID, reuses the cached crypto context.
        // You do NOT need to call close() before calling open() again with the same CID.
        public void open(String cID, String path) throws Ncg2InvalidLicenseException, Ncg2Exception;

        // Validates the license and prepares the file for decryption.
        // Called automatically by open() on the first open.
        public void prepare() throws Ncg2InvalidLicenseException, Ncg2Exception;

        // Closes the current read session and releases the file stream.
        // Optional for same-CID usage — you may call open() directly without close().
        // Use when you want to explicitly free the file stream (e.g., pausing a session).
        public void close();

        // Fully releases all resources. Must be called when the book session is finished
        // or before opening a different CID on the same instance.
        public void release();

        // Reads decrypted content data.
        public long read(byte[] buff, long sizeToRead) throws Ncg2Exception;

        // Moves the file pointer to the specified offset.
        public void seek(long offset, SeekMethod seekMethod) throws Ncg2Exception;

        // Returns the current file pointer position.
        public long getCurrentFilePointer() throws Ncg2Exception;

        // Returns the size of the decrypted content.
        public long getContentSize() throws Ncg2Exception;

        // Returns an InputStream for the NCG ePub file.
        public InputStream getInputStream() throws Ncg2Exception;
    }
}
```

#### `close()` vs `release()`

| Method | Required? | Purpose | Next `open()` behavior |
|:--- |:--- |:--- |:--- |
| `close()` | Optional | Explicitly release file stream (e.g., pause session) | Fast reopen — skips full security check |
| `release()` | **Required** | End the entire book session or switch CID | Full license re-validation and header re-parse |

#### Basic Usage — Single Book

```java
// Create one NcgEpubFile per book
NcgEpubFile epubFile = DemoLibrary.getNcgAgent().createNcgEpub();

// Read multiple files of the same book — no close() needed between open() calls
epubFile.open(contentID, chapterPath1);
// ... read chapter 1 content ...

epubFile.open(contentID, chapterPath2);  // directly reopen, no close() required
// ... read chapter 2 content ...

epubFile.open(contentID, chapterPath3);
// ... read chapter 3 content ...

// Done with the book — fully release all resources
epubFile.release();
```

#### Managing Multiple Books

```java
// Keep one NcgEpubFile per CID
private final Map<String, NcgEpubFile> mEpubFileCache = new HashMap<>();

private NcgEpubFile getEpubFile(String cID) {
    NcgEpubFile file = mEpubFileCache.get(cID);
    if (file == null) {
        file = DemoLibrary.getNcgAgent().createNcgEpub();
        mEpubFileCache.put(cID, file);
    }
    return file;
}

// Reading a chapter — no close() between files of the same book
public void readChapter(String cID, String chapterPath) throws Ncg2Exception {
    NcgEpubFile epubFile = getEpubFile(cID);
    epubFile.open(cID, chapterPath);
    // ... read ...
    // No close() needed — next open() with same CID reuses the context directly
}

// When leaving a book entirely
public void releaseBook(String cID) {
    NcgEpubFile epubFile = mEpubFileCache.remove(cID);
    if (epubFile != null) {
        epubFile.release();
    }
}

// On Activity/Fragment destroy — release all
public void releaseAll() {
    for (NcgEpubFile file : mEpubFileCache.values()) {
        file.release();
    }
    mEpubFileCache.clear();
}
```

:::note
- `close()` is optional for same-CID usage. Calling `open()` directly without `close()` is safe and faster.
- Always call `release()` in `onDestroy()` or equivalent lifecycle callback to prevent resource leaks.
- A single `NcgEpubFile` instance must only be used by one thread at a time. For parallel processing, create one instance per thread using an object pool.
:::

## Releasing Object

After `Ncg2Agent` object ends its use, `release()` method should be called for object release. The `release()` method should be called when the app is closed. (i.e., when a user explicitly terminates the app). SDK sample app shows a dialog in `OnBackPressed()` method to call `release()` method.

```java
case DIALOG_EXIT :
    builder.setMessage( getString( R.string.confirm_end ));
    builder.setPositiveButton( getString( R.string.yes ), new DialogInterface.OnClickListener() {
        @Override public void onClick(DialogInterface dialog, int which) {
            ...
            finish();
            DemoLibrary.getNcgAgent().release();
        }
    });
    builder.setNegativeButton( R.string.no, null );
    dialog = builder.create();
    break;
```

> **NOTE** :
> There is a case to call `Ncg2Agent.release` method in `onTerminate` method of the app. However, `onTerminate` method is not always called explicitly so it is not recommended to call `Ncg2Agent.release` method in `onTerminate`.

## Processing Error

Errors in DoveRunner NCG Android SDK is processed through exception. There is `Ncg2Exception` class which is a basic exception class; and then there exist a variety of separate exception classes which inherit the basic class. 

`NcgException` class is a basic exception class of DoveRunner NCG Android SDK, which has internally `ErrorCode` and `Error Message` as its members.
If `ErrorCode` is set, `ErrorCode` can be confirmed with `getErrorCode()` method; if `ErrorCode` is not set, -1 is returned.
`Error Message` can be confirmed with `getMessage()` method.
The following is an example code to catch `Ncg2Exception` exception when calling for the case to use `setDataSource()` method of Ncg2Player.

```java
try {
    mPlayer.setDataSource(mNcgFilePath, mNcgFileSize);
    mPlayer.prepareAsync();
} catch (Ncg2Exception e) {
    e.printStackTrace();
    errorMsg = e.getMessage();
} catch (IllegalArgumentException e) {
    e.printStackTrace();
    errorMsg = e.getMessage();
} catch (IllegalStateException e) {
    e.printStackTrace();
    errorMsg = e.getMessage();
} catch (IOException e) {
    e.printStackTrace();
    errorMsg = e.getMessage();
}
```

## Event Log (NcgExceptionalEventListener)

`NcgExceptionalEventListener` is an interface that receives all internal event messages and exception information generated by the NCG SDK. It is intended to help diagnose and collect logs when DRM-related issues occur in a customer's application.

```java
interface Ncg2Agent {
    interface NcgExceptionalEventListener {
        // Receives internal event messages from the SDK
        void log(String message);

        // Receives internal exceptions from the SDK
        void logException(Exception ex);
    }

    // Register the listener
    public void setExceptionalEventListener(NcgExceptionalEventListener listener);
}
```

Register the listener during initialization in `Application.onCreate()`:

```java
public class DemoApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        initializeNcgAgent();
    }

    private void initializeNcgAgent() {
        DemoLibrary.getNcgAgent().setExceptionalEventListener(new Ncg2Agent.NcgExceptionalEventListener() {
            @Override
            public void log(String message) {
                Log.e("NcgSDK", message); // Remove or disable in production builds
            }

            @Override
            public void logException(Exception ex) {
                Log.e("NcgSDK", ex.getMessage()); // Remove or disable in production builds
            }
        });

        try {
            DemoLibrary.getNcgAgent().init(this, Ncg2Agent.OfflineSupportPolicy.OfflineSupport);
        } catch (Ncg2Exception e) {
            e.printStackTrace();
        }
    }
}
```

:::caution
`NcgExceptionalEventListener` is intended for **development and debugging purposes only**. Before publishing your app, make sure all log output inside `log()` and `logException()` is **removed or disabled**. Leaving log output enabled in production may expose sensitive internal DRM information.
:::

## Testing in Emulator

The following API allows SDK usage in emulator environments during development. This must be removed or disabled before distributing the actual app.

```java
// Enable emulator support (development only)
Ncg2Agent ncgAgent = Ncg2SdkFactory.getNcgAgentInstance();
ncgAgent.enableVirtualMachine();

// Disable emulator support (required for production)
ncgAgent.disableVirtualMachine();
```