Android SDK 指南

本页介绍了如何使用 Android SDK。

访问示例应用

如需下载 Android 示例应用,请点击 Android 示例应用。

如需使用 Android 移动 SDK 创建示例应用,您需要满足以下条件:

  • CCAI Platform 门户公司密钥和公司 Secret。

  • Android 5.0(API 级别 21,Lollipop)或更高版本。

  • Firebase Cloud Messaging 或 Google Cloud Messaging(用于推送通知)。

  • 应用已迁移到 AndroidX。

Twilio SDK 升级要求

如果直接使用我们的软件包集成 Android SDK,则需要 Twilio SDK 遵循特定版本,否则可以忽略此要求。

// Twilio VoIP SDK
api 'com.twilio:voice-android:6.1.1'
// Twilio Conversations SDK
api 'com.twilio:conversations-android:3.1.0'

此外,Android SDK 中已包含 Proguard 规则,以确保 ProGuard 不会移除 Twilio Programmable Voice 库,并且在 ProGuard 意外移除该库时,可用于问题排查。

-keep class com.twilio.** { *; }
-keep class tvo.webrtc.** { *; }
-dontwarn tvo.webrtc.**
-keep class com.twilio.voice.** { *; }
-keepattributes InnerClasses

为了支持最新的 Twilio 版本,从 Android SDK 0.34.0 版开始,SDK 不再与以 Java 7 为目标平台的应用实现二进制兼容。如需使用此版本和未来版本,开发者必须升级其应用以定位到 Java 8。请参阅以下代码示例:

android {
    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
}

获取公司密钥和公司 Secret

  1. 使用管理员凭证登录管理门户。

  2. 依次前往设置 > 开发者设置 > 公司密钥和密码。

  3. 检索公司密钥和公司密钥代码。

安装

将 Android SDK 代码库添加到根项目的 Gradle 设置中。

build.gradle(项目)

allprojects {
    repositories {
        google()
        jcenter()
        maven {
            url "https://sdk.ujet.co/android/"
        }
    }
}

build.gradle(模块:app)

dependencies {
    // Replace x.y.z with latest version of CCAI Platform SDK
    def ujetSdkVersion = "x.y.z"
    implementation "co.ujet.android:ujet-android:$ujetSdkVersion"

    // CCAI Platform supports co-browse for Web SDK version 0.46.0 or
    // higher.
    // To use co-browse, declare the following dependency.
    implementation "co.ujet.android:cobrowse:$ujetVersion"
}

设置公司设置

在 AndroidManifest.xml 文件中以元数据形式输入公司设置。

AndroidManifest.xml

<application>
    // ...
    <!-- Company Settings -->
    <meta-data android:name="co.ujet.android.subdomain" android:value="@string/ujet_subdomain"/>
    <meta-data android:name="co.ujet.android.companyKey" android:value="@string/ujet_company_key"/>
    <meta-data android:name="co.ujet.android.companyName" android:value="@string/ujet_company_name"/>
    // ...
</application>

strings.xml

<resources>
    <string name="ujet_subdomain">YOUR_SUBDOMAIN</string>
    <string name="ujet_company_key">YOUR_COMPANY_KEY</string>
    <string name="ujet_company_name">YOUR_COMPANY_NAME</string>
</resources>

JWT 签名

出于安全考虑,最终用户信息必须在您的服务器中作为 JWT 进行签名。

示例应用包含用于测试的 APIManager,您需要将 UJET_COMPANY_SECRET 放入 mock/APIManager.java 中。APIManager 必须实现一个方法,该方法会发起异步调用来对 JWT 身份验证令牌进行签名,并返回该令牌。

在生产环境中,您必须在服务器上实现签名过程。

public class APIManager {
    public static final String UJET_COMPANY_SECRET = "Please input your UJET_COMPANY_SECRET from Ujet developer admin page";
    // ...
}

初始化 SDK

在 Android 应用类 onCreate 方法中初始化 SDK。

public class ExampleApplication extends Application implements UjetRequestListener {
    @Override
    public void onCreate() {
        super.onCreate();

        Ujet.init(this);
    }
    // ...
}

最终用户身份验证

最终用户是指通过应用与您的客户支持团队联系的消费者。

为了在应用中对最终用户进行身份验证,我们引入了 JWT 签名机制。

Android SDK 在需要身份验证时会请求对载荷进行签名。 如果签名成功,应用会将已签名的 JWT 交换为最终用户的身份验证令牌。

在 ExampleApplication 中,您应实现 UjetRequestListener 接口,以便对身份验证令牌和客户数据进行签名。

public class ExampleApplication extends Application implements UjetRequestListener {
    @Override
    public void onCreate() {
        super.onCreate();

        Ujet.init(this);
    }

    @Override
    public void onSignPayloadRequest(Map<String, Object> payload, UjetPayloadType ujetPayloadType, UjetTokenCallback tokenCallback) {
        if (ujetPayloadType == UjetPayloadType.AuthToken) {

            // In production, you should implement this on your server.

            // The server must implement a method that initiates an asynchronous call
to sign and return a JWT auth token.
            APIManager.getHttpManager()
                .getAuthToken(payload, UjetPayloadType.AuthToken, new UjetTokenCallback() {
                    @Override
                    public void onSuccess(@Nullable final String authToken) {
                        tokenCallback.onToken(authToken);
                    }

                    @Override
                    public void onFailure(@Nullable final String authToken) {
                        tokenCallback.onError();
                    }
                });
        }
    }
}

如需了解详情,请参阅最终用户身份验证。

设置推送通知

本部分概述了如何启用移动应用推送通知。

准备 Firebase

您应准备一个 Firebase 项目。

如果您已有项目,则可以使用自己的项目并跳过此过程。

  1. 在 Firebase 控制台中创建一个 Firebase 项目。

  2. 从 Firebase 控制台的“设置”>“常规”中下载 google-services.json。

  1. 在 Firebase 控制台中,从“设置”>“CLOUD MESSAGING”获取服务器密钥。

添加服务账号密钥

您需要向移动应用添加服务账号密钥,才能接收推送通知。如需获取服务账号密钥,请参阅使用服务账号进行身份验证。

如需向移动应用添加服务账号密钥,请按以下步骤操作:

  1. 在 CCAI Platform 门户中,依次点击设置 > 开发者设置。如果您没有看到设置菜单,请点击 菜单。

  2. 前往移动应用窗格。

  3. 点击应用旁边的 修改。系统会显示修改移动应用对话框。

  4. 在服务账号字段中,输入您的服务账号密钥,然后点击保存。

Android 客户端设置

  1. 将 google-services.json 复制到应用目录(例如 PROJECT_ROOT/app/google-services.json)中。

  2. 使用以下代码获取 FCM 令牌:

    FirebaseMessaging.getInstance().getToken()
        .addOnCompleteListener(task -> {
            if (!task.isSuccessful() || task.getResult() == null) {
                Log.w("FCM", "Couldn't get FCM token");
                return;
            }
    
            String token = task.getResult();
            Log.i("FCM", "FCM token: " + token);
        });
    

    FirebaseMessagingService,如果令牌已刷新,则该服务会在清单中注册。如需了解详情,请参阅设置 Firebase Cloud Messaging 客户端应用 (Android)。

    public class YourFirebaseMessagingService extends FirebaseMessagingService {
       /**
           * There are two scenarios when onNewToken is called:
           * 1) When a new token is generated on initial app startup
           * 2) Whenever an existing token is changed
           * Under #2, there are three scenarios when the existing token is changed:
           * A) App is restored to a new device
           * B) User uninstalls/re-installs the app
           * C) User clears app data
           */
       @Override
       public void onNewToken(String token) {
           Log.i("FCM", "FCM token updated: " + token);
       }
    }
    
  3. 在 Application 类中实现 UjetRequestListener.onRequestPushToken。UjetRequestListener.onRequestPushToken 应返回 FCM/GCM 令牌。

    public class YourApplication extends Application implements UjetRequestListener {
        /**
            * Return your FCM/GCM token
            */
        @Override
        public String onRequestPushToken() {
            return yourToken(); // As shown in the previous step, you can get your token using FirebaseMessaging.getInstance().getToken().addOnCompleteListener(task -> { })
        }
    }
    
  4. 处理推送通知。如果您希望 Contact Center AI 平台 (CCAI Platform) 自行处理推送消息,可以直接将数据传递给 UjetPushHandler.handle()。

    • 应用仅处理设置了 ujet_noti_type(或 noti_type,为了向后兼容)字段的消息。

    • 否则,您可以选择仅将包含 ujet_noti_type 的消息发送到 UjetPushHandler.handle() 进行处理。

    以下是推送通知消息的示例:

    {
        "call_id"           : 12345,
        "ujet_noti_type"    : "connect_call",
        "noti_type"         : "connect_call",
        "call_type"         : "ScheduledCall",
        "fail_reason"       : "none",
        "fail_details"      : "none"
    }
    

处理 FCM 消息

public class YourFirebaseMessagingService extends FirebaseMessagingService {
    private UjetPushHandler ujetPushHandler;

    @Override
    public void onCreate() {
        super.onCreate();
        this.ujetPushHandler = new UjetPushHandler(this);
    }

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        if (ujetPushHandler.handle(remoteMessage)) {
            // Handled by CCAI Platform

        } else {
            // Handle your push notification message in here
        }
    }
}

处理 GCM 消息

public class YourGcmListenerService extends GcmListenerService {
    private UjetPushHandler ujetPushHandler;

    @Override
    public void onCreate() {
        super.onCreate();
        this.ujetPushHandler = new UjetPushHandler(this);
    }

    @Override
    public void onMessageReceived(String s, Bundle bundle) {
        if (ujetPushHandler.handle(bundle)) {
            // Handled by CCAI Platform

        } else {
            // Handle your message
        }
    }
}

在 GcmReceiver 中处理 GCM 消息(旧方法)

public class YourGcmReceiver extends WakefulBroadcastReceiver {
    private UjetPushHandler ujetPushHandler;

    @Override
    public void onReceive(Context context, Intent intent) {
        ujetPushHandler = new UjetPushHandler(context);
        if (ujetPushHandler.handle(intent.getExtras())) {
            // Handled by CCAI Platform

        } else {
            // Handle your message
        }
    }
}

启动应用

在您要启动应用的位置添加以下行(不带任何参数):

Ujet.start(new UjetStartOptions.Builder().build());

您也可以在不显示启动画面的情况下启动 Android SDK。

UjetStartOptions ujetStartOptions = new UjetStartOptions.Builder()
        .setSkipSplashEnabled(true)
        .build();

Ujet.start(ujetStartOptions);

您还可以使用此密钥,通过直接访问点从菜单中的特定点启动 Android SDK:

String menuKey = "MENU_KEY";
UjetStartOptions ujetStartOptions = new UjetStartOptions.Builder()
        .setMenuKey(menuKey)
        .build();

Ujet.start(ujetStartOptions);

menuKey 可在 CCAI Platform 门户中通过 Direct Access Point 创建(具有管理员角色)。

  1. 前往设置 > 队列。

  2. 从菜单结构中选择任意队列。

  3. 勾选创建直接访问点。

  4. 在文本表单中输入密钥。

  5. 点击保存。

您还可以使用特定工单 ID 启动 Android SDK,以便将其传递给 CRM。当聊天或通话接通时,系统会打开此工单 ID。

String ticketId = "TICKET_ID";
UjetStartOptions ujetStartOptions = new UjetStartOptions.Builder()
        .setTicketId(ticketId)
        .build();

Ujet.start(ujetStartOptions);

SDK 配置

在启动 Android SDK 之前,您可以配置多个选项。请参阅 UjetOption 类。只有在 CCAI Platform 门户中的设置 > 开发者设置 > MMA > 修改中开启启用 PSTN 备用切换开关时,下表中所述的“备用电话号码”和“网络敏感度”选项才有效。当启用 PSTN 回退切换开关处于关闭状态时,我们不会回退到 PSTN。CCAI Platform 使用默认网络敏感度 (0.85) 来检查网络连接。

选项 说明 值 默认值
日志级别 要在 Logcat 中输出的日志级别。 整数。与 Android 日志的日志级别相同。(最少:2,最多:7) 5 (Log.Warn)
默认语言 默认语言代码。 字符串。ISO 639 语言代码。(例如,en 表示英语) null
后备电话号码 当网络不可用或管理门户中不存在公司的代表手机号码时,系统会将该手机号码用作后备号码。 字符串。电话号码。 null
已启用未捕获异常处理程序 启用未捕获的异常处理程序。如果为 true,应用会在运行时使用 Thread.setDefaultUncaughtExceptionHandler 处理所有未捕获的 SDK 异常。但是,如果同一异常发生两次,应用就会崩溃。 布尔值。 true
网络敏感度 检查网络状态的灵敏度。 介于 0 和 1 之间的双精度值,其中 0 表示敏感度最低,1 表示敏感度最高。如果值为 1,则始终会回退到 PSTN 通话。如果使用,我们建议从 .97 的值开始。门户中设置 > 开发者设置 > 移动应用 > 后备手机号码阈值下的值会覆盖此值。 0.85
已启用深色模式 启用深色模式主题。如果为 true,则当用户开启深色模式时,SDK 会应用深色模式主题,否则会忽略该操作。 布尔值。 false
已启用单渠道 用于显示或绕过单个频道的频道选择界面的配置选项。如果为 true,则 SDK 会显示单个渠道选择屏幕,而不是自动选择渠道,即使菜单队列中仅启用了一个渠道也是如此。 布尔值。 false
自动最小化通话视图 配置选项,用于默认自动最小化初始通话界面,或等待用户将其最小化。 布尔值。 false
已启用智能体图标边框 用于在代理图标周围显示或移除圆形边框的配置选项。 布尔值。 false
选择器视图中的静态字号 用于自动调整或停用选择器项文本大小的配置选项。 布尔值。 false
在聊天中隐藏媒体附件 用于在聊天界面中显示或隐藏媒体附件图标的配置选项。 布尔值。 false
忽略 READ_PHONE_STATE 权限 如果设置为 true,则 SDK 不会请求 READ_PHONE_STATE 权限。如果您不想使用应用内 IVR 通话,请将此标志设置为 true,以避免请求此权限,并且您的应用需要明确移除 CCAI Platform SDK 的 android.permission.READ_PHONE_STATE 权限。请注意,我们不建议将此权限设置为 true,因为应用内 IVR 通话需要此权限才能正常运行。 布尔值 false
Cobrowse.io 许可密钥(如适用) 用于设置 Cobrowse.io 库的配置选项。您可以登录 https://cobrowse.io/dashboard/settings,然后前往许可密钥部分,找到您的 Cobrowse 许可密钥。 字符串 null
自定义对话标题 用于在聊天界面中自定义聊天标题文本的配置选项。 字符串 null
自定义聊天界面快捷回复 用于在对话界面中自定义虚拟客服快速回复的配置选项。默认情况下,虚拟客服快速回复会分组在一起,但如果您想单独显示这些回复,可以使用此配置选项设置 QuickReplyButtonsStyle.INDIVIDUAL UjetStylesOptions QuickReplyButtonsStyle.GROUPED
自定义聊天界面的各种属性 用于自定义各种属性(例如字体、背景颜色、图标等)的配置选项。 UjetStylesOptions null
隐藏状态栏 用于显示或隐藏状态栏的配置选项。如果值为 true,则 SDK 会隐藏状态栏。 布尔值 false
设置加载旋转图标可绘制资源 用于在整个应用中自定义加载旋转图标视图的配置选项。如果该选项不可用或为 null,则我们会使用默认加载视图。 整数 null
横向模式已停用 停用横向模式。如果值为 true,则不会应用横向。 布尔值 false
在聊天中隐藏“发起新对话”内嵌按钮 用于在聊天界面中显示或隐藏“发起新对话”内嵌按钮的配置选项。 布尔值 false
显示 CSAT 跳过按钮 用于在 CSAT 对话框中显示或隐藏“跳过”按钮的配置选项 布尔值 false
禁止最终用户终止对话 用于在对话界面中显示或隐藏“结束对话”按钮的配置选项 布尔值 false
隐藏“下载聊天转写文稿” 在聊天操作菜单和聊天界面中显示或隐藏“下载聊天记录”按钮。如果您在 ujet_common_hide 字符串中添加空格,Hide 文本会消失,并且仅显示返回箭头。 整数。值:

0 = 全部显示

1 = 从选项菜单中隐藏

2 = 在聊天后屏幕中隐藏

3 = 同时在选项菜单和聊天后屏幕中隐藏。

0

UjetOption.setBlockChatTerminationByEndUser

在全局级别关闭推送通知 将 UjetOption.setPushNotificationsAllowed 设置为 false 会绕过所有推送通知依赖项,并阻止推送通知到达最终用户。 布尔值 true
隐藏下一个可预订的时间 在非工作时间聊天签到屏幕上显示或隐藏下次可用时间消息。 布尔值 false
隐藏重新开始 在非工作时间聊天签到屏幕上显示或隐藏重新开始按钮。 布尔值 false
UjetOption ujetOption = new UjetOption.Builder()
        .setLogLevel(Log.INFO)
        .setDefaultLanguage("en")
        .setFallbackPhoneNumber("+18001112222")
        .setUncaughtExceptionHandlerEnabled(false)
        .setNetworkSensitivity(0)
        .setDarkModeEnabled(true)
        .setShowSingleChannelEnabled(true)
        .setAutoMinimizeCallView(true)
        .setShowAgentIconBorderEnabled(true)
        .setStaticFontSizeInPickerView(true)
        .setHideMediaAttachmentInChat(true)
        .setIgnoreReadPhoneStatePermission(true)
        .setCobrowseLicenseKey("COBROWSE_IO_LICENSE_KEY_HERE")
        .setCobrowseURL("COBROWSE_IO_API_URL_HERE")
        .setCustomChatHeaderTitle("CHAT_HEADER_TITLE_TEXT")
        .setUjetStylesOptions(
             new UjetStylesOptions.Builder()
            .setChatQuickReplyButtonsStyle(QuickReplyButtonsStyle.INDIVIDUAL)
            .setChatStyles(new ChatStyles(...)) // See `Content Cards Theme` item
            .build()            )
        .setBlockChatTerminationByEndUser(true)
        .setHideStatusBar(true)
        .setLoadingSpinnerDrawableRes(R.drawable.RESOURCE_NAME)
        .setLandscapeOrientationDisabled(true)
        .setShowCsatSkipButton(false)
        .setHideDownloadChatTranscript(0) // 0 to 3. 0 = Show everywhere, 1 = Hide from the options menu, 2 = Hide from the post chat screen, 3 = Hide from both the options menu and the post chat screen.
        .setPushNotificationsAllowed(true)
        .setHideNextAvailability(true)
        .setHideStartOver(true)
        .build();

//The following customizes various attributes in chat UI
ChatStyles chatStyles = new ChatStyles();
chatStyles.setBackButton(new BackButtonStyle(false, "ujet_agent_sample")); //customizes back button styles
chatStyles.setHeader(...); //customizes chat header styles
chatStyles.setAgentMessageBubbles(...); //customizes agent messages styles
chatStyles.setConsumerMessageBubbles(...); //customizes consumer messages styles
chatStyles.setSystemMessages(...); //customizes system messages styles
chatStyles.setEndChatButton(...); //customizes end chat button styles
chatStyles.setTimeStamps(...); //customizes timestamp styles
chatStyles.setUserInputBar(...); //customizes user input bar styles

UjetOption ujetOption = new UjetOption.Builder()
        .setUjetStylesOptions(
            new UjetStylesOptions.Builder()
            .setChatStyles(chatStyles)
            .build()
        )

//The following customizes various attributes in chat UI using json file. Store json file in assets folder
//and create a method to read json file contents and convert it into json string.
String chatStylesFromJson = parseJsonContentsFromAssetsFolder();

UjetOption ujetOption = new UjetOption.Builder()
        .setUjetStylesOptions(
            new UjetStylesOptions.Builder()
            .setChatStyles(chatStylesFromJson)
            .build()
        )

应用权限

该应用需要以下权限,并会在需要时向用户请求这些权限。

权限 说明
摄像头 用于智能操作,以便拍摄照片和录制视频
麦克风 允许应用通过 Twilio 使用 VoIP 通话
存储 允许应用保存照片和视频

语言偏好设置

Android SDK 将按以下优先级顺序确定语言。

  1. 在应用内的启动画面中选择的语言。

  2. 使用 UjetOptions 选择的默认语言。您可以使用 UjetOptions 中的 setDefaultLanguage("en") 设置默认语言。如需了解详情,请参阅“SDK 配置”部分中的“默认语言”。

  3. 如果应用支持在设备中选择的设备语言(通过设置 > 常规 > 语言),则会使用该语言。

  4. 如果应用不支持设备语言,但支持其最接近的父级方言,则会使用设备语言的最接近方言。例如,如果用户在设备中选择古巴西班牙语作为语言,而应用不支持古巴西班牙语,但支持父级方言西班牙语,则系统会使用西班牙语。

  5. 如果应用不支持设备语言,系统将默认使用英语。

首选渠道

借助“首选渠道”参数,您可以将消费者直接引导至特定渠道。然后,最终用户会跳过渠道选择步骤,直接通过“首选渠道”参数中指定的渠道发起联系。

UjetStartOptions.preferredChannel

Ujet.start(new)
UjetStartOptions.Builder().setPreferredChannel
(UjetPreferredChannel.UjetPreferredChannelChat).build());

事件通知

您可以选择设置 UjetEventListener 以接收应用事件通知。

此处列出了可用的事件类型和说明。

Ujet.setUjetEventListener(new UjetEventListener() {
    @Override
    public void onEvent(UjetEventType eventType, HashMap<String, Object> eventData) {
        // eventType specifies the event type and eventData holds the data related to the event.
        // You can parse the eventData and here we are just logging the event type and event data.
        Log.i("CCAI Platform Event Type", eventType.getValue());

        StringBuilder builder = new StringBuilder();
        for (Map.Entry<String, Object> entry : eventData.entrySet()) {
            builder.append(entry.getKey()).append(" : ").append(entry.getValue()).append("\n");
        }

        Log.i("CCAI Platform Event Data", builder.toString());
    }
});
事件类型 说明 事件中包含的数据
EmailClicked 当最终用户点击电子邮件渠道时触发。 队列菜单数据
EmailSubmitted 在最终用户发送电子邮件时触发。 队列菜单数据、电子邮件提交的数据
SessionPaused 在最终用户最小化聊天或通话会话时触发。 会话数据
SessionResumed 当最终用户从后台切换回聊天或通话会话时触发。 会话数据
SessionCreated 在创建聊天或通话会话时触发。 队列菜单数据、会话创建数据
SessionEnded 在聊天或通话会话结束时触发。 队列菜单数据、会话创建数据、会话结束数据
SdkTerminated 在 SDK 关闭时触发,包括意外关闭的情况。 SDK 终止数据
ContentCardClicked 在用户点击内容卡片时触发。 内容卡片点击数据
ContentCardButtonClicked 在用户点击内容卡片按钮时触发。 内容卡片按钮点击数据
QuickReplyClicked 当用户点击快速回复时触发。 快速回复点击数据
MessageLinkClicked 在用户点击链接时触发。 消息链接点击数据

队列菜单数据

键 类型 说明
event_name 字符串 包含事件名称。例如,“Email Clicked”(点击了电子邮件)。
application 字符串 包含应用名称。例如,“Android”。
app_id 字符串 包含与 Context.getPackageName() 相同的应用标识符。
app_version 字符串 包含应用版本名称和版本代码。示例:“0.32.0 (123)”。
sdk_version 字符串 包含 SDK 版本。例如,“0.32.0”。
timestamp 字符串 包含世界协调时间 (UTC) 格式的时间戳(格式为 yyyy-MM-dd'T'HH:mm:ss'Z')。
device_model 字符串 包含用户设备型号。例如,“Google Pixel”。
device_version 字符串 包含用户设备版本。例如,“10、Q、SDK 29”。
company 字符串 包含公司名称。例如,“公司”。
menu_name 字符串 包含叶节点的名称(用户最后选择的菜单)。例如,“子菜单”。
menu_id 字符串 包含叶节点的 ID(用户最后一次选择的菜单)。例如,“123”。
menu_path 字符串 包含用户选择的完整菜单序列。例如,“Parent / Child / Sub Menu”。
menu_key 字符串 包含 DAP 密钥,此字段为可选字段。例如“special_user_menu”。

通过电子邮件提交的数据

键 类型 说明
has_attachments 布尔值 如果电子邮件有任何附件,则返回 True,否则返回 False。

会话数据

键 类型 说明
event_name 字符串 包含事件名称。例如,“Email Clicked”(点击了电子邮件)。
type 字符串 包含会话类型。例如,“chat”或“call”。
timestamp 字符串 包含世界协调时间 (UTC) 格式的时间戳(格式为 yyyy-MM-dd'T'HH:mm:ss'Z')。

会话创建数据

键 类型 说明
session_id 字符串 包含会话 ID。示例:“100”。
type 字符串 包含会话类型。例如,“chat”或“call”。
end_user_identifier 字符串 包含最终用户标识符。例如,“John”。
messages_end_user 字符串 包含最终用户消息数,仅包含在聊天会话中。例如,“3”。
messages_agent 字符串 包含客服人员消息数,并且仅包含在聊天会话中。例如,“3”。

会话结束数据

键 类型 说明
agent_name 字符串 包含代理名称。例如,“John”。
ended_by 字符串 包含有关谁结束了会话的详细信息。可能的值包括“agent”(当代理结束会话时)、“end_user”(当最终用户结束会话时)、“timeout”(当对话超时时)或“dismissed”(当对话被关闭时)。
duration 字符串 包含会话时长(以秒为单位),仅包含在通话会话中。例如,“30 秒”。

SDK 终止数据

键 类型 说明
event_name 字符串 包含事件名称。例如,“Email Clicked”(点击了电子邮件)。

内容卡片点击数据

键 类型 说明
title 字符串 卡片标题。
title 字符串 卡片标题。
subtitle 字符串 卡片副标题。
body 字符串 内容卡片的说明。
link 字符串 网页链接或深层链接。SDK 会使用操作系统功能打开该应用。
event_params 字典 包含点击事件额外信息的字典。SDK 会使用此信息。

内容卡片按钮点击数据

键 类型 说明
title 字符串 卡片标题。
title 字符串 卡片标题。
link 字符串 网页链接或深层链接。SDK 会使用操作系统功能打开该应用。
event_params 字典 包含点击事件额外信息的字典。SDK 会使用此信息。

快速回复点击数据

键 类型 说明
title 字符串 卡片标题。
title 字符串 卡片标题。
link 字符串 网页链接或深层链接。SDK 会使用操作系统功能打开该应用。
event_params 字典 包含点击事件额外信息的字典。SDK 会使用此信息。

消息链接点击数据

键 类型 说明
event_name 字符串 包含事件名称,例如“Message Link Clicked”(点击了消息链接)。
link 字符串 网页链接或深层链接。SDK 会使用操作系统功能打开该应用。

向 CRM 发送自定义数据

自定义数据可以发送给支持人员,并会显示在来电/聊天的支持服务工单中。

您可以通过以下两种方法发送自定义数据:

  • 签名方法:使用 JWT 进行预定义的数据签名。

  • 未签名方法:使用纯 JSON 的预定义数据(不推荐)。

使用签名方法发送自定义数据

如需使用签名方法发送自定义数据,请实现签名方法。

首先,将自定义数据检索到宿主应用,然后将其发送到服务器进行签名。在服务器上,您可以使用定义的表单添加其他数据。 使用公司密钥对其进行签名,然后通过 JWT 返回。

public class ExampleApplication extends Application implements UjetRequestListener {
    @Override
    public void onCreate() {
        super.onCreate();

        Ujet.init(this);
    }

    @Override
    public void onSignPayloadRequest(Map<String, Object> payload, UjetPayloadType ujetPayloadType, UjetTokenCallback tokenCallback) {
        // ...
        if (ujetPayloadType == UjetPayloadType.CustomData) {
            /**
                * These codes are for providing signed custom data.
                * Add some data from app, and add more sensitive data from server and sign it.
                */
            UjetCustomData appCustomData = new UjetCustomData();
            appCustomData.put("model", "Model", "MODEL1234");
            appCustomData.put("customer_id", "Customer ID", 12345);
            appCustomData.put("temperature", "Temperature", 70.5f);
            appCustomData.put("purchase_date", "Purchase Date", new Date());
            appCustomData.put("battery", "Battery", "52%");
            appCustomData.put("location", "Location", "San Francisco, CA, United States");
            appCustomData.putURL("dashboard_url", "Dashboard URL", "https://internal.dashboard.com/12345");

            payload.put("custom_data", appCustomData.getData());

            tokenCallback.onToken(APIManager.getHttpManager().getSignedCustomData(payload));
        }
        // ...
    }
}

使用未签名的方法发送自定义数据

Google 建议您使用签名方法在应用中发送自定义数据。如需了解详情,请参阅使用签名方法发送自定义数据。

您可以通过启动 Android SDK 并设置启动选项来发送未签名的数据,以便使用 UjetStartOptions.Builder#setUnsignedCustomData 设置自定义数据,并且 UjetTokenCallback 应调用 onToken(null)。

HashMap<String, Object> jsonData = new HashMap<>();
// Convert json string into hashmap object and store it in jsonData
UjetCustomData customData = new UjetCustomData();
customData.putObject("external_chat_transfer", jsonData); // Use `external_chat_transfer` key to send chat transcript data

UjetStartOptions ujetStartOptions = new UjetStartOptions.Builder()
        .setUnsignedCustomData(customData)
        .build();

Ujet.start(ujetStartOptions);

使用未签名的自定义数据发送外部聊天记录

您可以在外部聊天开始时使用未签名的自定义数据将其转写内容发送到 CCAI Platform。使用 UjetCustomData.putObject("external_chat_transfer", hashMapObject) 设置 JSON 格式的转写数据,如下所示:

HashMap<String, Object> jsonData = new HashMap<>();
// Convert json string into hashmap object and store it in jsonData
UjetCustomData customData = new UjetCustomData();
customData.putObject("external_chat_transfer", jsonData); // Use `external_chat_transfer` key to send chat transcript data

UjetStartOptions ujetStartOptions = new UjetStartOptions.Builder()
        .setUnsignedCustomData(customData)
        .build();

Ujet.start(ujetStartOptions);

JSON 格式:

  • greeting_override:字符串

  • agent:字典

    • name:字符串

    • avatar:字符串 [代理头像的网址,可选]

  • transcript:数组

    • sender:字符串 ["end_user" 或 "agent"]

    • timestamp:字符串 [例如 "2021-03-15 12:00:00Z"]

    • content:数组

      • type:字符串 [text、media 之一]

      • text:字符串 [文本类型必需]

      • media:字典 [媒体类型必需]

        • type:字符串 [image、video 之一]

        • url:字符串 [指向媒体文件的公开网址]

JSON 示例:

{
    "greeting_override": "Please hold while we connect you with a human agent.",
    "agent": {
        "name": "Name",
        "avatar": "avatar url"
    },
    "transcript": [
        {
        "sender": "agent",
        "timestamp": "2021-03-15 12:00:15Z",
        "content": [
            {
            "type": "text",
            "text": "**Suggestions shown:**\n\n* Help with batch or delivery\n* Help with metrics or order feedback\n* Help with Instant Cashout"
            }
        ]
        },
        {
        "sender": "end_user",
        "timestamp": "2021-03-15 12:00:16Z",
        "content": [
            {
            "type": "text",
            "text": "Help with batch or delivery"
            }
        ]
        }
    ]
}

您可以在文本类型中使用 Markdown。支持以下格式:

  • 粗体

  • 斜体

  • 下划线

  • 换行符

  • 项目符号列表

  • 编号列表

  • 链接

自定义数据格式

本部分介绍了可在 JWT 中传递的自定义数据的格式。

JSON 编码为 JWT

JSON 应包含 iat 和 exp,以验证 JWT。自定义数据的对象是 custom_data 键的值。

{
    "iat" : 1537399656,
    "exp" : 1537400256,
    "custom_data" : {
        "location" : {
            "label" : "Location",
            "value" : "1000 Stockton St, San Francisco, CA, United States",
            "type" : "string"
        },
        "dashboard_url" : {
            "label" : "Dashboard URL",
            "value" : "http://(company_name)/dashboard/device_user_ID",
            "type" : "url"
        },
        "contact_date" : {
            "label" : "Contact Date",
            "value" : 1537399655992,
            "type" : "date"
        },
        "membership_number" : {
            "label" : "Membership Number",
            "value" : 62303,
            "type" : "number"
        },
        "model" : {
            "label" : "Model",
            "value" : "iPhone",
            "type" : "string"
        },
        "os_version" : {
            "label" : "OS Version",
            "value" : "12.0",
            "type" : "string"
        },
        "last_transaction_id" : {
            "label" : "Last Transaction ID",
            "value" : "243324DE-01A1-4F71-BABC-3572B77AC487",
            "type" : "string"
        },
        "battery" : {
            "label" : "Battery",
            "value" : "-100%",
            "type" : "string"
        },
        "bluetooth" : {
            "label" : "Bluetooth",
            "value" : "Bluetooth not supported",
            "type" : "string"
        },
        "wifi" : {
            "label" : "Wi-Fi",
            "value" : "Wi-Fi not connected",
            "type" : "string"
        },
        "ssn" : {
            "invisible_to_agent" : true,
            "label" : "Social Security Number",
            "value" : "102-186-1837",
            "type" : "string"
        }
    }
}

键是数据的唯一标识符。类型是值的类型。

  • string

    • JSON 字符串
  • number

    • integer, float
  • date

    • UTC Unix 时间戳格式,13 位数字。(包含毫秒)
  • url

    • HTTP 网址格式

标签是 CRM 页面上的显示名称。

阻止显示客户数据

您可以将 invisible_to_agent 属性与自定义数据对象搭配使用,以防止在代理适配器中显示已签名或未签名的自定义数据。在前面的示例中,最终用户的社会保障号不会显示在代理适配器中,因为 "invisible_to_agent" : true 包含在 ssn 对象中。

如果您在自定义数据对象中添加 "invisible_to_agent" : true 属性,则可能会出现以下行为:

如需了解详情,请参阅在代理适配器中查看会话数据。

预留数据属性

您可以在会话开始时将预留数据属性作为已签名的自定义数据发送到 Contact Center AI 平台 (CCAI Platform)。如需了解详情,请参阅发送预留数据属性。

以下是自定义数据中预留数据属性的示例:

  {
    "custom_data": {
      "reserved_verified_customer": {
        "label": "Verified Customer",
        "value": false,
        "type": "boolean"
      },
      "reserved_bad_actor": {
        "label": "Bad Actor",
        "value": false,
        "type": "boolean"
      },
      "reserved_repeat_customer": {
        "label": "Repeat Customer",
        "value": false,
        "type": "boolean"
      }
    }
  }
  

将每个值设置为 true 或 false。

  • reserved_verified_customer:如果您认为此最终用户是合法客户,则为 true。
  • reserved_bad_actor:如果您认为此最终用户可能是恶意行为者,则为 true。
  • reserved_repeat_customer:如果您已确定此最终用户之前曾联系过您的联络中心,则为 true。

后备

您可以使用 UjetErrorListener 作为意外错误的后备。如果您未设置此监听器或返回 false,Android SDK 将处理该错误。

仅当设置 > 开发者设置 > MMA > 修改弹出式窗口中的“启用 PSTN 备用号码”切换开关处于开启状态时,Android SDK 才会将用户重定向到拨号器并提供备用号码,否则会退出 SDK。

错误类型 错误代码 触发器
NETWORK_ERROR 1 网络不可用。请注意,如果在聊天、通话或评分界面期间网络不可用,则不会触发此错误。
AUTHENTICATION_ERROR 100 身份验证期间发生意外错误。
AUTHENTICATION_JWT_ERROR 101 JWT 验证期间发生意外错误(例如解析错误)。
VOIP_CONNECTION_ERROR 1000 未能与 VoIP 提供商建立连接。此操作通过 VoIP SDK 的回调进行处理。
VOIP_LIBRARY_NOT_FOUND 1001 预计会使用 VoIP 提供商连接通话,但找不到提供商。如果开发者集成了错误的 SDK 或未在其依赖项中添加 VoIP 提供商库,就可能会发生这种情况。
CHAT_LIBRARY_NOT_FOUND 1100 在找不到聊天库时发生。如果开发者集成了错误的 SDK 或未在其依赖项中添加 Twilio Chat 库,就可能会发生这种情况。
Ujet.setUjetEventListener(new UjetEventListener() {
    @Override
    public void onEvent(UjetEventType eventType, HashMap<String, Object> eventData) {
        // eventType specifies the event type and eventData holds the data related to the event.
        // You can parse the eventData and here we are just logging the event type and event data.
        Log.i("CCAI Platform Event Type", eventType.getValue());

        StringBuilder builder = new StringBuilder();
        for (Map.Entry<String, Object> entry : eventData.entrySet()) {
            builder.append(entry.getKey()).append(" : ").append(entry.getValue()).append("\n");
        }

        Log.i("CCAI Platform Event Data", builder.toString());
    }
});

深层链接

如果您想对 IVR (PSTN) 通话使用智能操作,则需要在项目中设置深层链接。

深层链接格式是唯一的 URI,例如:

ujet:// <package_name>/smartchannel.

此外,您还必须在管理门户中设置此链接或任何会重定向到此链接的网址(“设置”>“操作管理”>“启用发送短信以下载应用”)。

您需要在清单中添加包含深层链接的 intent 过滤器。

<activity android:name="co.ujet.android.activity.UjetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:host="<package_name>"
                android:scheme="ujet"
                android:path="/smartchannel" />
    </intent-filter>
</activity>

实时聊天弹性

如需提高实时聊天的正常运行时间,请配置实时聊天弹性。这会添加一个辅助后备消息媒体层,在主媒体层发生故障时使用。

如需添加此功能,请将以下依赖项添加到模块级 build.gradle 文件中:

dependencies {
    implementation "co.ujet.android:chat_blue:VERSION"
}

将 VERSION 替换为最新版本的 UJET SDK。

来电行为变更

从搭载 Android 10 版本的设备开始,当主机应用在后台运行时,最终用户将无法直接接听来电。而是使用通知来提醒用户有来电(即使手机处于锁定状态),并让用户选择接听或拒接来电。

当宿主应用在后台运行,且在来电之前锁定了屏幕时,我们会显示相同的通知。此行为变更旨在遵守 Google 最近针对应用在后台运行时启动 activity 施加的限制。当宿主应用在前台运行或在搭载 Android 10 以下版本的设备上运行时,行为不受影响。

自定义 SDK 会话

本部分简要介绍了如何进一步自定义 SDK。

检查是否存在现有会话

在开始会话之前,请使用所述方法检查是否存在任何现有会话或正在进行的会话。如果存在,您可以提示最终用户恢复或取消该订阅。

当用户发生变化时,这一点尤为重要。

if (Ujet.getStatus() != UjetStatus.None) {
    // Display alert to cancel login or resume existing session
}

断开会话连接

如果您想断开任何正在进行的会话,请参阅该方法。

在使用此方法之前,请务必使用 Ujet.getStatus() 检查是否存在此类会话。如果您想在 SDK 断开会话连接后执行操作(例如显示消息或关闭应用),则可以使用下一部分中介绍的响应回调 onFinished(),否则请将回调设置为 null。

Ujet.disconnect(new UjetResponseCallback() {
    @Override
    public void onFinished() {
        // `onFinished()` is triggered after CCAI Platform disconnects the session.
        finish(); // Finishes the activity.
    }
});

清除缓存中的最终用户数据

当最终用户相关数据从您的应用中更新或更改时,您有责任清除缓存。例如,如果最终用户已退出登录,则调用该方法以移除该用户的缓存,以便在下次启动 SDK 时为新的最终用户启动新会话。

Ujet.clearUserData();

隐藏 SDK

您可以使用 Ujet.hideSDK() 方法隐藏 SDK。例如,当 SDK 干扰应用界面时,此功能非常有用。在调用此方法之前,请使用 Ujet.init() 初始化 SDK。如果 SDK 成功隐藏,Ujet.init() 会返回 true,否则会返回 false。隐藏 SDK 后,您可以使用 Ujet.start() 方法再次启动它。

以下示例会隐藏 SDK:

Ujet.hideSDK();

配置外部偏转链接图标

如需自定义外部偏转链接渠道中的图标,请将图标上传到应用的 drawable 文件夹中,并确保在 CCAI Platform 门户中创建外部偏转链接时使用相同的图标名称,具体路径为设置 > Chat > 外部偏转链接 > 查看链接 > 添加偏转链接。

如果 CCAI Platform 门户中的图标名称与上传到应用中的图标不匹配,则 Android SDK 将使用默认图标。

配置调查问卷谢谢图标

您可以通过将图标上传到应用的 drawable 文件夹并使用文件名 ujet_survey_thank_you_icon 作为图标名称,自定义或替换调查问卷致谢页中的图标。

界面和字符串自定义

本部分介绍了如何自定义字符串、内容卡片、主题和聊天标题。

字符串

您可以通过在 strings.xml 中替换每个字符串的键,自定义应用中使用的字符串。

<resources>
    <!--Greeting title and message in splash screen-->
    <string name="ujet_greeting_title">Customer Support</string>
    <string name="ujet_greeting_description">runs on UJET</string>
</resources>

调查问卷配置

文字大小自定义

通过在 dimens.xml 中替换以下键,自定义应用中使用的标题、说明和选择器文本大小。

可自定义的文字大小如下:

<resources>
    <!-- Don't include the following tags if you don't want to customize any of these keys and prefer to use the CCAI Platform default values instead. -->

    <!-- You can customize title text size by updating value here. -->
    <dimen name="ujet_title">10sp</dimen>

    <!-- You can customize description text size by updating value here. -->
    <dimen name="ujet_description">10sp</dimen>

    <!-- You can customize picker text size by updating value here. -->
    <dimen name="ujet_picker_item_text_size">10sp</dimen>
</resources>

主题

请按照以下步骤自定义主题和背景。第 1 步是主题,第 2 步是背景。

  1. 通过替换 style.xml 中每个样式项的键来自定义主题。例如,

    <!--Default style applies to both Light and Dark Mode Themes-->
    <style name="Ujet">
        <item name="ujet_typeFace">ProximaNova-Reg.otf</item>
        <item name="ujet_colorPrimary">@color/primaryDefault</item>
        <item name="ujet_colorPrimaryDark">@color/primaryDarkDefault</item>
        <item name="ujet_buttonRadius">10dp</item>
        <item name="ujet_companyLogo">@drawable/your_company_logo_default</item>
    
        <!-- You can customize the avatar in waiting UI before call or chat is connected by using the following option. -->
        <item name="ujet_defaultAvatar">@drawable/your_default_avatar</item>
    </style>
    
    <!--This is optional and can be used to update style in Light Mode Theme only-->
    <style name="Ujet.Light">
        <item name="ujet_typeFace">ProximaNova-Reg.otf</item>
        <item name="ujet_colorPrimary">@color/primaryLightMode</item>
        <item name="ujet_colorPrimaryDark">@color/primaryDarkLightMode</item>
        <item name="ujet_buttonRadius">10dp</item>
        <item name="ujet_companyLogo">@drawable/your_company_logo_light_mode</item>
    
        <!-- You can customize the avatar in waiting UI before call or chat is connected by using the following option. -->
        <item name="ujet_defaultAvatar">@drawable/your_default_avatar</item>
    </style>
    
    <!--This is optional and can be used to update style in Dark Mode Theme only-->
    <style name="Ujet.Dark">
        <item name="ujet_typeFace">ProximaNova-Reg.otf</item>
        <item name="ujet_colorPrimary">@color/primaryDarkMode</item>
        <item name="ujet_colorPrimaryDark">@color/primaryDarkForDarkMode</item>
        <item name="ujet_buttonRadius">10dp</item>
        <item name="ujet_companyLogo">@drawable/your_company_logo</item>
    
        <!-- You can customize the avatar in waiting UI before call or chat is connected by using the following option. -->
        <item name="ujet_defaultAvatar">@drawable/your_default_avatar</item>
    </style>
    
  2. 您可以通过替换 style.xml 中每个样式项的键,在应用中自定义背景颜色。屏幕截图中显示了可自定义的背景颜色。

    <style name="Ujet">
        <!-- Don't include the following tags if you don't want to customize any of these keys and prefer to use the CCAI Platform default values instead. -->
        <!-- You can customize light mode theme background color by updating value here in hex. -->
        <item name="ujet_colorBackground">@color/backgroundDefault</item>
        <!-- You can customize dark mode theme background color by updating value here in hex. -->
        <item name="ujet_colorBackgroundDark">@color/backgroundDefaultDark</item>
    </style>
    

自定义聊天标题

您可以在界面中自定义对话中的对话标题文本。

您可以使用以下选项自定义聊天标题文本:

<item name="ujet_chatCustomHeaderTextColor">@color/chatHeaderTextLightMode</item>
<item name="ujet_chatCustomHeaderTextColowDark">@color/chatHeaderTextDarkMode</item>
<item name="ujet_chatCustomHeaderTextSize">16sp</item>
<item name="ujet_chatCustomHeaderTextStyle">bold</item>

您可以使用以下选项在聊天界面中自定义虚拟客服快速回复:

<item name="ujet_colorChatQuickReplyButtonBackground">@color/chatQuickReplyButtonBackgroundLightMode</item>
<item name="ujet_colorChatQuickReplyButtonBackgroundDark">@color/chatQuickReplyButtonBackgroundDarkMode</item>
<item name="ujet_colorChatQuickReplyButtonPressedBackground">@color/chatQuickReplyButtonPressedBackgroundLightMode</item>
<item name="ujet_colorChatQuickReplyButtonPressedBackgroundDark">@color/chatQuickReplyButtonPressedBackgroundDarkMode</item>
<item name="ujet_colorChatQuickReplyButtonText">@color/chatQuickReplyButtonTextLightMode</item>
<item name="ujet_colorChatQuickReplyButtonTextDark">@color/chatQuickReplyButtonTextDarkMode</item>
<item name="ujet_colorChatQuickReplyButtonPressedText">@color/chatQuickReplyButtonPressedTextLightMode</item>
<item name="ujet_colorChatQuickReplyButtonPressedTextDark">@color/chatQuickReplyButtonPressedTextDarkMode</item>
<item name="ujet_colorChatQuickReplyButtonStroke">@color/chatQuickReplyButtonStrokeLightMode</item>
<item name="ujet_colorChatQuickReplyButtonStrokeDark">@color/chatQuickReplyButtonStrokeDarkMode</item>
<item name="ujet_chatQuickReplyButtonTypeFace">Kreon-Regular.ttf</item>
<item name="ujet_chatQuickReplyButtonStrokeWidth">3dp</item>
<item name="ujet_chatQuickReplyButtonCornerRadius">3dp</item>
<item name="ujet_chatQuickReplyButtonVerticalMargin">0dp</item>
<item name="ujet_chatQuickReplyButtonHorizontalPadding">10dp</item>
<item name="ujet_chatQuickReplyButtonVerticalPadding">1dp</item>
<item name="ujet_chatQuickReplyButtonAlignment">right</item>

内容卡片

您可以在自定义对话的同时,自定义内容卡片。您可以使用 JSON 文件(请参阅 app/src/main/assets/json/ujet_styles.json 文件中的 content_card property)或使用 ContentCardStyle 类来执行此操作。

ChatStyles(
    ...
    contentCard = ContentCardStyle(
        backgroundColor = "color_reference",
        cornerRadius = 8,
        font = FontStyle(
            colorReference = "color_reference",
            size = 16,
            style = "bold|italic",
            family = "Roboto-Black.ttf",
        ),
        border = BorderStyle(
            color = "color_reference",
            width = 2,
        ),
        title = TextStyle(
            FontStyle(
                colorReference = "color_reference",
                size = 18,
                style = "bold|italic",
                family = "Roboto-Black.ttf",
            )
        ),
        subtitle = TextStyle(
            FontStyle(
                colorReference = "color_reference",
                size = 16,
                style = "bold|italic",
                family = "Roboto-Black.ttf",
            )
        ),
        body = TextStyle(
            FontStyle(
                colorReference = "color_reference",
                size = 16,
                style = "bold|italic",
                family = "Roboto-Black.ttf",
            )
        )
    )
)

网页表单主题

您可以自定义网页表单卡片,也可以自定义对话。您可以使用 JSON 文件(请参阅 app/src/main/assets/json/ujet_styles.json 文件中的 form_card 属性)或使用 FormCardStyle 类来执行此操作。

ChatStyles(
    ...
    formCard = FormCardStyle(
        backgroundColor = "color_reference",
        cornerRadius = 8,
        font = FontStyle(
            colorReference = "color_reference",
            size = 16,
            style = "bold|italic",
            family = "Roboto-Black.ttf",
        ),
        border = BorderStyle(
            color = "color_reference",
            width = 2,
        ),
        title = TextStyle(
            FontStyle(
                colorReference = "color_reference",
                size = 18,
                style = "bold|italic",
                family = "Roboto-Black.ttf",
            )
        ),
        subtitle = TextStyle(
            FontStyle(
                colorReference = "color_reference",
                size = 16,
                style = "bold|italic",
                family = "Roboto-Black.ttf",
            )
        ),
        image = ImageStyle (
           height = 94,
        ),
    )
)

问卷调查

您可以通过将图标上传到应用的 drawable 文件夹来更改调查问卷致谢页上的图标。

请务必使用 ujet_survey_thank_you_icon 作为图标名称。

如需自定义屏幕共享意见征求对话框,请实现 CoBrowseAlertProvider 抽象类。您可以使用 DialogFragment 或 AlertDialog 来实现自定义意见征求对话框。

  • 如果您使用 DialogFragment,请添加两个按钮,一个用于接受,一个用于拒绝。将自定义 DialogFragment 实例传递给 dialogInstance 回调。

  • 如果您使用 AlertDialog,请勿将 DialogFragment 实例传递给 dialogInstance 回调。

征得用户同意后,通过调用 consentStatus 回调并相应地传递布尔值,将其传递给 SDK。

使用以下代码示例实现自定义意见征求对话框:

class CoBrowseAlertProviderImpl : CoBrowseAlertProvider() {
    // You should only implement the cobrowse consent dialog methods that you want to customize and replace the default UI.
    override fun showCoBrowseSessionActivationRequestDialog(context: Context, dialogInstance: (DialogFragment) -> Unit, consentStatus: (Boolean) -> Unit) {

        // Using DialogFragment for customization
        val dialog = ConsentDialog.newInstance(
                title = context.getString(co.ujet.android.R.string.ujet_cobrowse_session_request_alert_title),
                message = context.getString(co.ujet.android.R.string.ujet_cobrowse_session_request_alert_message),
                positiveButton = context.getString(co.ujet.android.R.string.ujet_cobrowse_session_request_alert_button_allow),
                negativeButton = context.getString(co.ujet.android.R.string.ujet_cobrowse_session_request_alert_button_deny),
                cancelable = false,
                onNegativeClick = { consentStatus(false) }, // Pass consent value
                onPositiveClick = { consentStatus(true) }
            )
        // Pass DialogFragment instance
        dialogInstance(dialog)

        // Using AlertDialog for customization, if you use AlertDialog instead of DialogFragment then you don't need to call `dialogInstance(dialog)`, just pass consent value using `consentStatus()`
        val alertDialog = AlertDialog.Builder(context).apply {
           setTitle("Co-Browse Session Request") // Set dialog title
           setMessage("Allow the agent to co-browse with you?") // Set dialog message
           setPositiveButton("Allow"){dialogInterface, _ ->
               // Pass consent
               consentStatus(true)
           }
           setNegativeButton("Deny"){dialogInterface, _ ->
               // Pass consent
               consentStatus(false)
           }
        }.create()
        alertDialog.show()
    }

    override fun showCoBrowseSessionRemoteControlRequestDialog(context: Context, dialogInstance: (DialogFragment) -> Unit, consentStatus: (Boolean) -> Unit) {
        // Same as showCoBrowseSessionActivationRequestDialog
    }
    override fun showCoBrowseSessionFullDeviceRequestDialog(context: Context, dialogInstance: (DialogFragment) -> Unit, consentStatus: (Boolean) -> Unit) {
        // Same as showCoBrowseSessionActivationRequestDialog
    }
    override fun showCoBrowseConfirmationDialog(context: Context, dialogInstance: (DialogFragment) -> Unit, consentStatus: (Boolean) -> Unit) {
        // Same as showCoBrowseSessionActivationRequestDialog
    }
    override fun showStopCoBrowseConfirmationDialog(context: Context, dialogInstance: (DialogFragment) -> Unit, consentStatus: (Boolean) -> Unit) {
        // Same as showCoBrowseSessionActivationRequestDialog
    }
}
// A generic DialogFragment class
class ConsentDialog : DialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        // Implement your custom dialog UI here
    }
}

使用以下 API 将此实现传递给 SDK:

Ujet.setCoBrowseAlertProvider(CoBrowseAlertProviderImpl())

配置网络表单

如需配置 Web 表单,请实现 UjetWebFormListener 接口的 ujetWebFormDidReceive 方法。此方法接收一个事件(FormMessageReceived 映射)作为参数,其中包含与表单相关的信息。该事件包含以下 JSON 结构:

{
    "type": "form_message_received",
    "smart_action_id": 1,
    "external_form_id": "external_foobar",
    "signature": "4868a7e1dcb5..."
}

处理事件

如需处理该事件,请按以下步骤操作:

  1. 从事件映射(smart_action_id、external_form_id 和 signature)中提取相关信息。
  2. 为表单数据生成表单 URI 和签名。
  3. 使用 callback.onEvent(formDataEvent) 将表单数据作为 FormDataEvent 映射传递给 SDK。
  4. 如果在生成 URI 或签名期间发生错误,请使用 callback.onError() 调用错误回调。

传递给 SDK 的映射 (FormDataEvent) 应具有以下结构:

{
    "type": "form_data",
    "signature": "4868a7e1dcb5...",
    "data": {
        "external_form_id": "form_id",
        "smart_action_id": 1,
        "uri": "foobar"
    }
}

签名 (HMAC-SHA256) 必须使用数据生成,并使用共享密钥进行签名。在生成签名之前,必须先按字母顺序对数据的对象键进行排序,并且应将相同的数据发送到 SDK。

以下代码示例展示了如何实现 Web 表单监听器:

class UjetWebFormListenerImpl : UjetWebFormListener {
    override fun ujetWebFormDidReceive(event: Map<String, Any?>, callback: UjetWebFormCallback) {
        try {
            // Extract required fields from the event
            val smartActionId = event["smart_action_id"]
            val externalFormId = event["external_form_id"]
            val signature = event["signature"]
            // Simulate form URI generation (replace with actual implementation)
            val formUri = "generated_form_uri"
            // Prepare the data map, key should be alphabetically ordered
            val data = mapOf(
                "external_form_id" to externalFormId,
                "smart_action_id" to smartActionId,
                "uri" to formUri
            )
            // Generate the signature using `data`
            val generatedSignature = "generated_signature"
            // Create the FormDataEvent map
            val formDataEvent = mapOf(
                "type" to "form_data",
                "signature" to generatedSignature,
                "data" to data
            )
            // Pass the FormDataEvent to the SDK
            callback.onEvent(formDataEvent)
        } catch (e: Throwable) {
            // If any error occurs, invoke the error callback
            callback.onError()
        }
    }
}

使用以下 API 将此实现传递给 SDK:

Ujet.setUjetWebFormListener(UjetWebFormListenerImpl())

问题排查

发起新对话用时超过 30 秒

检查您是否在响应自定义数据的委托方法。您应根据请求返回有效的自定义数据,或仅通过回调返回 null。请参考以下代码。

    @Override
    public void onSignPayloadRequest(Map<String, Object> payload, UjetPayloadType ujetPayloadType, UjetTokenCallback tokenCallback) {
        if (ujetPayloadType == UjetPayloadType.CustomData) {
            tokenCallback.onToken(null);
        }
    }

政策声明说明

如果您在 Google Play 管理中心内收到通知,要求您为 Android SDK 使用的服务或权限声明政策,请使用以下说明之一。

声明 Android SDK 使用的前台服务

Google 在 Android 14 中引入了前台服务类型,并强制要求在启动前台服务时指定该类型,详见 https://developer.android.com/about/versions/14/changes/fgs-types-required#remote-messaging。 Contact Center AI 平台 (CCAI 平台) Android SDK 使用前台服务来发起聊天和通话,因此我们使用 FOREGROUND_SERVICE_REMOTE_MESSAGING 服务类型进行聊天(因为我们处理的是短信),并使用 FOREGROUND_SERVICE_MICROPHONE 服务类型进行通话。 如果没有这些服务类型,SDK 在搭载 Android 14 版本的设备上发起聊天或通话时会崩溃。

声明 Android SDK 使用的全屏 intent 权限

在设备锁定时,需要 USE_FULL_SCREEN_INTENT 权限才能显示来电推送通知。它会提醒最终用户,并全屏显示来电提醒(内置手机应用就是这样向最终用户通知来电的)。

支持 Android 15

如需支持 Android 15,请按以下步骤操作:

  1. 在 build.gradle(:app) 中设置 compileSdkVersion = 35 和 targetSdkVersion = 35。

  2. 确保项目的 Android Gradle 插件 (AGP) 版本为 8.5.1 或更高版本。如需了解详情,请参阅更新共享库的打包方式。

  3. 安装 Java 开发工具包 (JDK) 17 或更高版本,以便使用 AGP 8.0 或更高版本构建 Android 项目。如需了解详情,请参阅需要 JDK 17 才能运行 AGP 8.0。