package cn.tee3.avd;
/**
 * Created by bob.liu on 2015/8/24.
 */

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.os.Looper;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import javax.annotation.Nullable;

import tee3.webrtc.DefaultVideoDecoderFactory;
import tee3.webrtc.DefaultVideoEncoderFactory;
import tee3.webrtc.EglBase;
import tee3.webrtc.Logging;
import tee3.webrtc.MediaCodecVideoEncoder;
import tee3.webrtc.PeerConnectionFactory;
import tee3.webrtc.SoftwareVideoDecoderFactory;
import tee3.webrtc.SoftwareVideoEncoderFactory;
import tee3.webrtc.VideoCodecInfo;
import tee3.webrtc.VideoDecoderFactory;
import tee3.webrtc.VideoEncoderFactory;
import tee3.webrtc.audio.AudioDeviceModule;
import tee3.webrtc.audio.JavaAudioDeviceModule;
import tee3.webrtc.audio.LegacyAudioDeviceModule;
import tee3.webrtc.voiceengine.WebRtcAudioManager;
import tee3.webrtc.voiceengine.WebRtcAudioRecord;
import tee3.webrtc.voiceengine.WebRtcAudioTrack;
import tee3.webrtc.voiceengine.WebRtcAudioUtils;

/**
 * AVD引擎接口类
 * <p>
 * AVD引擎接口定义了引擎全局初始化操作和房间安排、查询、删除等操作。
 */
public class AVDEngine {
    private static final String TAG = "AVDEngine";
    private static String soName = "cn_tee3_avd_so";
    private int audioSampleRate = -1;

    private JavaAudioDeviceModule.SamplesReadyCallback clientAudioSamplesCallback;
    private JavaAudioDeviceModule.SamplesReadyCallback audioSamplesCallback = new JavaAudioDeviceModule.SamplesReadyCallback() {
        @Override
        public void onWebRtcAudioRecordSamplesReady(JavaAudioDeviceModule.AudioSamples samples) {
            if (clientAudioSamplesCallback != null) {
                clientAudioSamplesCallback.onWebRtcAudioRecordSamplesReady(samples);
            }
        }
    };

    public enum DataCryptoType {
        AES128(1),
        AES256(2),
        SM4(3);

        private int value;
        DataCryptoType(int value) {
            this.value = value;
        }
        public int getValue() {
            return value;
        }
    }

    /**
     * 引擎选项
     *
     * @note 引擎选项枚举列出了用于设置引擎的选项，在以下函数中使用。
     * @sa setOption
     * @sa getOption
     */
    public static enum Option {
        eo_camera_mode_frontback(0), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示摄像头使用模式：前后置摄像头模式(true)和多摄像头模式（false）。
         * 多摄像头模式：本机的所有摄像头将展现在房间中；可以同时预览发布多个摄像头；桌面端本地应用和网页端应用多采用此种方式。
         * 前后置摄像头模式：本机的前后置摄像头将作为一个摄像头展现在房间中；同一时刻只能打开一个摄像头；普通移动端采用此种方式。
         */
        eo_camera_capability_default(1), /**
         * < 值类型为CameraCapability, 字符类型为"{\"width\":640,\"height\":480,\"maxFPS\":10}"：表示摄像头缺省采集参数。当前系统默认的采集
         * 参数为：windows桌面版和web网页版{640，480，10}；android/ios移动版{320，240，10}。
         */
        eo_video_quality_publish_default(2), /**
         * < 值类型为VideoQuality, 字符类型为"low" or "normal" or "high"：表示摄像头发布缺省采用的视频质量，与camera_capability_default的设置相互影响。
         * 当前系统默认的采集参数为：windows桌面版和web网页版 普通质量；android/ios移动版 流畅质量。
         */
        eo_video_ration_default(3), /**
         * < 值类型为VideoRatio, 字符类型为"4_3" or "16_9" or "cif"：表示视频缺省宽高比。
         * 当前系统默认的采集参数为：4:3。
         */
        eo_video_quality_subscribe_default(4), /**
         * < 值类型为VideoQuality, 字符类型为"low" or "normal" or "high"：表示订阅视频缺省采用的视频质量。
         * 当前系统默认的采集参数为：windows桌面版和web网页版 普通质量；android/ios移动版 流畅质量。
         */
        eo_video_publish_svc_default(5), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示视频发布时缺省是否采用多流。
         * 当前系统默认的采集参数为：false。
         */
        eo_video_codec_priority(6), /**
         * < 值类型为VideoCodec, 字符类型为"h264"，"vp8"：表示视频编码优先采用h264，vp8编解码。
         * 当前系统默认参数值为：vp8。
         */
        eo_demo_urlbase_liverecord(7), /**
         * < 值类型为String：表示演示直播和录制的服务器地址，只能获取。
         */
        eo_data_channel_tcp_priority(8), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示房间中数据通道用的网络连接类型：TCP优先(true)和UDP优先（false），
         * 缺省为UDP优先。
         */
        eo_network_proxy(9), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示启用或不启用，建议局域网或者简单的环境不使用代理。
         * 当前系统默认参数值为：true。
         */
        eo_video_resolution_16balign(10), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示启用或不启用视频分辨率16字节严格对齐模式；在16字节对齐模式下，分辨率的宽和高都会向对齐的分辨率靠，
         * 如1080-->1088,540-->544,180-->176等，有些硬件设备（某些android手机）只支持16字节对齐的硬件编解码，android上可以考虑开启此选项来做更好的设备兼容性和编码效率。
         * 当前系统默认参数值为：false。
         */
        eo_video_swapwh_by_rotation(11), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示视频publish或preview时设置的分辨率根据输入视频旋转角度变换宽高：变换宽高(true)和不变换宽高严格按设置分辨率裁剪拉伸（false），
         * 缺省为不变换宽高严格按设置分辨率裁剪拉伸。
         */
        eo_video_codec_hw_priority(12), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示是否优先使用硬件编解码：优先使用硬件编解码(true)和优先使用软件编解码（false），
         * 缺省为硬件编解码优先。
         */
        eo_audio_aec_LowLatencyModeDelayEstimate(13), /**
         * < android系统可用；值类型为int, 取值：[30, 400]; 字符类型数值字符串如"50"：表示AEC延时下限，用于回音消除。
         * 缺省为 80； 回音消除算法与设备的麦克风和扬声器距离，及会场大小都有关系，有可能需要特别调试。
         */
        eo_audio_aec_HighLatencyModeDelayEstimate(14), /**
         * < android系统可用；值类型为int, 取值：[30, 400]; 字符类型数值字符串如"50"：表示AEC延时上限，用于回音消除。
         * 缺省为 160； 回音消除算法与设备的麦克风和扬声器距离，及会场大小都有关系，有可能需要特别调试。
         */
        eo_audio_agc_PlayoutGainMultipleValue(15), /**
         * < 值类型为float, 取值：[0.1, 10.0]; 字符类型数值字符串如"2.0"：表示语音播放时的增益倍数。
         * 缺省为 1.0。
         */
        eo_audio_agc_RecordGainMultipleValue(16), /**< 值类型为float, 取值：[0.1, 10.0]; 字符类型数值字符串如"2.0"：表示语音采集后的增益倍数。
         * 缺省为 1.0。值设置过大会导致声音失真。
         */
        eo_audio_aec_AudioDelayOffset(17), /**
         * < 值类型为int, 取值：[0, 500]; 字符类型数值字符串如"50"：表示AEC估计延时，用于回音消除。
         * 缺省为 0； 回音消除算法与设备的麦克风和扬声器距离，及会场大小都有关系，有可能需要特别调试。
         * | delay | in ms between AnalyzeReverseStream() receiving a far-end frame and
         * ProcessStream() receiving a near-end frame containing the corresponding echo.
         * On the client-side this can be expressed as
         * delay = (t_render - t_analyze) + (t_process - t_capture)
         */
        eo_audio_aec_Enable(18), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示启用或不启用, 一般情况下都需要回音消除算法，当需要录制音乐的时候，建议关闭。
         * 当前系统默认参数值为：true。
         */
        eo_audio_aec_DAEcho_Enable(19), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示在回音消除时启用或不启用延时消除算法，此算法较耗费cpu，但效果会较好，移动端缺省不启用，如果特殊需要就启用。
         * 当前系统默认参数值为：false。
         */
        eo_audio_noiseSuppression_Enable(20), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示在音频采集时是否使用噪音抑制，抑制后会消除部分噪音。
         * 当前系统默认参数值为：true。
         */
        eo_audio_autoGainControl_Enable(21), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示在音频采集时是否启用自动增益，增益打开后会自动调整麦克风采集音频大小。
         * 当前系统默认参数值为：true。
         */
        eo_audio_highpassFilter_Enable(22), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示在音频采集时是否启用高通滤波，一些尖锐的声音会被过滤掉。
         * 当前系统默认参数值为：false。
         */
        eo_mcu_cluster_route_params(23), /**
         * < 值类型为json String：设置分布式集群部署中加房间时mcu服务器的分配路由参数。
         * 取值格式如下：{"ip_tag":"local","idc_code":""}
         * ip_tag  对应于 rtc_node_addr 中的tag标志，用于区分同一台服务器的多网卡地址，可以自定义，然后在参数中传入。举例的话，比如 'local','internal','dianxin','liantong'
         * idc_code 对应于 rtc_node 中的 idc_code标志，用于区分不同的服务器，唯一，可以自定义。比如北京服务器设置为'beijing'，杭州的设置为'hangzhou'，然后在参数中传入，用于定位到服务器。
         */
        eo_media_connproxy_ip(24), /**
         * < 值类型为String：表示媒体服务器IP映射后对应的映射IP，通过连接此IP和相关端口可以与真实媒体服务器通讯。
         */
        eo_media_connproxy_port(25), /**
         * < 值类型为int,字符类型数值字符串如"8050"：表示媒体服务器端口映射后对应的映射端口，通过连接映射IP和此I端口可以与真实媒体服务器通讯。
         */
        eo_video_codec_hw_encode(26), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示是否优先使用硬件编码：优先使用硬件编码(true)和优先使用软件编码（false），
         * 缺省为硬件编码优先。
         */
        eo_video_codec_hw_decode(27), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示是否优先使用硬件解码：优先使用硬件解码(true)和优先使用软件解码（false），
         * 缺省为硬件解码优先。
         */
        eo_video_renderusecapture(28), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示在导入视频时的渲染attachRender时，是否采用导入视频直接渲染还是经过拉伸的视频再渲染。
         * 当前系统默认参数值为：false。
         */
        eo_audio_noiseSuppression_Level(29), /**
         * < 值类型为int, 取值：[0, 3]; 字符类型数值字符串如"1"：表示在使用噪音抑制时的音频噪音程度，数字越大抑制越厉害。
         * 当前系统默认参数值为：2。
         */
        eo_dump_audio_pcm_record(30), /**
         * 调试时使用：是否录制本地音频，类型bool ，字符串 "true" or "false",加会之前调用,生成文件在日志文件目录下。
         * 当前系统默认参数值为：false，
         */

        eo_video_resolution_lowNoSupported(31), /**
         * 对低分辨率视频不支持机型的设置选项，“true” 代表不支持，“false” 代表支持
         */

        eo_video_localrender_lowfps(32),    /** 这个参数主要是安卓系统系统下使用，值类型为int 值范围是[1，2，3] 默认是3，如果值为3，接受到帧数除以3后的数量来渲染。*/

        eo_video_enable_simulcast(33),      /** 是否开启多流，类型bool，字符串 "true" or "false"，系统默认false，不开启。 */

        eo_auto_change_resolution(101),     /** 值类型为bool，是否改变发布视频质量（分辨率和码流），字符类型为"true" or "false"：默认是true*/

        /**
         * < 下面的选项预留测试使用，分别表示字符串，整形等自定义参数，一直放在最后
         */
        eo_test_rtc_string_1(2000),
        eo_test_rtc_int_1(2001),
        eo_internal_hardware_identifier(2002), /** < 值类型为String：表示设备的硬件标识号，sdk内部使用。*/
        eo_auto_drop_frame(2005),               /**< 值类型为bool, 字符类型为"true" or "false"：当编码器码率超过设置的码率时，通过丢弃一部分数据帧，尽量避免码率变化过大而引起网络堵塞；系统默认为false*/
        eo_auto_drop_frame_limit_bitrate_times(2006), /**< 值类型为int, 系统默认为1倍，静态码流和动态码流一致：eo_auto_drop_frame 被启用后，为了平滑码率会丢掉部分视频帧，该参数可以限制丢帧模块输出码流上限，为静态码流的多少倍。*/
        eo_auto_drop_frame_min_framerate(2007), /**< 值类型为int, 系统默认为-1：eo_auto_drop_frame 被启用后，为了平滑码率会丢掉部分视频帧，该参数可以限制丢帧模块最小输出帧率。*/

        /// only for android
        eo_video_codec_support_h264hw(3000), /**
         * < 值类型为bool, 字符类型为"true" or "false"：表示当前设备是否支持h264的硬件编解码。
         */
        eo_video_codec_support_h265hw(3001);      /**< 值类型为bool, 字符类型为"true" or "false"：表示当前设备是否支持h264的硬件编解码。
         */

        private int value;

        Option(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }

    public static enum LoggingSeverity {
        LS_SENSITIVE, LS_VERBOSE, LS_INFO, LS_WARNING, LS_ERROR
    }

    public enum ProxyType {
        PROXY_HTTPS(1),
        PROXY_SOCKS5(2);

        private int value;
        ProxyType(int i) {
            value = i;
        }

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }
    };

    /**
     * AVD引擎回调接口类
     * <p>
     * AVD引擎回调接口定义了引擎接口操作的异步返回。
     */
    public static interface Listener {
        /// 异步返回

        /**
         * 初始化引擎操作异步返回
         *
         * @param[in] result 初始化错误代码。
         * @sa ready
         */
        public void onInitResult(int result);

        /**
         * 反初始化引擎通知
         *
         * @param[in] reason 反初始化错误代码。
         * @note 此函数不是引擎的uninit的异步返回，uninit是同步操作。此函数是SDK内部检测到引擎出现了问题后通知应用层的回调。
         */
        public void onUninitResult(int reason);
        //

        /**
         * 获取房间信息操作异步返回
         *
         * @param[in] result 获取房间错误代码。
         * @param[in] room 房间信息。
         * @sa getRoomByRoomId
         * @sa getRoomByAppRoomId
         */
        public void onGetRoomResult(int result, RoomInfo room);

        /**
         * 查询房间信息操作异步返回
         *
         * @param[in] result 查询房间错误代码。
         * @param[in] rooms 房间信息列表。
         * @sa findRoom
         */
        public void onFindRoomsResult(int result, List<RoomInfo> rooms);

        /**
         * 安排房间操作异步返回
         *
         * @param[in] result 安排房间错误代码。
         * @param[in] roomId 房间Id。
         * @sa scheduleRoom
         */
        public void onScheduleRoomResult(int result, String roomId);

        /**
         * 取消房间操作异步返回
         *
         * @param[in] result 取消房间错误代码。
         * @param[in] roomId 房间Id。
         * @sa cancelRoom
         */
        public void onCancelRoomResult(int result, String roomId);
        /** 呼叫外部设备回调
        * @param[in] result 错误代码。
        * @param[in] userId 设备用户id。
        * @sa callOutgoingDevice
        */
        public void onCallOutgoingDeviceResult(int result, String userId);
    }

    public static interface GetUsersCountResult {
        /**
         * 获取房间中参会者人数异步返回
         *
         * @param[in] result 错误代码。
         * @param[in] usersCount 房间参会者人数。
         * @param[in] roomId 房间Id。
         * @sa getUsersCount
         */
        public void onGetUsersCountResult(int result, int usersCount, String roomId);
    }
    public static interface NetworkGatewayAdapter{
        ///
        /** 通过回调获取代理网关地址
         *
         * @param[in] ip 待转换ip。
         * @param[in] port 待转换port。
         * @return 返回通过代理网关转换后的ip:port组合成的字符串
         */
        public String onGetGatewayAddress(String ip, int port);
    }
    public static void setSOName(String asoName) {
        soName = asoName;
    }

    public static void runOnLoopThreadSync(Runnable runnable) {
        ToolKit.runOnMainThreadSync(runnable);
    }

    public static void runOnMainThreadAsync(Runnable runnable) {
        ToolKit.runOnMainThreadAsync(runnable);
    }

    /**
     * 获取AVD引擎对象
     *
     * @return 返回AVD引擎。
     * @note AVD引擎是一个单例对象，一个进程空间只存在一个引擎。
     */
    public static AVDEngine instance() {
        return SingletonHolder.INSTANCE;
    }

    @Nullable private Context appcontext = null;
    private long nativeEngine;
    private long nativeListener = 0;
    private long nativeNetworkGatwayListener = 0;
    @Nullable private Listener listener4cb = null;
    @Nullable private Handler listenerHandler = null;
    @Nullable private SDKListener listener4native = null;

    private static class SingletonHolder {
        static final AVDEngine INSTANCE = new AVDEngine();
    }

    private AVDEngine() {
        {
            Log.i(TAG, "loadLibrary " + soName + ".so  abs:" + Build.CPU_ABI + " model:" + Build.MODEL + " man:" + Build.MANUFACTURER
                    + " asdk:" + Build.VERSION.SDK_INT + " serial:" + Build.SERIAL);
            Log.i(TAG, "deviceInfo: " + getDeviceInfo());
            System.loadLibrary(soName);
        }
        nativeEngine = nativegetEngine();
        if (0 == nativeEngine) {
            throw new RuntimeException("Failed to Create Engine!");
        }
    }

    private static String getDeviceInfo() {
        StringBuffer sb = new StringBuffer();
        sb.append("board:" + Build.BOARD);
        sb.append(",boot:" + Build.BOOTLOADER);
        sb.append(",brand:" + Build.BRAND);
        sb.append(",device:" + Build.DEVICE);
        sb.append(",display:" + Build.DISPLAY);
        sb.append(",radio:" + Build.getRadioVersion());
        sb.append(",finger:" + Build.FINGERPRINT);
        sb.append(",handware:" + Build.HARDWARE);
        sb.append(",host:" + Build.HOST);
        sb.append(",id:" + Build.ID);
        sb.append(",serial:" + Build.SERIAL);
        sb.append(",product:" + Build.PRODUCT);
        sb.append(",tags:" + Build.TAGS);
        sb.append(",time:" + Build.TIME);
        sb.append(",type:" + Build.TYPE);
        sb.append(",user:" + Build.USER);
        //
        //sb.append(": vos:" + Build.VERSION.BASE_OS);
        sb.append(",vcname:" + Build.VERSION.CODENAME);
        sb.append(",vsdki:" + Build.VERSION.SDK_INT);
        sb.append(",vsdk:" + Build.VERSION.SDK);
        sb.append(",vcr:" + Build.VERSION.RELEASE);
        return sb.toString();
    }

    @Nullable public Context getContext() {
        return this.appcontext;
    }

    ///

    /**
     * 初始化引擎
     *
     * @param[in] context android应用程序上下文对象。
     * @param[in] listener 引擎回调指针。
     * @param[in] severuri 服务器uri。
     * @param[in] appkey 叁体公司分发给应用公司的应用秘钥。
     * @param[in] secretkey 叁体公司分发给应用公司的安全秘钥。
     * @note AVD引擎在做任何功能操作前，必须先要初始化引擎成功。引擎初始化是一个异步操作，调用此接口后等待回调中函数onInitResult回调，判断是否初始化成功。
     * @return 返回错误代码。
     */
    public int init(Context context, Listener listener, String severuri, String appkey, String secretkey) {
        Tlog.d(TAG, "initwithsecretkey");
        int ret = initJava(context, listener, severuri, true);
        if (ErrorCode.AVD_OK != ret) {
            return ret;
        }
        return nativeinit(this.nativeListener, severuri, appkey, secretkey);
    }

    public int init(Context context, Listener listener, String severuri, String appkey, String secretkey, boolean usedcamera) {
        Tlog.d(TAG, "initwithsecretkey2");
        int ret = initJava(context, listener, severuri, usedcamera);
        if (ErrorCode.AVD_OK != ret) {
            return ret;
        }
        return nativeinit(this.nativeListener, severuri, appkey, secretkey);
    }

    /**
     * 初始化引擎
     *
     * @return 返回错误代码。
     * @param[in] context android应用程序上下文对象。
     * @param[in] listener 引擎回调指针。
     * @param[in] severuri 服务器uri。
     * @param[in] token 认证后的凭证。
     * @note AVD引擎在做任何功能操作前，必须先要初始化引擎成功。引擎初始化是一个异步操作，调用此接口后等待回调中函数onInitResult回调，判断是否初始化成功。
     */
    public int init(Context context, Listener listener, String severuri, String token) {
        Tlog.d(TAG, "initwithtoken");
        int ret = initJava(context, listener, severuri, true);
        if (ErrorCode.AVD_OK != ret) {
            return ret;
        }
        return nativeinitWithToken(this.nativeListener, severuri, token);
    }

    /**
     * 初始化引擎
     *
     * @return 返回错误代码。
     * @param[in] context   引擎运行的app上下文。
     * @param[in] *listener 引擎回调指针。
     * @param[in] severuri 服务器uri。
     * @param[in] oemName  叁体公司Open-AVD产品OEM的厂家名称。
     * @param[in] usedcamera 是否使用视频，若不使用，可以加快引擎初始化。
     * @note AVD引擎在做任何功能操作前，必须先要初始化引擎成功。引擎初始化是一个异步操作，调用此接口后等待回调中函数onInitResult回调，判断是否初始化成功。
     */
    public int initWithOEM(Context context, Listener listener, String severuri, String oemName, boolean usedcamera) {
        Tlog.i(TAG, "initWithOEM begin...");
        long tsbegin = System.nanoTime();
        int ret = initJava(context, listener, severuri, usedcamera);
        if (ErrorCode.AVD_OK != ret) {
            return ret;
        }
        ret = nativeinitWithOEM(this.nativeListener, severuri, oemName);
        Tlog.i(TAG, "initWithOEM end. timespan=" + (System.nanoTime() - tsbegin));
        if (0 == ret) {
            doafterinitok();
        }
        return ret;
    }

    /**
     * 程序从后台切换回前台
     */
    public void switchToForeground() {
        Tlog.i(TAG, "switchToForeground, working:" + isWorking() + ",hasrooms:" + Room.hasRooms());
        if (!isWorking()) {
            return;
        }
        // audio route
        if (Room.hasRooms()) {
            AudioRoute.instance().init();
        }
    }

    protected void doafterinitok() {

//        instance().setOption(Option.eo_camera_mode_frontback, "true");
        //instance().setOption(Option.eo_test_rtc_string_1, getTee3Dir()+"recorder/");
        //instance().setOption(Option.eo_audio_aec_Enable, "true");
    }

    private boolean isH264HwSupported = false;
    private boolean isVP8HwSupported = false;
    private boolean isH265HwSupported = false;
    private static final String VIDEO_FLEXFEC_FIELDTRIAL =
            "WebRTC-FlexFEC-03-Advertised/Enabled/WebRTC-FlexFEC-03/Enabled/";
    private static final String VIDEO_VP8_INTEL_HW_ENCODER_FIELDTRIAL = "WebRTC-IntelVP8/Enabled/";
    private static final String DISABLE_WEBRTC_AGC_FIELDTRIAL =
            "WebRTC-Audio-MinimizeResamplingOnMobile/Enabled/";
    private static final String VIDEO_CODEC_H264 = "H264";
    private static final String VIDEO_CODEC_H264_BASELINE = "H264 Baseline";
    private static final String VIDEO_CODEC_H264_HIGH = "H264 High";
    private static final String AUDIO_CODEC_OPUS = "opus";
    private static final String AUDIO_CODEC_ISAC = "ISAC";

    private int initJava(Context context, Listener listener, String severuri, boolean usedcamera) {
        if (isWorking()) {
            Tlog.i(TAG, "initJava, return isworking");
            return ErrorCode.Err_Already_Initialized;
        }
        if (null == context || null == severuri || severuri.isEmpty()) {
            Tlog.e(TAG, "initJava, context or severuri invalid.");
            return ErrorCode.Err_Invalid_Arg;
        }
        this.appcontext = context;
        enableLog2SDK(true);
        Tlog.i(TAG, "initJava, context:" + context + ",listener:" + listener + ",severuri:" + severuri + ",usedcamera=" + usedcamera);
        if (null == rootEglBase) {
            rootEglBase = EglBase.create();
        }
        long tsbegin = System.nanoTime();

        peerConnectionParameters = new PeerConnectionParameters(true, false,
                false, 640, 480, 15,
                500*1000, VIDEO_CODEC_H264_HIGH,
                true,
                false,
                30*1000, AUDIO_CODEC_ISAC,
                false,
                false,
                false,
                true,
                false,
                false,
                false,
                false,
                false,
                false);
        String fieldTrials = "";
        if (peerConnectionParameters.videoFlexfecEnabled) {
            fieldTrials += VIDEO_FLEXFEC_FIELDTRIAL;
            Log.d(TAG, "Enable FlexFEC field trial.");
        }
        fieldTrials += VIDEO_VP8_INTEL_HW_ENCODER_FIELDTRIAL;
        if (peerConnectionParameters.disableWebRtcAGCAndHPF) {
            fieldTrials += DISABLE_WEBRTC_AGC_FIELDTRIAL;
            Log.d(TAG, "Disable WebRTC AGC field trial.");
        }
        PeerConnectionFactory.InitializationOptions i_options = PeerConnectionFactory.InitializationOptions.builder(context)
                .setFieldTrials(fieldTrials)
                .setEnableVideoHwAcceleration(peerConnectionParameters.videoCodecHwAcceleration)
                .setEnableInternalTracer(true)
                .setInjectableLogger(Tlog.getRtcLogger(), getRtcServerity())
                .createInitializationOptions();
        PeerConnectionFactory.initialize(i_options);

        nativeInitializeAndroidGlobals(context, fieldTrials, true, peerConnectionParameters.videoCodecHwAcceleration);
        Tlog.d(TAG, "initJava, nativeInitializeAndroidGlobals timespan=" + (System.nanoTime() - tsbegin));

//        setOption(Option.eo_internal_hardware_identifier, getMachineId());
        tsbegin = System.nanoTime();
        this.isH264HwSupported = MediaCodecVideoEncoder.isH264HwSupported();
        this.isVP8HwSupported = MediaCodecVideoEncoder.isVp8HwSupported();
        this.isH265HwSupported = MediaCodecVideoEncoder.isH265HwSupported();
        if (null != this.appcontext) {
            NetworkProber.instance().init(this.appcontext);
            String appinfo = getAppInfo();
            String hwcodec = (this.isH264HwSupported ? "-h264" : "") + (this.isH265HwSupported ? "-h265" : "") + (this.isVP8HwSupported ? "-vp8" : "");
            if (hwcodec.isEmpty()) {
                hwcodec = "none";
            }
            appinfo += (". hw codec supported:" + hwcodec);
            log2SDK(LoggingSeverity.LS_INFO, appinfo);
        }
        initHandler();
        this.listener4cb = listener;
        this.nativeListener = nativeCreateListener(this.listener4native);
        Tlog.d(TAG, "initJava, NetworkProber nativeListener timespan=" + (System.nanoTime() - tsbegin));

        tsbegin = System.nanoTime();

        PeerConnectionFactory.Options c_options = new PeerConnectionFactory.Options();
        createPeerConnectionFactoryInternal(c_options);
        Tlog.d(TAG, "initJava, nativeinitConnectionFactory timespan=" + (System.nanoTime() - tsbegin));
        return 0;
    }


    /**
     * Peer connection parameters.
     */
    public static class PeerConnectionParameters {
        public final boolean videoCallEnabled;
        public final boolean loopback;
        public final boolean tracing;
        public final int videoWidth;
        public final int videoHeight;
        public final int videoFps;
        public final int videoMaxBitrate;
        public final String videoCodec;
        public final boolean videoCodecHwAcceleration;
        public final boolean videoFlexfecEnabled;
        public final int audioStartBitrate;
        public final String audioCodec;
        public final boolean noAudioProcessing;
        public final boolean aecDump;
        public final boolean saveInputAudioToFile;
        public final boolean useOpenSLES;
        public final boolean disableBuiltInAEC;
        public final boolean disableBuiltInAGC;
        public final boolean disableBuiltInNS;
        public final boolean disableWebRtcAGCAndHPF;
        public final boolean enableRtcEventLog;
        public final boolean useLegacyAudioDevice;

        public PeerConnectionParameters(boolean videoCallEnabled, boolean loopback, boolean tracing,
                                        int videoWidth, int videoHeight, int videoFps, int videoMaxBitrate, String videoCodec,
                                        boolean videoCodecHwAcceleration, boolean videoFlexfecEnabled, int audioStartBitrate,
                                        String audioCodec, boolean noAudioProcessing, boolean aecDump, boolean saveInputAudioToFile,
                                        boolean useOpenSLES, boolean disableBuiltInAEC, boolean disableBuiltInAGC,
                                        boolean disableBuiltInNS, boolean disableWebRtcAGCAndHPF, boolean enableRtcEventLog,
                                        boolean useLegacyAudioDevice) {
            this.videoCallEnabled = videoCallEnabled;
            this.loopback = loopback;
            this.tracing = tracing;
            this.videoWidth = videoWidth;
            this.videoHeight = videoHeight;
            this.videoFps = videoFps;
            this.videoMaxBitrate = videoMaxBitrate;
            this.videoCodec = videoCodec;
            this.videoFlexfecEnabled = videoFlexfecEnabled;
            this.videoCodecHwAcceleration = videoCodecHwAcceleration;
            this.audioStartBitrate = audioStartBitrate;
            this.audioCodec = audioCodec;
            this.noAudioProcessing = noAudioProcessing;
            this.aecDump = aecDump;
            this.saveInputAudioToFile = saveInputAudioToFile;
            this.useOpenSLES = useOpenSLES;
            this.disableBuiltInAEC = disableBuiltInAEC;
            this.disableBuiltInAGC = disableBuiltInAGC;
            this.disableBuiltInNS = disableBuiltInNS;
            this.disableWebRtcAGCAndHPF = disableWebRtcAGCAndHPF;
            this.enableRtcEventLog = enableRtcEventLog;
            this.useLegacyAudioDevice = useLegacyAudioDevice;
        }
    }

    private PeerConnectionParameters peerConnectionParameters;
    private EglBase rootEglBase = null;
    protected EglBase.Context getEglBaseContext()  {
        return (null == rootEglBase) ? null : rootEglBase.getEglBaseContext();
    }
    private boolean preferIsac;
    private long nativeFactory = 0;
    private void createPeerConnectionFactoryInternal(PeerConnectionFactory.Options options) {

        if (peerConnectionParameters.tracing) {
            PeerConnectionFactory.startInternalTracingCapture(
                    Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
                            + "webrtc-trace.txt");
        }

        // Check if ISAC is used by default.
        preferIsac = peerConnectionParameters.audioCodec != null
                && peerConnectionParameters.audioCodec.equals(AUDIO_CODEC_ISAC);

        final AudioDeviceModule adm = peerConnectionParameters.useLegacyAudioDevice
                ? createLegacyAudioDevice()
                : createJavaAudioDevice();

        // Create peer connection factory.
        if (options != null) {
            Log.d(TAG, "Factory networkIgnoreMask option: " + options.networkIgnoreMask);
        }
        final boolean enableH264HighProfile =
                VIDEO_CODEC_H264_HIGH.equals(peerConnectionParameters.videoCodec);
        final VideoEncoderFactory encoderFactory;
        final VideoDecoderFactory decoderFactory;

        if (peerConnectionParameters.videoCodecHwAcceleration) {
            encoderFactory = new DefaultVideoEncoderFactory(
                    rootEglBase.getEglBaseContext(), true /* enableIntelVp8Encoder */, enableH264HighProfile);
            decoderFactory = new DefaultVideoDecoderFactory(rootEglBase.getEglBaseContext());
        } else {
            encoderFactory = new SoftwareVideoEncoderFactory();
            decoderFactory = new SoftwareVideoDecoderFactory();
        }
        VideoCodecInfo[] encs = encoderFactory.getSupportedCodecs();
        Log.d(TAG, "supported encs:" + encs.length);
        for (VideoCodecInfo item : encs) {
            Log.d(TAG, "enc:" + item.name + "" +item.params.toString());
        }
        VideoCodecInfo[] decs = decoderFactory.getSupportedCodecs();
        Log.d(TAG, "supported decs:" + decs.length);
        for (VideoCodecInfo item : decs) {
            Log.d(TAG, "dec:" + item.name + "" +item.params.toString());
        }

        long nativeFactory = nativeinitConnectionFactory(getContext(), options,
                adm == null ? 0 : adm.getNativeAudioDeviceModulePointer(),
                encoderFactory, decoderFactory, 0, 0);
        if (nativeFactory == 0) {
            throw new RuntimeException("Failed to initialize PeerConnectionFactory!");
        }
        Log.d(TAG, "Peer connection factory created.");
    }

    AudioDeviceModule createLegacyAudioDevice() {
        // Enable/disable OpenSL ES playback.cl
        if (!peerConnectionParameters.useOpenSLES) {
            Log.d(TAG, "Disable OpenSL ES audio even if device supports it");
            WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true /* enable */);
        } else {
            Log.d(TAG, "Allow OpenSL ES audio if device supports it");
            WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(false);
        }

        if (peerConnectionParameters.disableBuiltInAEC) {
            Log.d(TAG, "Disable built-in AEC even if device supports it");
            WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
        } else {
            Log.d(TAG, "Enable built-in AEC if device supports it");
            WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(false);
        }

        if (peerConnectionParameters.disableBuiltInNS) {
            Log.d(TAG, "Disable built-in NS even if device supports it");
            WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(true);
        } else {
            Log.d(TAG, "Enable built-in NS if device supports it");
            WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(false);
        }

        //WebRtcAudioRecord.setOnAudioSamplesReady(saveRecordedAudioToFile);

        // Set audio record error callbacks.
        WebRtcAudioRecord.setErrorCallback(new WebRtcAudioRecord.WebRtcAudioRecordErrorCallback() {
            @Override
            public void onWebRtcAudioRecordInitError(String errorMessage) {
                Log.e(TAG, "onWebRtcAudioRecordInitError: " + errorMessage);
                reportError(errorMessage);
            }

            @Override
            public void onWebRtcAudioRecordStartError(
                    WebRtcAudioRecord.AudioRecordStartErrorCode errorCode, String errorMessage) {
                Log.e(TAG, "onWebRtcAudioRecordStartError: " + errorCode + ". " + errorMessage);
                reportError(errorMessage);
            }

            @Override
            public void onWebRtcAudioRecordError(String errorMessage) {
                Log.e(TAG, "onWebRtcAudioRecordError: " + errorMessage);
                reportError(errorMessage);
            }
        });

        WebRtcAudioTrack.setErrorCallback(new WebRtcAudioTrack.ErrorCallback() {
            @Override
            public void onWebRtcAudioTrackInitError(String errorMessage) {
                Log.e(TAG, "onWebRtcAudioTrackInitError: " + errorMessage);
                reportError(errorMessage);
            }

            @Override
            public void onWebRtcAudioTrackStartError(
                    WebRtcAudioTrack.AudioTrackStartErrorCode errorCode, String errorMessage) {
                Log.e(TAG, "onWebRtcAudioTrackStartError: " + errorCode + ". " + errorMessage);
                reportError(errorMessage);
            }

            @Override
            public void onWebRtcAudioTrackError(String errorMessage) {
                Log.e(TAG, "onWebRtcAudioTrackError: " + errorMessage);
                reportError(errorMessage);
            }
        });

        return new LegacyAudioDeviceModule();
    }

    AudioDeviceModule createJavaAudioDevice() {
        // Enable/disable OpenSL ES playback.
        if (!peerConnectionParameters.useOpenSLES) {
            Log.w(TAG, "External OpenSLES ADM not implemented yet.");
            // TODO(magjed): Add support for external OpenSLES ADM.
        }

        // Set audio record error callbacks.
        JavaAudioDeviceModule.AudioRecordErrorCallback audioRecordErrorCallback = new JavaAudioDeviceModule.AudioRecordErrorCallback() {
            @Override
            public void onWebRtcAudioRecordInitError(String errorMessage) {
                Log.e(TAG, "onWebRtcAudioRecordInitError: " + errorMessage);
                reportError(errorMessage);
            }

            @Override
            public void onWebRtcAudioRecordStartError(
                    JavaAudioDeviceModule.AudioRecordStartErrorCode errorCode, String errorMessage) {
                Log.e(TAG, "onWebRtcAudioRecordStartError: " + errorCode + ". " + errorMessage);
                reportError(errorMessage);
            }

            @Override
            public void onWebRtcAudioRecordError(String errorMessage) {
                Log.e(TAG, "onWebRtcAudioRecordError: " + errorMessage);
                reportError(errorMessage);
            }
        };

        JavaAudioDeviceModule.AudioTrackErrorCallback audioTrackErrorCallback = new JavaAudioDeviceModule.AudioTrackErrorCallback() {
            @Override
            public void onWebRtcAudioTrackInitError(String errorMessage) {
                Log.e(TAG, "onWebRtcAudioTrackInitError: " + errorMessage);
                reportError(errorMessage);
            }

            @Override
            public void onWebRtcAudioTrackStartError(
                    JavaAudioDeviceModule.AudioTrackStartErrorCode errorCode, String errorMessage) {
                Log.e(TAG, "onWebRtcAudioTrackStartError: " + errorCode + ". " + errorMessage);
                reportError(errorMessage);
            }

            @Override
            public void onWebRtcAudioTrackError(String errorMessage) {
                Log.e(TAG, "onWebRtcAudioTrackError: " + errorMessage);
                reportError(errorMessage);
            }
        };

        JavaAudioDeviceModule.Builder builder = JavaAudioDeviceModule.builder(getContext());
        if (audioSampleRate != -1) {
            builder.setSampleRate(audioSampleRate);
        }
        return builder.setSamplesReadyCallback(audioSamplesCallback)
                .setUseHardwareAcousticEchoCanceler(!peerConnectionParameters.disableBuiltInAEC)
                .setUseHardwareNoiseSuppressor(!peerConnectionParameters.disableBuiltInNS)
                .setAudioRecordErrorCallback(audioRecordErrorCallback)
                .setAudioTrackErrorCallback(audioTrackErrorCallback)
                .createAudioDeviceModule();
    }

    public AVDEngine setAudioSampleRate(int rate) {
        if (rate >= 8000 && rate <= 192000) {
            audioSampleRate = rate;
        }
        return this;
    }

    /** 导出PCM音频数据 */
    public void setAudioFrameCallback(JavaAudioDeviceModule.SamplesReadyCallback callback) {
        clientAudioSamplesCallback = callback;
    }

    private void reportError(final String errorMessage) {
        Log.e(TAG, "Peerconnection error: " + errorMessage);

    }

    private String getAppInfo() {
        if (null == this.appcontext) {
            return "appinfo: get failed, no appcontext.";
        }
        PackageInfo info = null;
        String appname = this.appcontext.getPackageName();
        PackageManager manager = this.appcontext.getPackageManager();
        try {
            info = manager.getPackageInfo(appname, 0);
        } catch (PackageManager.NameNotFoundException e) {
            return "appinfo: getPackageInfo failed, e:" + e.getMessage();
        }
        String appinfo = "appinfo:" + appname + ",vcode:" + info.versionCode + ",vname:"
                + info.versionName + ",permission:" + getContext().getApplicationInfo().permission;
        return appinfo;
    }

    /**
     * 反初始化引擎
     *
     * @return 返回错误代码。
     * @note AVD反初始化后，引擎中涉及的所有房间对象都无效，引擎上的功能函数都无法正常使用。
     */
    public int uninit() {
        Tlog.i(TAG, "uninit, listener:" + this.listener4cb);
        Room.clearRooms();
        Tlog.d(TAG, "uninit, after clearRooms.");
        int ret = nativeuninit();
        if (this.nativeListener != 0) {
            nativeFreeListener(this.nativeListener);
            this.nativeListener = 0;
        }
        Tlog.d(TAG, "uninit, after FreeListener.");
        this.listener4cb = null;
        this.result_getUsersCount = null;
        VideoRenderer.nativeClearCopyPlane2Buff();
        Tlog.d(TAG, "uninit, after ClearCopyPlane.");
        NetworkProber.instance().unInit();
//        if (null != rootEglBase) {
//            rootEglBase.release();
//            rootEglBase = null;
//        }
        Tlog.i(TAG, "uninit, end.");
        return ret;
    }

    /**
     * 判断引擎是否正常工作
     *
     * @return 返回引擎是否正常工作。
     */
    public boolean isWorking() {
//        assert (0 != nativeEngine);
        return nativeisWorking();
    }
    ///
//    public static final int RESULT_CODE_STARTAUDIO  = 1;
//    public static final int RESULT_CODE_STARTCAMERA = 2;
//    protected boolean checkPermission(String permission, final Activity activity, int reqcode) {
//        int checkResult = ContextCompat.checkSelfPermission(this.appcontext, permission);
//        if (PackageManager.PERMISSION_GRANTED != checkResult) {
//            Tlog.w(TAG, "checkPermission, no permission:"+permission);
//            if (null != activity) {
//                String[] perms = {permission};
//                ActivityCompat.request(activity, perms, reqcode);
//            }
//            return false;
//        }
//        else{
//            Tlog.i(TAG, "checkPermission, has permission:"+permission);
//        }
//        return true;
//    }
//    public boolean checkAudioPermission(final Activity activity)  {
//        // android 6.0以上版本有效； 6.0以下版本无效，需要做真实的录制才能录制成功
//        return checkPermission(android.Manifest.permission.RECORD_AUDIO, activity, RESULT_CODE_STARTAUDIO);
//    }
//    public boolean checkVideoPermission(final Activity activity)  {
//        return checkPermission(android.Manifest.permission.CAMERA, activity, RESULT_CODE_STARTCAMERA);
//    }
    //

    /**
     * 根据房间Id获取房间信息
     *
     * @return 返回错误代码。
     * @param[in] roomId 房间Id。
     * @note 获取房间信息为异步操作，调用此接口后等待回调中函数onGetRoomResult回调，判断是否获取成功。
     */
    public int getRoomByRoomId(String roomId) {
        Tlog.d(TAG, "getRoomByRoomId, roomId:" + roomId);
        assert (0 != nativeEngine);
        return nativegetRoomByRoomId(roomId);
    }

    /**
     * 根据应用层房间Id获取房间信息
     *
     * @return 返回错误代码。
     * @param[in] appRoomId 应用层房间Id。
     * @note 获取房间信息为异步操作，调用此接口后等待回调中函数onGetRoomResult回调，判断是否获取成功。
     */
    public int getRoomByAppRoomId(String appRoomId) {
        Tlog.d(TAG, "getRoomByAppRoomId, appRoomId:" + appRoomId);
        assert (0 != nativeEngine);
        return nativegetRoomByAppRoomId(appRoomId);
    }

    /**
     * 查找房间信息
     *
     * @return 返回错误代码。
     * @param[in] filter 查找条件：json字符串，用房间信息结构中的字段“fieldname=value,...”来表示。
     * @note 查找房间信息为异步操作，调用此接口后等待回调中函数onFindRoomsResult回调，判断是否查找成功。
     */
    public int findRooms(String filter/*jason: fieldname=value,... */) {
        Tlog.d(TAG, "findRooms, filter:" + filter);
        assert (0 != nativeEngine);
        return nativefindRooms(filter);
    }

    /**
     * 安排房间
     *
     * @return 返回错误代码。
     * @param[in] roomInfo 房间信息。
     * @note 安排房间为异步操作，调用此接口后等待回调中函数onScheduleRoomResult回调，判断是否安排成功。
     */
    public int scheduleRoom(RoomInfo roomInfo) {
        Tlog.d(TAG, "scheduleRoom, roominfo:" + roomInfo.toString());
        assert (0 != nativeEngine);
        return nativescheduleRoom(roomInfo);
    }

    /**
     * 取消删除房间
     *
     * @return 返回错误代码。
     * @param[in] roomId 房间Id。
     * @note 取消房间为异步操作，调用此接口后等待回调中函数onCancelRoomResult回调，判断是否取消成功。
     */
    public int cancelRoom(String roomId) {
        Tlog.d(TAG, "cancelRoom, roomId:" + roomId);
        assert (0 != nativeEngine);
        return nativecancelRoom(roomId);
    }

    /**
     * 根据房间号获取房间人数
     *
     * @param[in] roomId 房间Id。
     * @note 获取房间人数为异步操作，调用此接口后等待回调中函数onGetUsersCountResult回调，判断是否取消成功。
     * @return 返回错误代码。
     */
    @Nullable private GetUsersCountResult result_getUsersCount;

    public int getUsersCount(String roomId, GetUsersCountResult result) {
        Tlog.d(TAG, "getUsersCount, roomId:" + roomId + ",result:" + result);
        assert (0 != nativeEngine);
        result_getUsersCount = result;
        return nativegetUsersCount(roomId);
    }

    /** 呼叫外部设备
    *
    * @param[in] name   设备名称。
    * @param[in] roomId 房间Id。
    * @param[in] url    外部地址。
    * @param[in] userId 外部设备用户id，可选项。
    *
    * @note
    * @return 返回错误代码。
    */
    public int callOutgoingDevice(String name, String roomId, String url, String userId){
        Tlog.d(TAG, "callOutgoingDevice, name:" + name + ",roomId:" + roomId + ",url:" + url + ",userId:" + userId);
        return nativecallOutgoingDevice(name, roomId, url, userId);
    }

    /**
     * 设置全局用户信息
     *
     * @return 返回错误代码。
     * @param[in] info 用户信息。
     * @note 全局用户信息设置后，所有房间或直播房间加入都使用此用户信息
     */
    public int setUserInfo(User info) {
        return nativesetUserInfo(info);
    }

    /**
     * 获取全局用户信息
     *
     * @return 返回用户信息。
     */
    public User getUserInfo() {
        return nativegetUserInfo();
    }

    /**
     * 获取应用程序名称
     */
    @Nullable public static String getAppName(Context context) {
        if (null == context) {
            Tlog.w(TAG, "getAppName, context is  null.");
            return null;
        }
        try {
            PackageManager packageManager = context.getPackageManager();
            PackageInfo packageInfo = packageManager.getPackageInfo(
                    context.getPackageName(), 0);
            int labelRes = packageInfo.applicationInfo.labelRes;
            return context.getResources().getString(labelRes);
        } catch (PackageManager.NameNotFoundException e) {
            Tlog.w(TAG, "getAppName, NameNotFoundException: ", e);
        }
        return null;
    }

    /**
     * [获取应用程序版本名称信息]
     *
     * @param context
     * @return 当前应用的版本名称
     */
    @Nullable public static String getVersionName(Context context) {
        if (null == context) {
            Tlog.w(TAG, "getVersionName, context is  null.");
            return null;
        }
        try {
            PackageManager packageManager = context.getPackageManager();
            PackageInfo packageInfo = packageManager.getPackageInfo(
                    context.getPackageName(), 0);
            return packageInfo.versionName;

        } catch (PackageManager.NameNotFoundException e) {
            Tlog.w(TAG, "getVersionName, NameNotFoundException: ", e);
        }
        return null;
    }

    public static String getTee3Dir() {
        String tee3dir = "";
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            tee3dir = Environment.getExternalStorageDirectory().getPath() ;
        }
        if (tee3dir.isEmpty()) {
            tee3dir = Environment.getDataDirectory().getPath();
        }
        tee3dir += "/cn.tee3/";
        isFolderExists(tee3dir);
        return tee3dir;
    }

    static private boolean isFolderExists(String strFolder) {
        File file = new File(strFolder);
        if (!file.exists()) {
            if (file.mkdir()) {
                return true;
            } else {
                return false;
            }
        }
        return true;
    }

    public String getDefaultLogfile() {
        DateFormat format = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
        String appName = getAppName(getContext());
        if (null == appName) {
            appName = "noname";
        }
        String logfile = getTee3Dir() + appName + "_" + format.format(new Date()) + ".log";
        return logfile;
    }

    /**
     * 设置引擎日志配置
     *
     * @return 返回错误代码。
     * @param[in] params 日志等级[sensitive verbose info warning error none ] 以上只可取一个值；
     * 调试输出[debug]		 是否在调试输出；
     * 调试输出[stats]		 是否输出网络流量等统计信息；
     * 日志文件追加[append] 是否追加日志文件（默认同一个日志文件是替换，不追加）；
     * 日志格式[realtstamp] 长日志格式，格式为：MM-DD hh:mm:ss.xxx；缺省是紧缩格式显示为[秒.微秒]表示程序开始后的时间间隔。
     * 两个值时中间空格分隔。
     * @param[in] filename 日志文件名，若无日志文件名，则不写文件。
     */
    private LoggingSeverity severity = LoggingSeverity.LS_WARNING;
    public int setLogParams(String params, String filename) {
        Tlog.i(TAG, "setLogParams, params:" + params + ",filename:" + filename + ",context:" + this.appcontext);
        int ret = nativesetLogParams(params, filename);
        String msg = "systeminfo: [" + Build.MODEL + "," + Build.VERSION.SDK + "," + Build.VERSION.RELEASE
                + "," + Build.DEVICE + "," + Build.MANUFACTURER
                + "] abs:" + Build.CPU_ABI;
        severity = LoggingSeverity.LS_INFO;
        if (params.contains("sensitive")) {
            severity = LoggingSeverity.LS_SENSITIVE;
        }
        if (params.contains("verbose")) {
            severity = LoggingSeverity.LS_VERBOSE;
        }
        if (params.contains("warning")) {
            severity = LoggingSeverity.LS_WARNING;
        }
        if (params.contains("error")) {
            severity = LoggingSeverity.LS_ERROR;
        }
        if (null != this.appcontext) {
            PeerConnectionFactory.updateLogger(Tlog.getRtcLogger(), getRtcServerity());
        }
        log2SDK(LoggingSeverity.LS_ERROR, msg);
        return ret;
    }
    private Logging.Severity getRtcServerity() {
        Logging.Severity rtcsev = Logging.Severity.LS_WARNING;
        switch (severity) {
            case LS_ERROR:
                rtcsev = Logging.Severity.LS_ERROR;
                break;
            case LS_WARNING:
                rtcsev = Logging.Severity.LS_WARNING;
                break;
            case LS_INFO:
                rtcsev = Logging.Severity.LS_INFO;
                break;
            case LS_VERBOSE:
                rtcsev = Logging.Severity.LS_VERBOSE;
                break;
            default:
                break;
        }
        return rtcsev;
    }

    static public void enableLog2SDK(boolean enable) {
        if (enable) {
            AVDEngine.instance();
        }
        Tlog.enableLog2SDK(enable);
    }

    static void log2SDK(LoggingSeverity serv, String msg) {
        nativelog2SDK(serv.ordinal(), msg);
    }

    static long startLogTrace(String param, ILogListener listener) {
        return nativestartLogTrace(listener, param);
    }

    static void stopLogTrace(long listener) {
        nativestopLogTrace(listener);
    }

    /**
     * 设置引擎Dump文件
     *
     * @return 成功返回有效Filepath，失败发回“”。
     * @param[in] dirOrFilename 可以是一个已存在的文件目录必须以‘/’结尾，也可以是详细的文件路径。
     * @note 如果是文件目录，文件名将由SDK产生格式为【"3TeeOpenAVD_"+Version+"_"+ date + ".dmp"】。
     */
    public String setDumpFile(String dirOrFilename) {
        File file = new File(dirOrFilename);

        String directory = "";
        String name = "";
        if (dirOrFilename.endsWith("/")) {
            directory = dirOrFilename;
        } else {
            if (file.isDirectory()) {
                directory = dirOrFilename + "/";
            } else {
                if ((file.getParent() != null)) {
                    directory = file.getParent();
                }
                if (file.getName() != null) {
                    name = file.getName();
                }
            }
        }
        if (directory.isEmpty()) {
            directory = getTee3Dir();
        }
        if (name.isEmpty()) {
            SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.getDefault());
            String date = sDateFormat.format(new Date());
            name = "3TeeOpenAVD_" + getVersion() + "_" + date + ".dmp";
        }
        File dir = new File(directory);
        if (!dir.exists()) {
            Tlog.e(TAG, "setDumpFile, directory is not exist.");
            return "";
        }
        Tlog.d(TAG, "setDumpFile, directory:" + directory + ", name:" + name);
        return nativesetDump(directory, name);
    }

    /**
     * 获取引擎版本号
     *
     * @return 返回引擎版本号。
     */
    public String getVersion() {
        Tlog.d(TAG, "getVersion,");
        return nativegetVersion();
    }

    /**
     * 根据错误代码获取关联的错误信息描述
     *
     * @return 返回错误信息描述。
     * @param[in] code 错误代码。
     */
    public static String getErrorMessage(int code) {
        return nativegetErrorMessage(code);
    }

    // use internal
    public static int initJavaForNoEngine(Context context, boolean usedcamera) {
        Tlog.i(TAG, "initJavaForNoEngine, context:" + context + ",usedcamera=" + usedcamera);
        long tsbegin = System.nanoTime();
        boolean ret = nativeInitializeAndroidGlobals(context, "", true, usedcamera);
        if (!ret) {
            Tlog.e(TAG, "initJavaForNoEngine, nativeInitializeAndroidGlobals failed.");
            return -1;
        }
        Tlog.d(TAG, "initJavaForNoEngine, nativeInitializeAndroidGlobals timespan=" + (System.nanoTime() - tsbegin));

        tsbegin = System.nanoTime();
        //nativeinitConnectionFactory();
        Tlog.d(TAG, "initJavaForNoEngine, nativeinitConnectionFactory timespan=" + (System.nanoTime() - tsbegin));
        return 0;
    }
    //

    /**
     * 设置引擎选项
     *
     * @return 返回错误代码。
     * @param[in] type 引擎选项类型。
     * @param[in] svalue 引擎选项值。
     */
    public int setOption(Option type, String svalue) {
        Tlog.d(TAG, "setOption, type:" + type + ",svalue:" + svalue);
        if (null == type || null == svalue) {
            return ErrorCode.Err_Invalid_Arg;
        }
        return nativesetOption(type.getValue(), svalue);
    }

    /**
     * 获取引擎选项
     *
     * @return 返回引擎选项值。
     * @param[in] type 引擎选项类型。
     */
    public String getOption(Option type) {
        if (Option.eo_video_codec_support_h264hw == type) {
            return this.isH264HwSupported ? "true" : "false";
        }
        if (Option.eo_video_codec_support_h265hw == type) {
            return this.isH265HwSupported ? "true" : "false";
        }
        return nativegetOption(type.ordinal());
    }

    /**
     * 全局设置本地视频预览和发布的分辨率可选项
     *
     * @return 返回是否替换成功。
     * @param[in] highs 清晰分辨率列表集合（建议 width>800）
     * @param[in] normals 普通分辨率列表集合（建议 800>width>320）
     * @param[in] lows 较低分辨率列表集合（建议 width<320）
     */
    public static boolean setSupportedCapabilities(
            List<MVideo.CameraCapability> highs, List<MVideo.CameraCapability> normals, List<MVideo.CameraCapability> lows) {
        return nativesetSupportedCapabilities(highs, normals, lows);
    }

    public void setNetworkGatewayAdapter(NetworkGatewayAdapter gateway){
        if (gateway == null){
            if(0 == nativeNetworkGatwayListener) {
                nativeFreeNetworkGatewayListener(nativeNetworkGatwayListener);
            }
        }else{
            if(0 == nativeNetworkGatwayListener) {
                nativeNetworkGatwayListener = nativeCreateNetworkGatewayListener(gateway);
            }
            nativeSetNetworkGatewayListener(nativeNetworkGatwayListener);
        }
    }

    public static void setSystemInfo(String systemInfo){
        nativesetSystemInfo(systemInfo);
    }

    protected static void reLoadCamerasIfNone() {
        nativereLoadCamerasIfNone();
    }

    /**
     * 设置代理信息
     * @param type      代理的类型. 目前支持Https代理(音视频数据以TCP发送),Socks5代理（音视频数据以UDP发送）
     * @param ip        代理的IP地址
     * @param port      代理的端口
     * @param userName  代理的认证账户(如果不认证,可传递空字符串)
     * @param password  代理的认证密码(如果不认证,可传递空字符串)
     */
    public void setProxy(ProxyType type, String ip, int port, String userName, String password) {
        nativesetProxy(type.getValue(), ip, port, userName, password);
    }

    /**
     * 设置数据加密类型
     * @param type 数据加密方式，值类型DataCryptoType，范围1（AES128），2（AES256），3(SM4)，默认1，前提条件要开启dtls加密，参考ro_media_use_dtls，默认dtls加密的。
     */
    public void setDataCryptoType(int type) {
        nativesetDataCryptoType(type);
    }

    ///
    private static native long nativegetEngine();

    private static native boolean nativeInitializeAndroidGlobals(
            Context context, String trials, boolean enableTracer, boolean video_hw_acceleration);

    private static native long nativeinitConnectionFactory(Context context, PeerConnectionFactory.Options options,
                                                           long nativeAudioDeviceModule, VideoEncoderFactory encoderFactory,
                                                           VideoDecoderFactory decoderFactory, long nativeAudioProcessor,
                                                           long nativeFecControllerFactory);

    private native long nativeCreateListener(Listener listener);

    private native int nativeinit(long nativeListener, String severurl, String appkey, String token);

    private native int nativeinitWithToken(long nativeListener, String severurl, String token);

    private native int nativeinitWithOEM(long nativeListener, String severurl, String oemName);

    private native int nativeuninit();

    private native void nativeFreeListener(long nativeListener);

    private native boolean nativeisWorking();
    //
    private native long nativeCreateNetworkGatewayListener(NetworkGatewayAdapter listener);
    private native int nativeSetNetworkGatewayListener(long nativeListener);
    private native int nativeFreeNetworkGatewayListener(long nativeListener);
    //
    private native int nativegetRoomByRoomId(String roomId);

    private native int nativegetRoomByAppRoomId(String appRoomId);

    private native int nativefindRooms(String filter);

    private native int nativescheduleRoom(RoomInfo roomInfo);

    private native int nativecancelRoom(String roomId);

    private native int nativegetUsersCount(String roomId);

    private native int nativecallOutgoingDevice(String name, String roomId, String url, String userId);

    //
    private native int nativesetOption(int type, String svalue);

    private native String nativegetOption(int type);

    private native static int nativesetLogParams(String params, String filename);

    private native static String nativegetVersion();

    private native static String nativesetDump(String dir, String filename);

    private native static String nativegetErrorMessage(int code);

    private native static void nativelog2SDK(int sev, String msg);

    private native static long nativestartLogTrace(ILogListener listener, String param);

    private native static void nativestopLogTrace(long listener);

    //
    private static native int nativesetUserInfo(User info);

    private static native User nativegetUserInfo();

    //
    private static native void nativereLoadCamerasIfNone();

    private static native boolean nativesetSupportedCapabilities(
            List<MVideo.CameraCapability> highs, List<MVideo.CameraCapability> normals, List<MVideo.CameraCapability> lows);

    private static native void nativesetSystemInfo(String systemInfo);

    private native void nativesetProxy(int type, String ip, int port, String userName, String password);

    private native void nativesetDataCryptoType(int type);

    ///
    private final int msg_onInitResult = 1;
    private final int msg_onUninitResult = 2;
    private final int msg_onGetRoomResult = 3;
    private final int msg_onFindRoomsResult = 4;
    private final int msg_onScheduleRoomResult = 5;
    private final int msg_onCancelRoomResult = 6;
    private final int msg_onGetUsersCountResult = 7;
    private final int msg_onCallOutgoingDeviceResult = 8;

    private boolean initHandler() {
        if (null == listener4native) {
            listener4native = new SDKListener();
        }
        if (null != listenerHandler) {
            return true;
        }
        AVDEngine.runOnLoopThreadSync(new Runnable() {
            @Override
            public void run() {
                initHandler_main();
            }
        });
        return true;
    }

    @SuppressWarnings("unchecked")
    private List<RoomInfo> toRoomList(Object obj) {
        return (List<RoomInfo>) obj;
    }
    private boolean initHandler_main() {
        this.listenerHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                Tlog.v(TAG, "handleMessage, msg:" + msg.toString());
                if (null == listener4cb) {
                    super.handleMessage(msg);
                    return;
                }
                switch (msg.what) {
                    case msg_onInitResult:
                        listener4cb.onInitResult(msg.arg1);
                        break;
                    case msg_onUninitResult:
                        listener4cb.onUninitResult(msg.arg1);
                        break;
                    case msg_onGetRoomResult:
                        listener4cb.onGetRoomResult(msg.arg1, (RoomInfo) msg.obj);
                        break;
                    case msg_onFindRoomsResult:
                        listener4cb.onFindRoomsResult(msg.arg1, toRoomList(msg.obj));
                        break;
                    case msg_onScheduleRoomResult:
                        listener4cb.onScheduleRoomResult(msg.arg1, (String) msg.obj);
                        break;
                    case msg_onCancelRoomResult:
                        listener4cb.onCancelRoomResult(msg.arg1, (String) msg.obj);
                        break;
                    case msg_onGetUsersCountResult:
                        if (null != result_getUsersCount) {
                            result_getUsersCount.onGetUsersCountResult(msg.arg1, msg.arg2, (String) msg.obj);
                            result_getUsersCount = null;
                        }
                        break;
                    case msg_onCallOutgoingDeviceResult:
                        listener4cb.onCallOutgoingDeviceResult(msg.arg1, (String) msg.obj);
                        break;
                }
                super.handleMessage(msg);
            }
        };
        return true;
    }

    private class SDKListener implements Listener, GetUsersCountResult {
        public SDKListener() {
        }

        @Override
        public void onInitResult(int result) {
            if (0 == result) {
                doafterinitok();
            }
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onInitResult, result, 0);
            listenerHandler.sendMessage(msg);
        }

        @Override
        public void onUninitResult(int result) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onUninitResult, result, 0);
            listenerHandler.sendMessage(msg);
        }

        //
        @Override
        public void onGetRoomResult(int result, RoomInfo room) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onGetRoomResult, result, 0, room);
            listenerHandler.sendMessage(msg);
        }

        @Override
        public void onFindRoomsResult(int result, List<RoomInfo> rooms) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onFindRoomsResult, result, 0, rooms);
            listenerHandler.sendMessage(msg);
        }

        @Override
        public void onScheduleRoomResult(int result, String roomId) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onScheduleRoomResult, result, 0, roomId);
            listenerHandler.sendMessage(msg);
        }

        @Override
        public void onCancelRoomResult(int result, String roomId) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onCancelRoomResult, result, 0, roomId);
            listenerHandler.sendMessage(msg);
        }

        @Override
        public void onGetUsersCountResult(int result, int usersCount, String roomId) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onGetUsersCountResult, result, usersCount, roomId);
            listenerHandler.sendMessage(msg);
        }
        @Override
        public void onCallOutgoingDeviceResult(int result, String userId) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onCallOutgoingDeviceResult, result, 0, userId);
            listenerHandler.sendMessage(msg);
        }
    }

    ///get MachineId
    @Nullable public String getMachineId() {
        String machineId = getMacAddress();
        if (null != machineId && !machineId.equals("")) {
            machineId = "MAC_" + machineId;
            return machineId;
        }
        machineId = getIMEI(getContext());
        if (null != machineId && !machineId.equals("")) {
            machineId = "IMEI_" + machineId;
            return machineId;
        }
        machineId = getSerialNumber();
        if (null != machineId && !machineId.equals("")) {
            machineId = "SN_" + machineId;
            return machineId;
        }
        machineId = getAndroidID(getContext());
        if (null != machineId && !machineId.equals("")) {
            machineId = "AID_" + machineId;
            return machineId;
        }
        Tlog.e(TAG, "getMachineId, cann't get a hardware identifier.");
        return machineId;
    }

    @Nullable private String getMacAddress() { //会返回0000000
        String macSerial = null;
        try {
            Process pp = Runtime.getRuntime().exec("cat /sys/class/net/wlan0/address ");
            InputStreamReader ir = new InputStreamReader(pp.getInputStream());
            LineNumberReader input = new LineNumberReader(ir);
            String str = "";
            for (; null != str; ) {
                str = input.readLine();
                if (str != null) {
                    macSerial = str.trim();// 去空格
                    if (macSerial.matches("[0]+")) {//字符串是否全是0
                        macSerial = null;
                    }
                    break;
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return macSerial;
    }

    @Nullable public String getIMEI(Context context) {//会返回null或者000000
        String IMEINum = null;
        try {
            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            IMEINum = telephonyManager.getDeviceId();
            if (IMEINum != null) {
                if (IMEINum.matches("[0]+")) {
                    IMEINum = null;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return IMEINum;
    }

    @Nullable public String getSerialNumber() {//会返回0123456789ABCDEF
        String SerialNumber = null;
        SerialNumber = Build.SERIAL;
        if (SerialNumber != null) {
            if (SerialNumber.equals("0123456789ABCDEF")) {
                SerialNumber = null;
            }
        }
        return SerialNumber;
    }

    @Nullable public String getAndroidID(Context context) {
        String androidID = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
        return androidID;
    }

    public interface ILogListener{
        /** 日志消息回调
         * @param[in] logmsg 日志消息。
         * @sa onCallBackLog
         */
        void onCallBackLog(String logmsg);
    }

}
